Wypowiedzi
-
W SQL można całkiem dużo, tylko nie koniecznie zawsze ma to sens.
'Warunków brzegowych' w większości rzeczywistych przypadków mamy na tyle dużo i są one na tyle różnorodne, że nie sądzę aby można było stwierdzić że rozwiązanie czysto SQLowe/przetwarzanie batchowe/jakieś inne jest z całą pewnością lepsze dla danej klasy problemów bez dokładnego określenia całości problemu - w tym np. konkretnego zbioru danych, rodzaju generowanego/dopuszczalnego w konkretnym systemie obciążenia, oczekiwanych wyników - i samych możliwości jego realizacji (choćby know-how osób implementujących rozwiązanie, dostępności i jakości narzędzi). Chyba najczęściej łatwiej po prostu sprawdzić metodą testów, jak się dane rozwiązanie w konkretnym przypadku sprawdza, niż próbować opisać całą złożoność problemu teoretycznie.
Swoją drogą jestem ciekaw, jaką można osiągnąć wydajność dla rozwiązań batchowych - podany przez Pana limit 100 rekordów wydaje się bardzo mały (co niby baza miałaby robić z tak niewielkim zbiorem danych żeby zabrało jej to istotną ilość czasu?) i dla jakiego typu operacji jego wydajność istotnie przewyższa 'bezpośrednią' manipulację na danych z pomocą SQLa lub języków wbudowanych serwera baz danych.
Również pozdrawiam :-)Leszek Trenkner edytował(a) ten post dnia 16.01.08 o godzinie 17:05 -
Jeżeli tego typu raport mielibyśmy generować tylko kilka dziennie, to czy faktycznie byłby sens pisać batcha/coś w oparciu o kursory jeżeli zapytanie w czystym SQL zajęło by np. minutę, a bardziej wymyślny kawałek zewnętrznego kodu na dopalaczach robiłby to w 10 sekund? Można by z tego zrobic jakis 'materialized view' i nikt by nawet nie zauważył kiedy się to robi i ile czasu zajmuje.
Rozwiązanie moje jest być może wysoce nieoptymalne, ale robi to co trzeba "w jednym zapytaniu", więc są pewne przesłanki za stosowaniem takich rozwiązań.
Co do indeksowania - ciężko będzie jego wpływ sprawdzić na Postgresie, bo trzeba by było pogenerować sporo danych, żeby zmusić planner do użycia indeksu - dla takich ilości jak mamy w próbce to zawsze łatwiej odczytać tabelę sekwencyjnie z RAM niż cokolwiek robić z indeksem. I nie wiemy jakie są rozkłady wartosći dla produktów, klientów i dat, a to ma - przynajmniej w Postgresie - wpływ na to czy, jakie i w jakiej kolejności indeksy zostaną użyte.
Sam max i pozostałe warunki mogą korzystać ze wsparcia indeksów (można pozakładać indeksy częściowe i klastrować tabelę np. po kliencie i/lub produkcie, co zresztą i dla wersji 'batchowej' okazałoby się korzystne), więc nie sądzę żeby faktyczny średni koszt wyszukania takiego max(id) był aż tak duży, jak Panowie
pesymistycznie szacują.
2 - filter("B"."PROD"<>:B1)
Nie znam aż tak Oracla, ale czy to nie oznacza filtrowanie -wszystkich- rekordów? (dla każdego rekordu n operacji - n^2).
Baza danych stara się szacować kolejność zakładania warunków, tak aby zminimalizować 'n' obecne na każym kroku. Możemy się spodziewać, że ilość rożnych produktów jest istotnie mniejsza od ilości rekordów i klientów, więc dla tego warunku n' może być mocno mniejsze niż n dla całej tabeli czy konkretnego klienta/produktu, bo to będą prawdopodobnie 'tylko' -wszystkie rekordy które spełniły poprzednie warunki-. I być może wystarczy dla każdego produktu tylko raz wygenerować listę rekordów, których "B"."PROD"<>:B1. A może baza zrobi to za pomocą indeksów bitmapowych i prawie wszystkie warunki sprawdzi z wykorzystaniem indeksu?
Teoretyczne oszacowanie wpływu tych czynników może być trudne, bo każda baza może mieć tutaj inny pomysł na wykoanie zapytania, ale faktycznie ma to duże znaczenie dla wydajności.
Czy wyładowanie danych z tabeli ( dodatkowo okraszone jakimiś warunkami ) nie wymaga filtrowania wszytkich rekordów? Bulk dump musi te dane zdaje się i tak odczytać, żeby stwierdzić czy je dalej 'przekazać'.
Jeżeli danych jest mało, to przypuszczam że mechanizm cache'u bazy w połączeniu z SQL da mniejszy koszt operacji I/O niż jakakolwiek próba przerzucenia danych 'na zewnątrz'.
Jak danych będzie dużo, to pewnie lepiej trzymać się z dala od każdego zbędnego zapisu/odczytu dysku, RAM i CPU jak na razie są na ogół znacznie szybsze od dowolnie wymyślnych dysków, więc zrobienie 1000 skanów wyników cząstkowych w RAM może być szybsze niż jeden odczyt sekwencyjny tabeli z dysku.
Wydaje mi się że porównywanie rozwiązań w oparciu o rozważania teoretyczne dotyczące ich złożoności obliczeniowej algorytmu ma sens o ile możemy zapewnić podobny 'koszt jednostkowy' pojedynczej operacji, w 'warunkach bojowych' może się okazać że koszty stałe niektórych operacji powodują - zakładając skończone zbiory danych - że algorytm O(n) może okazać się wolniejszy od O(n^2).
A i tak cała dyskusja ma już teraz charaketer raczej akademicki, bo kilka sposobów rozwiązania problemu przedstawiono, ale nie jesteśmy obecnie w stanie porównać ich wydajności w warunkach rzeczywistych, dla konkretnej implementacji, zbioru danych i sprzętu który miałby to wszystko wykonywać.Leszek Trenkner edytował(a) ten post dnia 16.01.08 o godzinie 15:30 -
Coś tutaj z edycją nie wyszło, więc post poniżej.Leszek Trenkner edytował(a) ten post dnia 16.01.08 o godzinie 15:31
-
Witam,
To mój pierwszy post w okolicy, ale za to długi będzie niemiłosiernie ;-)
Wracając do tematu "Jaki najdłuższy ciąg...", w wersji czysto-SQLowej możemy chyba popełnić takie zapytania (sprawdzone w Postgresie, chyba całkiem ANSI-SQL zgodne).
Dla każdego rekordu ustalamy id poprzedniego rekordu, kiedy ten sam klient kupił ten sam produkt (test to tabela zawierająca przykładowe dane):
select a.id, a.klient, a.prod,
coalesce((select max(id) from test b where b.klient=a.klient and a.prod = b.prod and b.id < a.id ),0) as prev_id
from test a;
Efekt:
id,klient,prod,prev_id
1,1,'A',0
2,1,'A',1
3,1,'B',0
4,1,'A',2
5,1,'A',4
6,1,'A',5
7,1,'A',6
8,1,'C',0
Albo ustalamy id poprzedniego rekordu, kiedy ten sam klient inny produkt:
select a.id, a.klient, a.prod,
coalesce((select max(id) from test b where b.klient=a.klient and a.prod <> b.prod and b.id < a.id ),0) as other_id
from test a;
Efekt:
id,klient,prod,other_id
1,4,'A',0
2,4,'A',0
3,4,'B',2
4,4,'A',3
5,4,'A',3
6,4,'A',3
7,4,'A',3
8,4,'C',7
Można to wrzucić w tabele tymczasowe jak danych będzie dużo/spodziewamy się wielu podobnych zapytań, to może nawet będzie sens dodatkowo poindeksować takie wyniki pośrednie.
Mając taką informację możemy pozliczać count(*) co chcemy w wybranych przedziałach i wybrać sobie z nich min, max czy też avg - co się komu podoba:
Ile razy z rzędu klient kupił X zanim kupił Y, albo kupił cokolwiek innego niż Y:
select klient, prod, max(kupil_a) as max_kupil_a, max(kupil_b) as max_kupil_b, max(kupil_c) as max_kupil_c, max(kupil_inny) as max_kupil_inny from (
select foo.*,
(select count(*) from test c where c.id > foo.prev_id and c.id<foo.id and c.klient=foo.klient and c.prod='A') as kupil_a,
(select count(*) from test c where c.id > foo.prev_id and c.id<foo.id and c.klient=foo.klient and c.prod='B') as kupil_b,
(select count(*) from test c where c.id > foo.prev_id and c.id<foo.id and c.klient=foo.klient and c.prod='C') as kupil_c,
(select count(*) from test c where c.id > foo.prev_id and c.id<foo.id and c.klient=foo.klient and c.prod<>foo.prod) as kupil_inny
from (select a.id, a.klient, a.prod,
coalesce((select max(id) from test b where b.klient=a.klient and a.prod = b.prod and b.id < a.id ),0) as prev_id
from test a) foo) foo2
group by klient,prod
order by klient,prod;
Duże trochę, ale cóż - ma raczej pokazać że się liczy co trzeba, normalnie dla n produktów raczej nie robilibyśmy n+1 podzapytań dla poszczególnych kolumn z danymi :-)
klient, prod, max_a, max_b, max_c, max_inny_niż_prod
1,'A',0,3,1,3
1,'B',5,0,1,6
1,'C',6,1,0,7
Ile razy maksymalnie kupił produkt X z rzędu:
select klient, prod, max(kupil_ten_sam) as max_kupil_ten_sam from (
select bar.*, (select count(*) from test c where c.id > bar.other_id and c.id<=bar.id and c.klient=bar.klient and c.prod=bar.prod) as kupil_ten_sam
from (select a.id, a.klient, a.prod,
coalesce((select max(id) from test b where b.klient=a.klient and a.prod <> b.prod and b.id < a.id ),0) as other_id
from test a) bar) bar2
group by klient,prod
order by klient,prod;
Efekt:
klient, prod, max_ten_sam_z_rzędu
1,'A',4
1,'B',3
1,'C',1
Zakładając że są na bazie sensowne indeksy (na id, kliencie i produkcie), wydaje mi się że żadne z tych zapytań nie powinno być zbyt skomplikowane ani zajmować szczególnie dużo czasu, żeby zarżnąć serwer.
Zbaczając z tematu:
Jeżeli serie danych zakupowych dla pojedynczych klientów byłyby typu B, 2mln*A, C, 5mln*A, B i to losowo wymieszane z 30 mln zakupów dla innych klientów, nie jestem przekonany, że przetwarzanie danych metodą sekwencyjna/batchową/kursorami dałoby jakąś sensowną wydajność - przy pytaniach o konktretne produkty konkretnego klienta wydaje mi się to jakoś mało prawdopodobne.Leszek Trenkner edytował(a) ten post dnia 16.01.08 o godzinie 03:36