Affinity to preferencja, a nie reguła
SQLite jest typowany dynamicznie. Wartość niesie własną klasę przechowywania (NULL, INTEGER, REAL, TEXT, BLOB), a zadeklarowany typ kolumny nie ogranicza ściśle tego, co możesz w niej zapisać. Zadeklarowany typ nadaje natomiast kolumnie affinity, czyli preferowaną klasę przechowywania, na którą SQLite próbuje konwertować przychodzące wartości.
Zobacz, co się dzieje, gdy affinity nie wystarcza, żeby zatrzymać niezgodność:
Drugi wiersz zapisuje napis 'two' w kolumnie INTEGER. SQLite spróbował przekonwertować 'two' na liczbę, nie dał rady (to nie jest liczba) i i tak zapisał wartość jako TEXT. typeof() pokazuje rzeczywistą klasę przechowywania każdej wartości, a ta nie zawsze zgadza się z deklaracją kolumny.
To zaskakuje osoby przychodzące z Postgresa albo MySQL. Tak jest jednak celowo.
Pięć rodzajów affinity
Każda kolumna w tabeli bez STRICT dostaje dokładnie jedną z nich:
TEXT: preferuje napisy.NUMERIC: preferuje liczby, ale przyjmuje tekst, jeśli nie da się go przekonwertować.INTEGER: jakNUMERIC, ale wartości bez części ułamkowej zapisuje jako liczby całkowite.REAL: preferuje liczby zmiennoprzecinkowe.BLOB: brak preferencji, zapisuje wszystko, co dostanie.
Affinity BLOB nazywa się też „brakiem affinity”: dostajesz ją, gdy w ogóle nie zadeklarujesz typu.
To samo wejście, napis '42', i pięć różnych zapisanych typów. Każda kolumna przekonwertowała wartość (albo nie) zgodnie ze swoją affinity.
Jak SQLite wybiera affinity na podstawie deklaracji
Oto część, na której ludzie się potykają: SQLite nie ma stałej listy „poprawnych” typów. Po nazwie kolumny możesz napisać niemal cokolwiek, a SQLite ustala affinity, przeszukując tekst pod kątem fragmentów, w tej kolejności:
- Zawiera
INT→INTEGER - Zawiera
CHAR,CLOBalboTEXT→TEXT - Zawiera
BLOBalbo nie ma typu →BLOB - Zawiera
REAL,FLOAalboDOUB→REAL - Wszystko inne →
NUMERIC
To cały algorytm. Wyjaśnia sporo dziwactw:
FLOATING_POINTS staje się INTEGER, bo fragment INT występuje w POINTS. Wygrywa pierwsza pasująca reguła, od góry do dołu. Dlatego bezmyślne kopiowanie typów z innej bazy danych może dać coś innego, niż się spodziewasz.
Affinity w praktyce: konwersje przy wstawianiu
Affinity ma największe znaczenie, gdy SQLite decyduje, czy przekonwertować wartość, czy zapisać ją bez zmian. Zasady:
- Affinity
TEXT: liczby iBLOBy są konwertowane na tekst. - Affinity
NUMERIC,INTEGER,REAL: tekst, który wygląda jak liczba, jest konwertowany; tekst, który tak nie wygląda, zostaje tekstem. - Affinity
BLOB: nic nie jest konwertowane.
Wiersz po wierszu:
'123'w kolumnieNUMERICstaje się liczbą całkowitą123. Konwersja tekstu na liczbę się udała i była bezstratna.'12.5'staje się liczbą rzeczywistą12.5.'hello'wNUMERICzostaje tekstem: nie ma liczby, na którą można by go przekonwertować.- Kolumna
TEXTzamienia liczby na ich zapis tekstowy. - Kolumna
BLOBzapisuje wszystko dokładnie tak, jak zostało podane, łącznie z typem.
Niuans INTEGER vs REAL
Affinity INTEGER działa niemal identycznie jak NUMERIC, z jednym wyjątkiem: wartość taka jak 3.0, która nie ma rzeczywistej części ułamkowej, jest zapisywana jako liczba całkowita 3, żeby oszczędzić miejsce.
3.0 trafia do obu kolumn jako INTEGER: ta optymalizacja działa też dla NUMERIC. 3.5 zachowuje część ułamkową i zostaje REAL. Wniosek: nie polegaj na typeof(), żeby sprawdzić, czy kolumna została zadeklarowana jako INTEGER czy REAL. Ta funkcja mówi, co jest faktycznie zapisane, a to może się różnić w zależności od wiersza.
Kiedy affinity daje w kość
Ta elastyczność jest wygodna, dopóki nie przestaje. W prawdziwym kodzie pojawiają się dwa rodzaje problemów:
1. Wkradają się złe dane. Jeśli twoja aplikacja ma błąd, który wysyła 'N/A' do kolumny INTEGER, SQLite to zapisze. Późniejsze zapytania wykonujące obliczenia na tej kolumnie zwracają dziwne wyniki albo NULL. Bez błędu, bez ostrzeżenia, po prostu ciche psucie danych.
2. Porównania zaczynają dziwnie działać. Sortowanie i sprawdzanie równości traktują wartości o różnych klasach przechowywania w różny sposób:
Liczby całkowite sortują się numerycznie, a potem wartości tekstowe sortują się leksykograficznie i trafiają za wszystkie liczby. Dostajesz więc 2, 3, 10 (liczby całkowite w kolejności numerycznej), a potem '20', '100' (napisy w kolejności alfabetycznej). Nie tego chce większość ludzi.
Jeśli kontrolujesz wstawianie danych i starannie je walidujesz, zwykłe tabele są w porządku. Jeśli nie, albo po prostu chcesz, żeby baza pilnowała typów za ciebie, jest lepsza opcja.
Dalej: tabele STRICT
SQLite 3.37 dodał tabele STRICT, które wyłączają affinity i odrzucają wartości niepasujące do zadeklarowanego typu. Dostajesz domyślne typowanie dynamiczne, gdy go chcesz, i wymuszanie typów w stylu Postgresa, gdy go nie chcesz. O tym jest następna strona.
Najczęściej zadawane pytania
Czym jest type affinity w SQLite?
Type affinity to preferowana klasa przechowywania dla kolumny. SQLite ma pięć: TEXT, NUMERIC, INTEGER, REAL i BLOB. Gdy wstawiasz wartość, SQLite próbuje przekonwertować ją na affinity kolumny, ale jeśli konwersja byłaby stratna albo niemożliwa, zapisuje wartość bez zmian. Affinity to wskazówka, a nie twarde ograniczenie.
Jak SQLite ustala affinity kolumny?
SQLite przeszukuje nazwę typu zapisaną w CREATE TABLE pod kątem fragmentów, w tej kolejności: jeśli zawiera INT, to INTEGER; w przeciwnym razie CHAR, CLOB albo TEXT daje TEXT; dalej BLOB (albo brak typu) daje BLOB; dalej REAL, FLOA albo DOUB daje REAL; w pozostałych przypadkach NUMERIC. Dlatego VARCHAR(50) staje się TEXT, a BIGINT staje się INTEGER: wpisane słowa są dopasowywane do wzorców.
Czy kolumna w SQLite może przechowywać wartości niewłaściwego typu?
Tak, w zwykłych tabelach. Kolumna zadeklarowana jako INTEGER bez problemu zapisze napis 'hello', bo affinity tylko sugeruje konwersję. Jeśli chcesz twardego wymuszania typów, użyj tabel STRICT, które od razu odrzucają niepasujące wartości. Omawiamy je w następnej kolejności.