GROUP_CONCAT

napísal , 22 Sep 2010 [ SQL ]

Jedným z dosť užitočných MySQL príkazov, ktorý nie je až taký známy, ako by sa na užitočnosť takého kalibru patrilo :), je GROUP_CONCAT. Používa sa ako agregujúca funkcia (s GROUP BY), podobne ako napríklad funkcia AVG, akurát že nerobí priemer zo všetkých prvkov stĺpca, ale ich pospája za sebou do jedného stringu.

Ako to funguje a načo je to dobré?

First things first, takáto je syntax:

GROUP_CONCAT(tabuľka.stĺpec SEPARATOR 'oddeľovač prvkov' ORDER BY tabuľka.stĺpec)

Prvý parameter určuje stĺpec, ktorého údaje chceme spájať, ďalej, za kľúčovým slovom SEPARATOR, nasleduje string, ktorý bude oddeľovať prvky a nakoniec stĺpec, podľa ktorého majú byť spájané prvky triedené. Povinný je iba prvý parameter, ak vynecháte oddeľovač použije sa čiarka ( ',' ) a ak vynecháte tretí parameter, prvky nebudú utriedené (duh:).

Zoberme si príklad, že máme klasický jednoduchý blog s článkami, každý článok má nejaké tie tagy a my chceme vytiahnuť posledných 10 článkov aj s nimi. Toto by sa dalo riešiť rôznymi spôsobmi - najneefektívnejšie by bolo najskôr načítať články (SELECT * FROM posts ORDER BY timestamp DESC LIMIT 10) a potom ku každému zvlášť načítať tagy (SELECT * FROM tags WHERE post_id = [číslo článku]), čo by znamenalo, že spravíme 11 queries, čo by znamenalo, že to je dosť neefektívne :) Mimochodom tento spôsob spomínam hlavne preto, že takto to má riešené CMS Drupal.

Lepšie riešenie by bolo načítať všetky články (SELECT * FROM posts ORDER BY timestamp DESC LIMIT 10), potom k nim naraz načítať všetky tagy (SELECT t.* FROM posts p JOIN tags t ON (p.post_id = t.post_id) ORDER BY p.timestamp DESC LIMIT 10) a spojiť tagy s článkami v PHP (prípadne vašom obľúbenom jazyku:p).

To pravé ultimátne riešenie :) ale prichádza až s použitím GROUP_CONCAT a vystačí si s jedným query. Načítame články, spojíme ich s tagmi a keď budeme grupovať podľa idčka článku, tagy sa pospájajú:

SELECT p.*,
GROUP_CONCAT(t.tag_id ORDER BY t.name DESC) AS tag_ids,
GROUP_CONCAT(t.name SEPARATOR '||' ORDER BY t.name DESC) AS tag_names
FROM posts p JOIN tags t ON (p.post_id = t.post_id)
GROUP BY p.post_id ORDER BY p.timestamp DESC LIMIT 10

Výstupom by boli potom údaje v nasledujúcom formáte:

posts.post_id posts.text posts.timestamp tag_ids tag_names
23 Text príspevku. Bla bla dríst. 1284227210 8,4,9 awesome||brmbrm||život, vesmír a vôbec
22 Ďalší článok n stuff. 1284221312 4,6 brmbrm||MySQL
...

Pri tagoch som dal ako oddeľovač '||' kvôli čiarkam, ktoré by sa v tagoch mohli vyskytnúť. V PHP by bolo potom spracovanie takýchto údajov veľmi jednoduché, stačí explode-núť idčka tagov a mená tagov a potom tieto 2 polia spojiť.

foreach ($posts as &$post) $post->tags = array_combine(explode(',', $post->tag_ids), explode('||', $post->tag_names));

Znížili sme teda počet queries aj množstvo kódu v PHP, it's a win-win :)

Treba si ale dávať pozor na jednu zvláštnosť - dĺžka stringu, ktorý vyrobí GROUP_CONCAT je limitovaná! Predvolené nastavenie je 512 bajtov, ktoré sa našťastie dá zmeniť aj za behu s právami obyčajného používateľa. Takže ak je šanca, že výsledný string bude dlhší, stačí predtým zavolať: SET SESSION group_concat_max_len = [počet znakov] a vec je vybavená.

Ak vám nestačilo, o niečo viac informácií nájdete samozrejme v MySQL manuále. :)

napísal , 22 Sep 2010

čo ty na to?

komentuj ku každému komentáru sa v databáze ukladá iba meno, text a dátum, iba za účelom zobrazenia pod článkom
neukladá sa email, IP adresa ani informácie o prehliadači a údaje sa nepoužívajú na reklamu, newsletter, na žiadnu ekonomickú aktivitu, nikam sa neposielajú, sú v databáze len aby sa mohli zobraziť pod článkom
ku každému komentáru sa v databáze ukladá iba meno, text a dátum, iba za účelom zobrazenia pod článkom
neukladá sa email, IP adresa ani informácie o prehliadači a údaje sa nepoužívajú na reklamu, newsletter, na žiadnu ekonomickú aktivitu, nikam sa neposielajú, sú v databáze len aby sa mohli zobraziť pod článkom