Resultset to zbiór elementów - krotek (realizowanych w postaci rekordów), z których wszystkie posiadają określone cechy - atrybuty (zmaterializowane w postaci wartości kolumn). Jest to założenie formalne, tzn. że nie wszystkie rekordy muszą dysponować merytorycznie doniosłą informacją zawartą w danej kolumnie (sytuacja NULLa). W rzeczywistości zachodzi czasem potrzeba, aby do zbioru rekordów włączyć wiersze o specjalnym znaczeniu (np. takie z pustymi wartościami w kolumnach, dzielące wizualnie zbiór rekordów na odpowiednie grupy). Oczywiście w sensie formalnym nie będą się one niczym różniły od pozostałych rekordów (tożsamość kolumn), jednak ich funkcja będzie szczególna (formatująca, separacyjna). W takiej sytuacji wymaga się także odpowiedniego uporządkowania całego zbioru, aby nadać mu określoną wizualną postać.
Załóżmy, że posiadamy tabelę
Osoby:
CREATE TABLE Osoby
(
id_osoby int IDENTITY(1,1) PRIMARY KEY,
imie nvarchar(50) NOT NULL,
nazwisko nvarchar(50) NOT NULL,
plec char(1) NOT NULL,
id_pary int NULL
);
Osoby z równymi wartościami w kolumnie
id_pary stanowią parę (nie chcę być posądzony o homofobię, ale w przykładzie dopuszczam tylko pary heteroseksualne w liczebności 2 - jak przystało na parę :) ).
Celem jest wyświetlenie par na takiej zasadzie, że każda para reprezentowana będzie przez 3 rekordy: pierwszy - kobieta, drugi - mężczyzna, trzeci - rekord z pustymi polami dla oddzielenia od kolejnej pary. Oczywiście pary można też pokazać w orientacji horyzontalnej - na zasadzie joina. W sytuacji rekordów z dużą ilością kolumn (np. dane teleadresowe) jeden rekord pod drugim może być jednak zasadniczo lepszym rozwiązaniem.
Napełnijmy tabelę
Osoby testowymi danymi (przy okazji warto zwrócić uwagę na sposób dodawania rekordów, konstrukcja jest mało znana, a co ważne - bardzo wydajna):
INSERT Osoby (imie, nazwisko, plec, id_pary)
EXEC
('
SELECT ''Stefan'', ''Kowalski'', ''m'', 1
SELECT ''Alberto'', ''Mampuaye'', ''m'', NULL
SELECT ''Leokadia'', ''Puszczalska'', ''k'', 1
SELECT ''Krystyka'', ''Waleczna'', ''k'', 2
SELECT ''Eryk'', ''Tajemniczy'', ''m'', 3
SELECT ''Stachu'', ''Waleczny'', ''m'', 2
SELECT ''Danuta'', ''Nuta'', ''k'', 4
SELECT ''Anna'', ''Kosmiczna'', ''k'', 3
SELECT ''Lech'', ''Barwny'', ''m'', 6
SELECT ''Hellena'', ''Kolorowa'', ''k'', 6
SELECT ''Jarek'', ''Specjalny'', ''m'', 5
SELECT ''Marianna'', ''Niekonieczna'', ''k'', 5
SELECT ''Ernest'', ''Samoobronny'', ''m'', 4
');
Rozwiązanie zadania to kwestia tzw. pionowych operacji na zbiorach (w odróżnieniu od poziomych - joinów). W naszym przypadku zastosujemy operator UNION. Potrzebujemy rekordów z kobietami mającymi parę, rekordów z mężczyznami mającymi parę i rekordów z pustymi wartościami w liczbie równej ilości par. Zapytanie wygląda następująco:
SELECT imie, nazwisko, plec, id_pary
FROM Osoby Os
WHERE plec = 'k' AND
EXISTS(
SELECT 1 FROM Osoby
WHERE id_pary = Os.id_pary AND
plec = 'm')
UNION
SELECT imie, nazwisko, plec, id_pary
FROM Osoby Os
WHERE plec = 'm' AND
EXISTS(
SELECT 1 FROM Osoby
WHERE id_pary = Os.id_pary AND
plec = 'k')
UNION ALL
SELECT '' as imie, '' as nazwisko, '' as plec, id_pary
FROM (
SELECT id_pary FROM Osoby
GROUP BY id_pary
HAVING Count(*) = 2
) sq;
Warto zwrócić uwagę na właściwość ALL drugiego operatora UNION. Należy pamiętać, że operator UNION usuwa wszystkie duplikaty z dołączanego zbioru rekordów. Aby dołączyć także powtarzające się wiersze, konieczne jest użycie opcji ALL. Jeżeli ALL nie zostałaby powyżej użyty, nie dołączylibyśmy rekordów z pustymi wartościami w liczbie równej ilości par, tylko w liczbie równej 1.
Zapytanie zwraca nam dokładnie te rekordy, których potrzebujemy do realizacji zadania. Nasz cel nie zostanie jednak osiągnięty, dopóki odpowiednio nie posortujemy wynikowego rezultatu. Sortowanie musi odbywać się dwubieżnie - po pierwsze trzeba posortować całe 3-elementowe podzbiory rekordów, po drugie - rekordy w ramach 3-elementowych podzbiorów (pierwszy rekord - kobieta itd.). Do pierwszego porządku potrzebna jest kolumna, która ma te same wartości w ramach 3-elementowego podzbioru, a jest unikalna w skali całego zbioru. Mamy szczęście, gdyż warunki te spełnia kolumna
id_pary. Szczęście nie jest jednak absolutne, bo w przypadku drugiego założenia musimy zastosować sztuczną kolumnę -
ordc (ordering column). W ostatecznym wyniku wyświetlimy tylko imię i nazwisko, całość logiki zawarta zostanie w podzapytaniu. Finalne zapytanie ma następującą postać:
SELECT imie, nazwisko
FROM
(
SELECT imie, nazwisko, id_pary, 1 as ordc
FROM Osoby Os
WHERE plec = 'k' AND
EXISTS(
SELECT 1 FROM Osoby
WHERE id_pary = Os.id_pary AND
plec = 'm')
UNION
SELECT imie, nazwisko, id_pary, 2 as ordc
FROM Osoby Os
WHERE plec = 'm' AND
EXISTS(
SELECT 1 FROM Osoby
WHERE id_pary = Os.id_pary AND
plec = 'k')
UNION ALL
SELECT '' as imie, '' as nazwisko, id_pary, 3 as ordc
FROM (
SELECT id_pary FROM Osoby
GROUP BY id_pary
HAVING Count(*) = 2
) sq
) subq
ORDER BY subq.id_pary, subq.ordc;
Powyższy SELECT w pełni realizuje cel zadania.