Cel: udokumentować podstawowe przypadki użycia SQLite z klientem shellowym.
SQLite
SQLite (https://sqlite.org/):
- jest Open-Source i w domenie publicznej;
- zawsze wydawał mi się “niepoważną”, zabawkową bazą danych… niesłusznie;
- jest najbardziej rozpowszechnioną bazą danych (prostota i wieloplatformowość sprawiają, że to “baza z wyboru” m.in. dla rozmaitych rozwiązań “wbudowanych”, jak systemy multimedialne w automotive);
- nie jest bazą klient-serwer, tylko wbudowaną. Programy używające
SQLite wywołują funkcje z biblioteki
sqlite
, z którą zostały zlinkowane; - wspiera pełnoprawny SQL i ma zaawansowane opcje, np. indeksy częściowe czy obsługę JSON. Lista ficzerów jest imponująca;
- ponoć pozwala wykonywać operacje zapisu/odczytu danych szybciej niż przy
bezpośrednim I/O z dysku; prawdopodobną przyczyną tego zjawiska jest
jednokrotne używanie
open
/close
przez sqlite (taka sama operacja na wielu plikach wymagałaby wielu wywołań systemowych).
A poza tym:
- mają prostą i czytelną stronę internetową
- używają “nietypowego” systemu kontroli wersji,
fossil-scm
(jest też klon na githubie) - deklarują wsparcie i kompatybilność wsteczną przynajmniej do 2050 r.
- mają szalone, w pozytywnym znaczeniu, testy. 100% pokrycia i dwukierunkowe testowanie wszystkich branchy. I fuzz testy.
Instalacja
- ze strony można pobrać binarki na wszystkie główne platformy (https://sqlite.org/download.html)
- dystrybucje linuksa udostępniają pakiety (np. APTowe
sqlite
,sqlite3
na Debianie/Ubuntu) - każdy szanujący się język programowania ma biblioteki klienckie
Dla wygody przykłady będę uruchamiał w dockerze:
$ docker run --rm -it alpine:latest sh
/ # apk update -q && apk add sqlite -q
/ # sqlite3
SQLite version 3.25.3 2018-11-05 20:37:38
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>
Podstawowe użycie
Uruchamianie
sqlite [options] filename [SQL]
1 plik, 1 baza. Plik podaje się przy starcie, jeśli nie istnieje, zostanie założony. W SQLite3 można także korzystać z bazy danych w pamięci i zapisać ją do pliku w wybranym momencie.
Meta-komendy, czyli polecenia kierowane do interpretera, a nie wprost do biblioteki bazodanowej zaczynają się od kropki - np.:
/ # sqlite test1.sqlite
SQLite version 3.25.3 2018-11-05 20:37:38
Enter ".help" for usage hints.
sqlite> .databases
main: //test1.sqlite
sqlite>
Tak, .help
warto przeczytać. :)
Do sesji można dodać dodatkową bazę danych SQLową komendą ATTACH
:
sqlite> ATTACH './test2.sqlite' AS test2_db;
sqlite> .databases
main: //test1.sqlite
test2_db: //./test2.sqlite
sqlite>
SQL
Wyrażenia działają tak jak w innych dialektach:
sqlite> select 1+3, MAX(3,42), null, "test";
4|42||test
Metakomendami .mode
i .headers
można zmienić tryb wyświetlania na podobny
do innych SQLi:
sqlite> .mode column
sqlite> .headers on
sqlite> select 1+3, MAX(3,42), null, "test";
1+3 MAX(3,42) null "test"
---------- ---------- ---------- ----------
4 42 test
Komendy SQLowe można podawać jako argument (wchodząc w tryb nieinteraktywny):
/ # sqlite3 foo.db 'select 123'
123
Można też czytać je z pliku:
/ # cat tabele.sql
-- średnie miesięczne temperatury w Krakowie (2005-2018 z dziurami)
CREATE TABLE temperatures(month_symbol, avg_temp);
INSERT INTO temperatures VALUES
("I",-2.48),
("II",-1.06),
("III",3.36),
("IV",9.6),
("V",14.18),
("VI",18),
("VII",19.96),
("VIII",19.8),
("IX",13.94),
("X",8.32),
("XI",4.8),
("XII",0.04);
-- mapowania oznaczeń miesięcy
CREATE TABLE months(month_symbol, month);
INSERT INTO months VALUES
("I",1),
("II",2),
("III",3),
("IV",4),
("V",5),
("VI",6),
("VII",7),
("VIII",8),
("IX",9),
("X",10),
("XI",11),
("XII",12);
/ # sqlite3 krk.sqlite < tabele.sql
Reszta SQLa (filtry, joiny, sortowania) też działa:
/ # sqlite3 krk.sqlite
SQLite version 3.25.3 2018-11-05 20:37:38
Enter ".help" for usage hints.
sqlite> .tables
months temperatures
sqlite> SELECT * FROM temperatures where avg_temp > 5;
IV|9.6
V|14.18
VI|18
VII|19.96
VIII|19.8
IX|13.94
X|8.32
sqlite> SELECT avg(avg_temp) FROM temperatures;
9.03833333333333
sqlite>
sqlite> CREATE TABLE temperatures_fixed AS
...> SELECT m.month,t.avg_temp
...> FROM temperatures AS t
...> JOIN months AS m
...> ON t.month_symbol=m.month_symbol;
sqlite>
sqlite> SELECT * from temperatures_fixed LIMIT 4;
1|-2.48
2|-1.06
3|3.36
4|9.6
sqlite>
sqlite> UPDATE temperatures_fixed
...> SET avg_temp=0
...> WHERE month=1;
sqlite>
sqlite> SELECT * from temperatures_fixed LIMIT 4;
1|0
2|-1.06
3|3.36
4|9.6
Rozmaitości
- Typy w SQLite są ”elastyczne”. Baza nie sprawdza, czy np. nie próbujemy wsadzić tekstu do pola typu INT. Nie trzeba też podawać typów przy zakładaniu tabel, co bardzo upraszcza prototypowanie.
- Łatwo zacząć od nowa, kasując plik z bazą.
.dump
pozwala zrzucić bazę w formacie nadającym się do reimportu. Przydatne, jeśli chcemy przeczytać dane, poprawić ręcznie trochę rekordów i wrzucić z powrotem.- Interpreter jest wygodny, ale daleko mu do
mycli
albo chociażpsql
. - SQLite2 nie czyta baz SQLite3 (i vice versa, i bardzo dobrze).
- Jest wiele tematów, które warto jeszcze poruszyć przy okazji innych jednodniówek - transakcje, indeksy, widoki, triggerry, rozszerzenia, równoczesne użycie bazy przez wiele procesów…