mysql rządzi? indeksy pokrywające

przeczytałem właśnie o pewnej funkcjonalności mysql'a o której wcześniej nie wiedziałem. w dodatku – której postgresql nie ma!
chodzi o indeksy pokrywające.
co to jest?
ogólna idea polega na tym, że silnik bazodanowy może wykorzystać do zwracania wartości wartości pobrane z indeksu a nie z tabeli.
kumacie coś z tego? pewnie nie. ja też nie. więc przykład.
mamy tabelkę:

# create table zakupy (id serial primary key, user_id int4, kwota int4);

piszę po postgresowemu, ale chodzi o pokazanie idei.
teraz.
często potrzebujemy zrobić zestawienie nt. łącznej sumy kwot zakupów użytkownika. czyli wynik zapytania:

select sum(kwota) from zakupy where user_id = <costam>

aby to przyspieszyć robimy indeks na pole user_id:

create index x on zakupy (user_id).

i jest lepiej.
system działa tak, że wyszukuje które rekordy w tabeli powinien wziąść pod uwagę (przy pomocy indeksu), potem je znajduje w tabeli, odczytuje, sumuje i zwraca.
proste.
ale wbrew pozorom mało wydajne.
w mysql'u jest coś takiego jak rzeczone indeksy pokrywające.
oznacza to, że jeśli zrobimy indeks:

create index x on zakupy (user_id, kwota).

to mysql użyje tego indeksu w dwóch celach:

  1. do znalezienia odpowiednich rekordów
  2. do pobrania kwot do zsumowania

na czym polega rewolucja? nie trzeba sięgać do tabeli by znaleźć dane!
szybkie. wydajne. zajebiste. tyle, że zżera trochę więcej miejsca na dysku. ale to jest tani zasób.
covering indices nie są domeną mysql'a. mają je też inne bazy. szybki searchmash pokazał, że na pewno są one obecne też w mssql'u (więc pewnie w sybase też). zgaduję, że oracle i db2 też je mają.
a czemu postgres nie? no cóż. temat był kilkukrotnie poruszany na liście pgsql-hackers, ale okazało się, że ze względu na mvcc sprawa jest mocno skomplikowana. i (na razie) nie ma. muszę przyznać, że jest to pierwsza rzecz jakiej (jako postgresowiec) zazdroszczę mysql'owi.

tani samochód od toyoty

toyota zapowiedziała prace nad ultra-tanim samochodem.
jako bezpośrednią przyczyne podano renault/dacia logan. samochód ten jest bardzo tani (jak na nowy) – kosztuje od 5,000€ (w polsce od 27,000pln).
co chce zrobić toyota? przebić tę ofertę. użyją ultra-tanich materiałów i technologii. jak trzeba, to stworzą coś zupełnie nowego. nie podano ceny nowego samochodu, ale ma być to "przynajmniej taniej niż logan".
pomysł jest mocno interesujący – toyota jest znana z jakości. może się okazać, że znajdą dużą ilość chętnych na takie autko. a stać ich na badania nad nowymi materiałami i technologiami – jeśli nie wydarzy się nic niesamowitego – w tym roku toyota prześcignie general motors i zostanie największym wytwórcą samochodów na świecie.

rocznica technologii stealth

dowiedziałem się właśnie przypadkiem (przeglądając archiwum skunk-works), że 16 sierpnia zeszłego roku była okrągła, 50 rocznica początku prac nad tym co z biegiem czasu stało się technologią stealth.
na początku chodziło po prostu o zmniejszenie radarowego echa odbitego od samolotu. rozpoczęto wtedy prace nad "project rainbow". prace zakończone porażką. ale była to pierwsza znana chwila gdy ktoś celowo i (w miarę) systematycznie pracował nad ukryciem samolotu przed radarami.
fajne. w sumie – pewnie sama ta informacja przez długi czas była tajna 🙂

lampy

eh. i kolejny produkt którego nie mam jak kupić 🙁
trafiłem na stronę firmy eurofase. wbrew nazwie nie ma nic wspólnego z europą.
firma zajmuje sie produkcją wszystkiego co świeci. żyrandole, kinkiety, lampy ogrodowe. i to co robią jest zdecydowanie mniej "masowe" – w rozumieniu wyglądu, a i pewnie ceny.
przykłady które mnie zafascynowały – seria żyrandoli opartych na motywach roślinnych:

perl best practices critic

jakiś czas temu damian conway napisał "perl best practices" ("perl. najlepsze rozwiązania"). zawarł w niej szereg sugestii jak pisać.
książkę ogólnie polecam, choć nie zgadzam się ze wszystkim co napisał. ale to już inna bajka.
w oparciu o to co napisał, powstał program: perlcritic (można go zainstalować przez "install Perl::Critic" w shellu cpanowym).
program ten analizuje twój program perlowy i wypisuje błędy (błędy czytaj konstrukcje inne niż zalecane przez damiana). wraz z odnośnikami do numerów stron w książce!
warto zobaczyć jak to wygląda. przykładowo. dla jednego z moich (działających!) programów wynik perlcritica wygląda tak:

=> perlcritic archiveMails.pl
Code before strictures are enabled at line 9, column 1.  See page 429 of PBP.  (Severity: 5)
Integer with leading zeros at line 75, column 55.  See page 58 of PBP.  (Severity: 5)
Don't modify $_ in list functions at line 94, column 37.  See page 114 of PBP.  (Severity: 5)

nie najgorzej. nie? zobaczmy co się dzieje jak każę mu wyświetlać wszystkie błędy, a nie tylko krytyczne:
ojć. całości nie pokażę. za dużo, ale to mogę pokazać:

=> perlcritic -1 archiveMails.pl  | wc -l
69

to mało mówi. więc zróbmy prostą statystykę:

=> perlcritic -1 archiveMails.pl  | perl -pe 's/at line \d+, column \d+/at line X, column Y/' | sort | uniq -c | sort -nr
     14 Mixed-case variable name(s) at line X, column Y.  See page 44 of PBP.  (Severity: 1)
     10 Regular expression without "/x" flag at line X, column Y.  See page 236 of PBP.  (Severity: 3)
      9 Regular expression without "/m" flag at line X, column Y.  See page 237 of PBP.  (Severity: 2)
      6 Builtin function called with parens at line X, column Y.  See page 13 of PBP.  (Severity: 1)
      4 Postfix control "unless" used at line X, column Y.  See pages 96,97 of PBP.  (Severity: 2)
      3 Useless interpolation of literal string at line X, column Y.  See page 51 of PBP.  (Severity: 1)
      3 Subroutine does not end with "return" at line X, column Y.  See page 197 of PBP.  (Severity: 4)
      2 "unless" block used at line X, column Y.  See page 97 of PBP.  (Severity: 2)
      2 Mixed-case subroutine name at line X, column Y.  See page 44 of PBP.  (Severity: 1)
      2 File handle for "print" is not braced at line X, column Y.  See page 217 of PBP.  (Severity: 1)
      1 RCS keywords $Revision$, $Source$, $Date$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 RCS keywords $Revision$, $HeadURL$, $Date$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 RCS keywords $Id$ not found at line X, column Y.  See page 441 of PBP.  (Severity: 2)
      1 Postfix control "if" used at line X, column Y.  See pages 93,94 of PBP.  (Severity: 2)
      1 Package variable declared or used at line X, column Y.  See pages 73,75 of PBP.  (Severity: 3)
      1 No "VERSION" variable found at line X, column Y.  See page 404 of PBP.  (Severity: 2)
      1 Integer with leading zeros at line X, column Y.  See page 58 of PBP.  (Severity: 5)
      1 Forbid $b before $a in sort blocks at line X, column Y.  See page 152 of PBP.  (Severity: 1)
      1 Double-sigil dereference at line X, column Y.  See page 228 of PBP.  (Severity: 2)
      1 Don't modify $_ in list functions at line X, column Y.  See page 114 of PBP.  (Severity: 5)
      1 Code is not tidy at line X, column Y.  See page 33 of PBP.  (Severity: 1)
      1 Code before warnings are enabled at line X, column Y.  See page 431 of PBP.  (Severity: 4)
      1 Code before strictures are enabled at line X, column Y.  See page 429 of PBP.  (Severity: 5)
      1 Capture variable used outside conditional at line X, column Y.  See page 253 of PBP.  (Severity: 3)

łał. a przypominam – ten kod działa i robi co trzeba.
tak czy inaczej – warto spojrzeć. to co perlcritic pokazuje, to czasem całkowicie nieistotne szczegóły (jak np. mixed case variable name), ale czasem jest to coś co warto poprawić. sam po przeczytaniu best practices sporo zmieniłem w swoim stylu kodowania.

gotowanie na filmie

jak może zauważyliście co jakiś czas robię sesję zdjęciową nt. gotowania.
jakiś czas temu zauwazyłem, że wszystkie książki z przepisami są pisane dla ludzi którzy już umieją gotować. po prostu takie przypominajki. o dużej ilości rzeczy nie mówią, lub traktuja jako oczywiste i warte tylko jednego słowa.
stąd wzięła się wizja fotografowania (nie mam kamery, bo jakbym miał to bym kręcił filmiki).
dziś trafiłem na stronę videojug. zawiera ona filmy pokazujące różne rzeczy które można robić w życiu. nie tylko gotowanie. zajmowanie się zwierzakami, majsterkowanie, sporty, maseczki pielęgnacyjne itd.
ale to część kuchenna mnie zauroczyła.
każdy przepis jest sfilmowany. bez udziału aktorów – po prostu pokazują co i jak się dodaje, jak się miesza, ile czasu. co trzeba mieć. filmy są po angielsku co może lekko utrudniać (ktoś wie czemu w przepisie na jambalayę mówią "green pepper" a pokazują coś co wygląda jak poszatkowana zielona papryka?).
ale poza tym – cód, miód i orzeszki. są przepisy na praktycznie każdy rodzaj żarcia. od zup, przez dania główne, desery aż po takie ciekawostki jak sałatka "coleslaw" znana choćby z kfc.

drzewa w sql’u – metoda “zagnieżdżonych zbiorów”

metodę zagnieżdżonych zbiorów poznałem po raz pierwszy po przeczytaniu którejś z książek joe celko. chyba tej: Advanced SQL Programming, ale na 100% nie jestem pewien.
zagnieżdżone zbiory (nested sets) polegają w duzym skrócie na tym, że każdy element drzewa jest opisany nie jednym id, ale parą liczb. są to w miarę dowolne liczby, z założeniem jedynie takim, że "lewa" jest mniejsza od "prawej", oraz, że obie liczby (lewa i prawa) wszystkich elementów drzewa poniżej danego muszą się mieścić w zakresie (lewa, prawa) swoich rodziców.
skomplikowane? też nie zrozumiałem.
przypomnijmy sobie nasze oryginalne, testowe drzewo:

teraz.
tworzymy sobie taką tabelkę:

# create table nested_sets ( id_left int4 primary key, id_right int4 not null check (id_left < id_right), name text);

jako primary key wybrałem sobie id_left, ale mogłem wybrać też right. w szczególności – wartości w polach id_left i id_right muszą być unikatowe. czyli jeśli w którymś elemencie w polu id_left jest wartość 5, to nie może się ona powtórzyć ani w id_left ani w id_right.
ok. jak nadać numerki?
proste. zaczynamy od elementu głównego, i przyznajemy mu id_left = 1. potem idziemy do jego pierwszego dziecka. jego id_left dajemy kolejny numer. jeśli ten element ma podelementy, to powtarzamy zejście w dół z przyznawaniem kolejnych id_left. jeśli dany element nie ma "dzieci", to nadajemy mu id_right równy kolejnej liczbie. i wracamy piętro wyżej.
mało jasne? pewnie nie umiem za dobrze opisać. więc lecimy w krokach:

  1. elementowi sql, przypisujemy id_left = 1, i schodzimy w dół do "postgresql".
  2. elementowi postgresql, przypisujemy id_left = 2, i ponieważ ma jakieś dzieci, schodzimy w dół – do "linux"
  3. elementowi linux przypisujemy id_left = 3
  4. ponieważ linux nie ma "dzieci", przypisujemy mu id_right = 4 i wracamy wyżej
  5. ponieważ postgresql nie ma "dzieci", przypisujemy mu kolejne id_right. czyli id_right = 5. i wracamy wyżej.
  6. jesteśmy z powrotem w sql. wchodzimy w kolejne dziecko – oracle
  7. elementowi oracle przypisujemy id_left = 6.
  8. itd. aż dojdziemy do ustawienia dla elementu sql, id_right = 18

zwracam uwagę na to, że w tej numeracji widać od razu, że ilość elementów jest równo połową największego id_right. co jest poniekąd logiczne.
cała tabelka wygląda tak:

id_left id_right name
1 18 sql
2 5 postgresql
3 4 linux
6 17 oracle
7 8 solaris
9 14 linux
10 11 glibc1
12 13 glibc2
15 16 windows

ok. jak się pyta taką bazę?
tu uwaga – ten model znam najsłabiej. głównie dlatego, że go osobiście nie lubię. jeśli znajdziecie błąd w tym co poniżej napiszę – proszę o informację. nie jestem nieomylny, a jak już mówiłem – tego modelu drzew nie lubię i nie bawiłem się nim w ogóle.
1. pobranie listy elementów głównych (top-levelowych)

SELECT
    ns1.*
FROM
    nested_sets ns1
    LEFT OUTER JOIN nested_sets ns2 ON (ns1.id_left > ns2.id_left AND ns1.id_right < ns2.id_right)
WHERE
    ns2.id_left IS NULL
;

zwrócić należy uwagę na fakt iż jeśli nasze drzewo ma tylko i wyłącznie 1 element top-levelowy to zapytanie można uprościć do:

# SELECT * FROM nested_sets WHERE id_left = 1;

jeśli stosujemy numerację od 1, lub

# SELECT * FROM nested_sets ORDER BY id_left ASC LIMIT 1;

jeśli numeracja startuje od nieznanej liczby.

2. pobranie elementu bezpośrednio “nad” podanym elementem:

dane wejściowe:

  • ID : id_left elementu
SELECT
ns.*
FROM
nested_sets ns
WHERE
[ID] BETWEEN ns.id_left + 1 AND ns.id_right
ORDER BY ns.id_left DESC LIMIT 1

jeśli zapytanie nic nie zwróci – znaczy to, że dany element był “top-levelowy”.

3. pobranie listy elementów bezpośrednio “pod” podanym elementem

dane wejściowe:

  • ID : id elementu
SELECT
nsc.*
FROM
nested_sets nsp
JOIN nested_sets nsc ON (nsc.id_left BETWEEN nsp.id_left + 1 AND nsp.id_right)
WHERE
nsp.id_left = [ID]
AND NOT EXISTS (
SELECT *
FROM nested_sets ns
WHERE
( ns.id_left BETWEEN nsp.id_left + 1 AND nsp.id_right )
AND
( nsc.id_left BETWEEN ns.id_left + 1 AND ns.id_right )
)

4. pobranie listy wszystkich elementów “nad” danym elementem (wylosowanym)

dane wejściowe:

  • ID : id elementu
SELECT
    ns.*
FROM
    nested_sets ns
WHERE
    [ID] BETWEEN ns.id_left + 1 AND ns.id_right

5. pobranie listy wszystkich elementów “pod” danym elementem (wylosowanym)

dane wejściowe:

  • ID : id elementu
SELECT
    nsc.*
FROM
    nested_sets nsp
    JOIN nested_sets nsc ON nsc.id_left BETWEEN nsp.id_left + 1 AND nsp.id_right
WHERE
    nsp.id_left = 6

6. sprawdzenie czy dany element jest “liściem” (czy ma pod-elementy)

dane wejściowe:

  • ID : id elementu
>SELECT true from nested_sets WHERE id_left = [ID] AND id_right = [ID] + 1;

jeśli zwróci true – to jest liść. jeśli nic nie zwróci – to nie jest liściem.

7. pobranie głównego elementu w tej gałęzi drzewa w której znajduje się dany (wylosowany) element

  • ID : id elementu
SELECT
    ns.*
FROM
    nested_sets ns
WHERE
    [ID] BETWEEN ns.id_left + 1 AND ns.id_right
ORDER BY
    ns.id_left ASC LIMIT 1

podstawową zaletą tego rozwiązania jest to, że bardzo szybko zwraca listę wszystkiego "nad", "pod" czy listę liści.

wadami jest mała intuicyjność, skomplikowane przenoszenie elementów (gdybym miał to zaimplementować, to najprawdopodobniej po prostu po każdym przeniesieniu od nowa bym numerował id_left/id_right.

dodatkowo – część zapytań wymaga albo "order by xxx limit 1", albo subselectów co raczej nie wróży dobrze wydajności. a order by limit 1 nie zawsze można użyć (np. użycie tego w joinach jest już mocno problematyczne).

latający dywan

właśnie przeczytałem o nowym produkcie do domu. zasadniczo dla dzieci, ale ja tam bym się nie obraził jakbym dostał 🙂
jest to .. dywan. z podpórkami typu "górki". w sumie – ciężko opisać. lepiej zobaczcie:

oczywiście konfiguracja górek jest dowolna.
chętni do kupienia? zapraszam tu – ale uprzedzam – nie jest to najtańszy dywanik.