Powtórki z SQL’a – Część II – GROUP_CONCAT i HAVING
Thursday, December 27th, 2007Zaledwie wczoraj pisałem o GROUP_CONCAT() w SQL, o możliwości dodatkowej filtracji zwracanych rekordów przez HAVING aby stwierdzić, że odwoływanie się do konkretnej kolumny zamiast do wyniku funkcji agregacyjnej w HAVING (co jest możliwe w MySQL ale nie do końca zgodne ze specyfikacją SQL) nie działa tak jak powinno.
Wracając do przykładu z poprzedniego wpisu:
SELECT entries.*, channels.channel_name AS chname,
GROUP_CONCAT(channels.channel_name) AS channel_names
FROM entry_channel
INNER JOIN entries ON entry_channel.entry_id = entries.entry_id
INNER JOIN channels ON entry_channel.channel_id = channels.channel_id
GROUP BY entry_channel.entry_id
HAVING chname = 'krzaki'
ORDER BY entries.created_at DESC
LIMIT 25
Wynik:
| entry_id | body | chname | channel_names |
|---|---|---|---|
| 1 | jakiś fajny wpisik | krzaki | krzaki,swieta |
Otrzymaliśmy 1 rekord gdyż wiersz zawierający string ‘krzaki’ pochodził z ostatniego wiersza podlegającego agregacji za pomocą GROUP BY, gdyby w zapytaniu zamiast HAVING chname = ‘krzaki’ znalazło się HAVING chname = ’swieta’ otrzymalibyśmy pusty wynik (pomimo, że taka wartość występuje w odpowiedniej kolumnie jednego z agregowanych wierszy).
Czyli jak skutecznie przefiltrować wyniki odnosząc się do wartości uzyskiwanej przez GROUP_CONCAT()?
…..HAVING jest jak najbardziej przydatne (jest wykonywane jako jedna z ostatnich procedur przez zwróceniem wyniku) ale kluczowe jest użycie funkcji FIND_IN_SET()
Oto zmodyfikowane zapytanie:
SELECT entries.*,
GROUP_CONCAT(channels.channel_name) AS channel_names,
FIND_IN_SET('swieta', GROUP_CONCAT(channels.channel_name)) AS found
FROM entry_channel
INNER JOIN entries ON entry_channel.entry_id = entries.entry_id
INNER JOIN channels ON entry_channel.channel_id = channels.channel_id
GROUP BY entry_channel.entry_id
HAVING found != 0
ORDER BY entries.created_at DESC
LIMIT 25
Zmiany:
usunąłem: “channels.channel_name AS chname” – nie ma żadnego sensu – jako kryterium dla HAVING – jeśli agregacji podlega więcej niż 1 wiersz
dodałem: “FIND_IN_SET(’szukana_wartość_w_agregowanej_kolumnie’, GROUP_CONCAT(agregowana_kolumna)) AS found”
oraz
“HAVING found != 0″
FIND_IN_SET() zwraca 0 jeśli szukana fraza nie znajduje się w zestawie.