Dlaczego ANSI SQL definiuje SUMĘ (bez wierszy) jako NULL?

28

W ANSI SQL standard określa (Rozdział 6.5, zestaw opis funkcji) następujące zachowanie zagregowanych funkcjami zbiór pusty Wynik:

COUNT(...) = 0
AVG(...) = NULL
MIN(...) = NULL
MAX(...) = NULL
SUM(...) = NULL

Zwracanie wartości NULL dla AVG, MIN i MAX ma idealny sens, ponieważ średnia, minimalna i maksymalna pustego zestawu jest niezdefiniowana.

Ten ostatni jednak denerwuje mnie: Matematycznie, suma zbioru pustego jest dobrze zdefiniowana: 0. Używając 0, neutralny element dodawania, ponieważ podstawowy przypadek sprawia, że ​​wszystko jest spójne:

SUM({})        = 0    = 0
SUM({5})       = 5    = 0 + 5
SUM({5, 3})    = 8    = 0 + 5 + 3
SUM({5, NULL}) = NULL = 0 + 5 + NULL

Zdefiniowanie SUM({})jako nullzasadniczo sprawia, że ​​„brak wierszy” jest specjalnym przypadkiem, który nie pasuje do innych:

SUM({})     = NULL  = NULL
SUM({5})    = 5    != NULL + 5 (= NULL)
SUM({5, 3}) = 8    != NULL + 5 + 3 (= NULL)

Czy jest jakaś oczywista zaleta dokonanego wyboru (SUMA ma wartość NULL), którego przegapiłem?

Heinzi
źródło
5
Tak Zgadzam się: COUNT i SUM nie zachowują się konsekwentnie.
AK

Odpowiedzi:

20

Obawiam się, że powodem jest po prostu to, że reguły zostały ustalone w sposób doraźny (podobnie jak wiele innych „cech” standardu ISO SQL) w czasie, gdy agregacje SQL i ich związek z matematyką były mniej zrozumiałe niż są obecnie (*).

To tylko jedna z bardzo wielu niespójności w języku SQL. Sprawiają, że język jest trudniejszy do nauczenia, trudniejszy do nauczenia się, trudniejszy do zrozumienia, trudniejszy w użyciu, trudniejszy do wszystkiego, czego chcesz, ale tak po prostu jest. Z oczywistych powodów kompatybilności wstecznej nie można zmienić reguł „na zimno” i „tak po prostu” (jeśli komitet ISO opublikuje ostateczną wersję normy, a następnie dostawcy zaczną wdrażać tę normę, to ci dostawcy nie docenią bardzo, jeśli w kolejnej wersji reguły są zmieniane w taki sposób, że istniejące (zgodne) implementacje poprzedniej wersji standardu „automatycznie nie spełniają” nowej wersji ...)

(*) Teraz lepiej jest zrozumieć, że agregacje w pustym zbiorze zachowują się bardziej konsekwentnie, jeśli systematycznie zwracają wartość tożsamości (= to, co nazywacie „elementem neutralnym”) bazowego operatora binarnego. Podstawowym operatorem binarnym dla COUNT i SUM jest suma, a jego wartość identyfikacyjna wynosi zero. W przypadku MIN i MAX ta wartość tożsamości jest odpowiednio najwyższą i najniższą wartością danego typu, jeśli dane typy są skończone. Przypadki takie jak uśrednianie, środki harmoniczne, mediany itp. Są jednak niezwykle skomplikowane i egzotyczne pod tym względem.

Erwin Smout
źródło
Myślę, że null ma sens nad pustym zestawem z min i max. Można powiedzieć, że wartość tożsamości naprawdę nie jest znana, ale suma żadnych wartości wynosi 0 z tego samego powodu, dla którego n * 0 wynosi zawsze 0. Ale min i max są różne. Nie sądzę, aby wynik był poprawnie zdefiniowany bez żadnych rekordów.
Chris Travers
Również avg () w zbiorze zerowym ma sens jako null, ponieważ 0/0 nie jest poprawnie zdefiniowane w tym kontekście.
Chris Travers
5
MIN i MAX nie różnią się tak bardzo. Weź odpowiednio bazowy operator binarny LOWESTOF (x, y) i HIGHESTOF (x, y). Te operatory binarne mają wartość tożsamości. Ponieważ w obu przypadkach (jeśli dany typ jest skończony), rzeczywiście istnieje pewna wartość z, taka, że ​​forall x: LOWESTOF (z, x) = x i forall y: HIGHESTOF (y, z) = y. (Wartość tożsamości nie jest taka sama w obu przypadkach, ale istnieje w obu przypadkach.) Zgadzam się, że wyniki na pierwszy rzut oka wyglądają wyjątkowo sprzecznie z intuicją, ale nie można zaprzeczyć matematycznej rzeczywistości.
Erwin Smout,
@Erwin: Zgadzam się ze wszystkimi twoimi punktami, z wyjątkiem tego, że tożsamość niektórych operacji, takich jak HIGHEST()wiele, nie jest elementem typu danych, jak w przypadku Reals, gdzie tożsamość byłaby -Infinity(i +Infinitydla LOWEST())
ypercubeᵀᴹ
1
@SQL kiwi. Czy zapominasz o statycznym sprawdzaniu typu? Jeśli wyrażenia takie jak SUM () są obsługiwane przez moduł sprawdzania typu statycznego tak, jakby zawsze zwracały liczbę całkowitą, wówczas oczywiście wywołanie SUM () nie powinno czasami zwracać czegoś, co nie jest liczbą całkowitą (np. Pusta relacja).
Erwin Smout,
3

W sensie pragmatycznym NULLprzydatny jest obecny wynik . Rozważ następującą tabelę i instrukcje:

C1 C2
-- --
 1  3 
 2 -1 
 3 -2 

SELECT SUM(C2) FROM T1 WHERE C1 > 9;

SELECT SUM(C2) FROM T1 WHERE C1 < 9;

Pierwsza instrukcja zwraca NULL, a druga zwraca zero. Gdyby pusty zestaw zwrócił zero SUM, potrzebowalibyśmy innego sposobu na odróżnienie prawdziwej sumy zerowej od pustego zestawu, być może używając licznika. Jeśli rzeczywiście chcemy zerowego dla pustego zestawu, prosty COALESCEspełni to wymaganie.

SELECT COALESCE(SUM(C2),0) FROM T1 WHERE C1 > 9;
Leigh Riffel
źródło
1
w rezultacie., SUMA (suma zestawu 1 i zestawu 2) <> SUMA (zestaw1) + SUMA (zestaw2), ponieważ dowolna liczba + NULL = NULL. Czy to ma dla ciebie sens?
AK
2
@Leigh: Używanie w COALESCE()ten sposób nie rozróżnia 0sumy ( ) pustego zestawu od NULLsumy () (powiedzmy, że tabela miała (10, NULL)wiersz.
ypercubeᵀᴹ
Poza tym nadal nie możemy odróżnić SUM (pusty zestaw) od SUM (zestaw jednego lub więcej NULL). Czy w ogóle musimy rozróżniać?
AK
@AlexKuznetsov - Możemy odróżnić sumę pustego zestawu od sumy zestawu zawierającego jedną lub więcej wartości null, o ile przynajmniej jeden wiersz zawiera wartość. Masz rację, że jeśli zestaw zawiera tylko wartości NULL, nie możemy odróżnić zestawu NULL od tego zestawu wszystkich wartości NULL. Nie chodziło mi o to, że jest przydatna w każdym przypadku, a jedynie o to, że może być użyteczna. Jeśli mam SUMkolumnę i wrócę do zera, to wiem bez konieczności sprawdzania, że ​​co najmniej jeden wiersz nie jest NULL używany do wyświetlenia wyniku.
Leigh Riffel,
@ypercude - Masz absolutną rację. Chodzi mi o to, że obecne zachowanie SUMA odróżnia pusty zestaw od zestawu zawierającego wartości (nawet jeśli niektóre są zerowe). Łatwiej jest używać COALESCE, gdy rozróżnienie nie jest wymagane, niż używać czegoś takiego, DECODE(count(c2),0,NULL,sum(c2))kiedy jest.
Leigh Riffel,
-1

Główną różnicą, którą widzę, jest w odniesieniu do typu danych. COUNT ma dobrze określony typ powrotu: liczba całkowita. Wszystkie pozostałe zależą od typu kolumny / wyrażenia, na które patrzą. Ich typ zwrotu musi być zgodny ze wszystkimi elementami zestawu (think float, currency, decimal, bcd, timespan, ...). Ponieważ nie ma zestawu, nie można sugerować typu zwrotu, dlatego NULL jest najlepszą opcją.

Uwaga: w większości przypadków możesz sugerować typ zwracany z typu kolumny, na który patrzysz, ale możesz wykonywać SUMY nie tylko na kolumnach, ale na wszelkiego rodzaju rzeczach. Implikacja typu zwrotu może być bardzo trudna, jeśli nie niemożliwa, w pewnych okolicznościach, szczególnie gdy myślisz o możliwych rozszerzeniach standardu (przychodzą na myśl typy dynamiczne).

TToni
źródło
5
Dlaczego nie możemy sugerować typu zwrotu w SUM(column)wyrażeniu? Czy nie mamy pustych tabel - a tam wszystkie kolumny mają zdefiniowane typy? Dlaczego miałoby być inaczej w przypadku pustego zestawu wyników?
ypercubeᵀᴹ
5
Mylisz się, gdy mówisz „ponieważ nie ma zestawu ”. Jest zestaw. Zestaw wszystkich możliwych wartości deklarowanego typu zaangażowanych kolumn lub wyrażeń. Ten zadeklarowany typ istnieje, nawet jeśli tabela, na którą patrzysz, jest pusta. Nawet puste tabele nadal mają nagłówek. Ten deklarowany typ jest dokładnie „domyślnym typem zwrotu”.
Erwin Smout
Czy oboje rzeczywiście przeczytaliście moją notatkę? Tak, od teraz działałoby to w przypadku SUM opartych na kolumnach. Ale gdy tylko napotkasz zmienną kolumnę typu danych (jeszcze nie w SQL Server - jeszcze), nie masz szczęścia.
TToni
2
Jak w takim przypadku określisz sumę? Jaki będzie wynik 24 + 56.07 + '2012-10-05' + 'Red'? Mam na myśli, że nie ma wątpliwości, jak SUM()się zachowamy, gdy będziemy mieli problem ze zdefiniowaniem dodatku.
ypercubeᵀᴹ
1
@TToni: „szczególnie, gdy myślisz o możliwych rozszerzeniach standardu”, nie jest kontekstem, do którego odnosiło się PO. PO bardzo wyraźnie odnosiło się do obecnej wersji standardu, która nie zawiera żadnego pojęcia „typów dynamicznych” ani niektórych podobnych. (Och, i tylko skomentowałem, ale nie głosowałem. Poza tym drobnym poślizgnięciem, z którym się zakwestionowałem, nic w twojej odpowiedzi nie było wystarczająco błędne, aby uzasadnić głosowanie negatywne. IMO.)
Erwin Smout