Menu

Type affinity w SQLite: jak naprawdę działają typy kolumn

Jak działa system type affinity w SQLite: pięć rodzajów affinity, reguły wyboru na podstawie deklaracji kolumny i powód, dla którego kolumna INTEGER może przechowywać tekst.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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: jak NUMERIC, 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:

  1. Zawiera INT → INTEGER
  2. Zawiera CHAR, CLOB albo TEXT → TEXT
  3. Zawiera BLOB albo nie ma typu → BLOB
  4. Zawiera REAL, FLOA albo DOUB → REAL
  5. 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 i BLOBy 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 kolumnie NUMERIC staje 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' w NUMERIC zostaje tekstem: nie ma liczby, na którą można by go przekonwertować.
  • Kolumna TEXT zamienia liczby na ich zapis tekstowy.
  • Kolumna BLOB zapisuje 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ