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:
jak nie odpowiadać na ogłoszenia o pracę
pewien kanadyjczyk stwierdził, że zgłosi się do pracy w dubaiu – webmaster.
zrobił to w mocno niekonwencjonalnym stylu – można by wręcz powiedzieć, że styl jest tak niekonwencjonalny, że facet może mieć pewność, że w życiu pracy w dubaju nie dostanie.
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.
tapeta na dziś
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:
- pobierz dane dla aktualnego elementu: SELECT * FROM categories WHERE id = [ID];
- jeśli parent_id dla aktualnego elementu jest inny niż NULL wykonaj:
- SELECT * FROM categories WHERE id = [aktualny.parent_id]
- 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:
- pobierz listę wszystkich elementów bezpośrednio pod podanym elementem (vide punkt 3)
- dla każdego z elementów powtórz powyższy punkt
- 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:
- 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 🙂
- 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.
tapeta na dziś
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?