nowa mazda

w detroit odbywają się właśnie targi motoryzacyjne. jest sporo nowości, ale praktycznie wszystkie są takie nijakie. ot, trochę przemodelowane wcześniejsze modele.
ale nie wszystkie.
mazda pokazała model ryuga. jest piękna. i cudowna. obejrzeć i poczytać o niej można na stronach autobloga. u mnie tylko 2 zdjęcia by pokazać wam czym się zachwycam:

wykład o klastrowaniu postgresa

za tydzień, w sobotę, 13 stycznia adam buraczewski wygłosi wykład nt. "słonie pracują w stadzie".
tematyka obejmie wszystkie liczące się metody klastrowania postgresa: slony, pgpool, dblink, sequoia, postgres-r i inne.
wykład odbędzie się w gliwicach – jeśli będziecie w pobliżu – polecam. temat ciekawy, a adam ma kolosalną wiedzę. i umiejętność jej przekazania.

drzewa w sql’u – metoda “śledzenie rodzica”

jest to zdecydowanie najbardziej rozpowszechniona metoda przechowywania drzew w bazach.
zakłada ona (zgodnie z naszymy podstawowymi założeniami), że każdy element ma tylko 1 element nadrzędny (lub go nie ma – gdy jest to element główny). dzięki temu całość można zapisać w jednej tabeli
:

$ CREATE TABLE categories (
    id        BIGSERIAL,
    parent_id INT8 REFERENCES categories (id),
    codename  TEXT NOT NULL DEFAULT '',
    PRIMARY KEY (id)
);
CREATE UNIQUE INDEX ui_categories_picn ON categories (parent_id, codename);

nasze testowe drzewo w tej tabeli będzie wyglądało tak:

# SELECT * FROM categories;
 id | parent_id |  codename
----+-----------+------------
  1 |    [null] | sql
  2 |         1 | postgresql
  3 |         1 | oracle
  4 |         2 | linux
  5 |         3 | solaris
  6 |         3 | linux
  7 |         3 | windows
  8 |         6 | glibc1
  9 |         6 | glibc2
(9 rows)

jak widać – same podstawowe danych, jedna tabelka, mała nadmiarowość. wygląda ok. a jak to odpytać?

1. pobranie listy elementów głównych (top-levelowych)

> SELECT * FROM categories where parent_id is null;

2. pobranie elementu bezpośrednio “nad" podanym elementem
dane wejściowe:

  • ID : id elementu
SELECT p.* FROM categories c JOIN categories p ON c.parent_id = p.id WHERE c.id = [ID];

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 * FROM categories WHERE parent_id = [ID];

4. pobranie listy wszystkich elementów “nad" danym elementem (wylosowanym)
dane wejściowe:

  • ID : id elementu

tu pojawia się problem. można to rozwiązać selectem z dużą ilością joinów (tyle joinów ile poziomów kategorii jest powyżej naszego elementu), albo zastosować rozwiązanie algorytmiczne:

  1. pobierz dane dla aktualnego elementu: SELECT * FROM categories WHERE id = [ID];
  2. jeśli parent_id dla aktualnego elementu jest inny niż NULL wykonaj:
    1. SELECT * FROM categories WHERE id = [aktualny.parent_id]
    2. ustaw aktualny na właśnie pobrany

jest to rozwiązanie typu z wieloma zapytaniami, ale rozwiązanie z joinami ma wszystkie wady zapytań z “metody wielu tabel" (nota bene tam też można było zastosować podejście algorytmiczne, realizowane w aplikacji klienckiej)

5. pobranie listy wszystkich elementów “pod" danym elementem (wylosowanym)
dane wejściowe:

  • ID : id elementu

niestety – i tym razem zapisanie tego w postaci pojedynczego zapytania będzie problematyczne. w tym przypadku należy zastosować rozwiązanie rekurencyjne:

  1. pobierz listę wszystkich elementów bezpośrednio pod podanym elementem (vide punkt 3)
  2. dla każdego z elementów powtórz powyższy punkt
  3. powtarzaj tak długo aż dojdziesz do sytuacji gdy już nie ma elementów poniżej.

6. sprawdzenie czy dany element jest “liściem" (czy ma pod-elementy)
dane wejściowe:

  • ID : id elementu
>SELECT count(*) from categories WHERE parent_id = [ID];

jeśli zwróci 0 – to dany element jest liściem.

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

  • ID : id elementu

niestety – jedyną słuszną metodą jest użycie algorytmu z punktu 4 i pobranie ostatniego elementu (takiego dla którego algorytm się kończy, bo parent_id jest NULLem.

podstawową zaletą tego rozwiązania jest to, że stała struktura tabel jest w stanie przechowywać dowolną ilość danych w drzewie – niezależnie od ilości elementów czy poziomów zagłębień.
wadami jest głównie konieczność korzystania z pewnych rozwiązań algorytmicznych zamiast użycia zapytań bazodanowych.

1terabajt na pojedynczym dysku

przez sieć przetoczyła się lawina informacji o tym, że hitachi zaprezentuje na targach ces dysk o pojemności 1 terabajta.
część komentatorów przeoczyła jednakże dwa fakty:

  1. hitachi zaprezentuje ten dysk, ale nie będzie on jeszcze w sprzedaży. sprzedaż ma ruszyć w pierwszym kwartale 2007 roku – czyli w najgorszym przypadku – 31 marca 🙂
  2. seagate też ma zaprezentować swój dysk 1 terowy. i ich dysk też nie będzie od razu w sprzedaży – ma być dostępy w pierwszej połowie roku. czyli – w najgorszym przypadku do 30 czerwca.

tak więc – dużymi krokami zbliża się chwila (znowu) gdy pojemności dysków i pamięci mierzymy innymi jednostkami (giga i tera). dodatkowo interesujące jest to, że seagate już teraz zapowiedział, że w ciągu kilku lat zacznie sprzedawać dyski o pojemności 37.5tera.
kiedy doczekamy się pecetów z media marktu z dyskami 1 tera?

mythbusters

w telewizji (o ile pamiętam na discovery) jest taki program "mythbusters".
w programie tym dwóch speców od efektów specjalnych – adam savage i jamie hyneman (plus ekipa) przeprowadzają testy różnych mitów. np. tego czy moneta zrzucona z drapacza chmur może zabić, albo czy przestrzelenie ściany samolotu w powietrzu powoduje wybuch taki jak na filmach.
trafiłem dziś na stronę gdzie zostały zebrane wszystkie mity z dotychczas emitowanych odcinków mythbusters, wraz z informacją czy mit został obalony czy potwierdzony. fajna sprawa do poczytania.

wyszukiwanie w/g nipu

część z was pewnie kiedyś zaprojektowała system gdzie był przechowywany numer nip.
numer nip jaki jest każdy wie – 10 cyfr, rozdzielonych myślnikami. zasadniczo – myślniki są nieważne. ale czasem ktoś (klient, urząd skarbowy, ktokolwiek) czepia się jak mu się np. nip zmieni z 522-186-96-44 na 522-18-69-644. niby ten sam. ale nie taki sam.
z tego powodu nip powinno się przechowywać w postaci takiej jak user podał.
ale – czy wyszukując nip mamy pewność, że wiemy w jakiej postaci "myślnikowej" dane są wpisane? a co jeśli mamy wpisane "522-186-96-44", a szukamy "522-18-69-644"?
czyli do wyszukiwania przydałoby się aby pamiętać bez myślników.
najprostszą wersją jest zrobienie dwóch kolumn: nip_original, nip_search. ale to jest brzydkie.
ładniej można zrobić to poprzez np. coś takiego:
mamy tabelkę:

create table test (id serial primary key, nip text not null, nazwa text not null);

i na niej zakładamy indeks w ten sposób:

create unique index test_idx on test (regexp_replace(nip, '[^0-9]', '', 'g'));

po czym sprawdzamy:

# explain analyze select * from test where regexp_replace(nip, '[^0-9]', '', 'g') = '1234567890';
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Index Scan using test_idx on test  (cost=0.00..8.29 rows=1 width=54) (actual time=0.167..0.167 rows=0 loops=1)
   Index Cond: (regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) = '1234567890'::text)
 Total runtime: 0.261 ms
(3 rows)

super.
teraz .. dobrze by było jakby dało się wyszukiwać prefixowo – aby np. w aplikacji się "podpowiadało" samo – po wpisaniu kolejnych cyfr.
aby to zrobić musimy sięgnąć po tzw. index opclass (uwaga – to jest konieczne tylko jeśli wasze locale jest inne niż C – ale pewnie jest inne):

drop index test_idx;
create unique index test_idx on test (regexp_replace(nip, '[^0-9]', '', 'g') text_pattern_ops);

no i test:

# explain analyze select * from test where regexp_replace(nip, '[^0-9]', '', 'g') like '1234%';
                                                                                     QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=13.40..922.28 rows=500 width=54) (actual time=0.240..0.457 rows=7 loops=1)
   Filter: (regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~~ '1234%'::text)
   ->  Bitmap Index Scan on test_idx  (cost=0.00..13.27 rows=500 width=0) (actual time=0.162..0.162 rows=7 oops=1)
         Index Cond: ((regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~>=~ 1234'::text) AND (regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~<~ '1235'::text))
 Total runtime: 0.593 ms
(5 rows)

wow.
co prawda trzeba za każdym razem pisać tego regexp_replace'a.
czy na pewno trzeba? nie. wystarczy zrobić wrappera widokiem:

# create view test_view as select *, regexp_replace(nip, '[^0-9]', '', 'g') as search_nip from test;

i potem:

# explain analyze select * from test_view where search_nip like '123%';
                                                                                    QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on test  (cost=13.40..923.53 rows=500 width=54) (actual time=0.375..7.384 rows=96 loops=1)
   Filter: (regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~~ '123%'::text)
   ->  Bitmap Index Scan on test_idx  (cost=0.00..13.27 rows=500 width=0) (actual time=0.199..0.199 rows=96 loops=1)
         Index Cond: ((regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~>=~ '123'::text) AND (regexp_replace(nip, '[^0-9]'::text, ''::text, 'g'::text) ~<~ '124'::text))
 Total runtime: 88.251 ms

super. działa. wyszukuje niezależnie od minusów, dane trzymamy tylko raz, mamy searcha prefixowego. czego chcieć więcej?

patent na … DRM

drm – digital rights management.
ogólna nazwa tycząca się jakichkolwiek metod służących ochronie praw autorskich lub intelektualnych do utworów, tekstów oraz innych "obiektów" zapisanych w postaci cyfrowej.
najsłynniejsze przypadki do css (dvd-css) – system zabezpieczeń dvd, różnego rodzaju drm'y w sklepach z mp3 (np. w itunes) czy software do drm od sony który instalował w systemie rootkita i który kosztował sony ponad 15 milionów dolarów w odszkodowaniach.
czemu o tym piszę? bo alan cox – osoba dosyć znana wszystkim zainteresowanym rozwojem linuksa – jako jądra systemu, zgłosił drm jako technikę którą chce opatentować. oficjalny wniosek został złożony, czekamy na to czy patent zostanie przyznany.
po co patent? dzięki temu alan będzie mógł trzymać "w szachu" wszelkiego rodzaju firmy (riaa/mpaa/…) które chciałyby kiedykolwiek wykorzystać jakieś patenty przeciwko linuksowi.