Je sélectionne des objets et leurs tags dans Postgres. Le schéma est assez simple, trois tables:
objetsid
taggingsid | object_id | tag_id
tagsid | tag
Je rejoins les tables comme ceci, en utilisant array_agg
pour regrouper les balises dans un seul champ:
SELECT objects.*,
array_agg(tags.tag) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
Cependant, si l'objet n'a pas de balises, Postgres renvoie ceci:
[ null ]
au lieu d'un tableau vide. Comment puis-je retourner un tableau vide lorsqu'il n'y a pas de balises? J'ai vérifié deux fois que je n'ai pas de balise null retournée.
Le agrégat docs dire "La fonction coalesce peut être utilisée pour substituer zéro ou un tableau vide à NULL si nécessaire". J'ai essayé COALESCE(ARRAY_AGG(tags.tag)) as tags
mais il retourne toujours un tableau avec null. J'ai essayé de créer de nombreuses choses dans le deuxième paramètre (comme COALESCE(ARRAY_AGG(tags.tag), ARRAY())
, mais elles entraînent toutes des erreurs de syntaxe.
Une autre option pourrait être array_remove(..., NULL)
( introduite dans 9.3 ) si tags.tag
est NOT NULL
(sinon, vous voudrez peut-être conserver les valeurs NULL
dans le tableau, mais dans ce cas, vous ne pouvez pas faire la distinction entre une balise NULL
existante et une variable NULL
. tag en raison du LEFT JOIN
):
SELECT objects.*,
array_remove(array_agg(tags.tag), NULL) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
Si aucune balise n'est trouvée, un tableau vide est renvoyé.
La documentation indique que, lorsque vous n’ajoutez aucune ligne, vous obtenez une valeur null et la remarque sur l’utilisation de COALESCE
aborde ce cas particulier.
Cela ne s'applique pas à votre requête, en raison de la manière dont un LEFT JOIN
se comporte - quand il trouve zéro lignes correspondantes, il retourne une ligne, remplie de valeurs null (et l'agrégat d'une ligne nulle est un tableau avec un élément nul).
Vous pourriez être tenté de remplacer aveuglément [NULL]
par []
dans la sortie, mais vous perdrez alors la possibilité de distinguer les objets sans balises et objets marqués où tags.tag
est nul . La logique de votre application et/ou les contraintes d’intégrité ne permettent peut-être pas ce second cas, mais c’est une raison de plus de ne pas supprimer une balise null si elle parvient à se faufiler.
Vous pouvez identifier un objet sans balises (ou, en général, indiquer quand un LEFT JOIN
n'a trouvé aucune correspondance) en vérifiant si le champ situé de l'autre côté de la condition de jointure est null. Donc, dans votre cas, il suffit de remplacer
array_agg(tags.tag)
avec
CASE
WHEN taggings.object_id IS NULL
THEN ARRAY[]::text[]
ELSE array_agg(tags.tag)
END
Depuis la version 9.4, il est possible de restreindre un appel de fonction d'agrégation pour ne traiter que les lignes correspondant à un certain critère: array_agg(tags.tag) filter (where tags.tag is not null)
J'ai découvert que cela va le faire:
COALESCE(ARRAY_AGG(tags.tag), ARRAY[]::TEXT[])
... en supposant que tags.tag
est un type de texte.
Je ne sais pas si cela ne fonctionnerait peut-être pas dans les anciennes versions de Postgres, mais je l’utilise en version 9.6 et il semble fonctionner et être moins encombrant que la solution CASE WHEN x IS NULL... GROUP BY...
fournie précédemment.
La documentation indique qu'un tableau contenant NULL
est renvoyé. Si vous voulez convertir cela en un tableau vide, alors vous devez faire quelques magies mineures:
SELECT objects.id,
CASE WHEN length((array_agg(tags.tag))[1]) > 0
THEN array_agg(tags.tag)
ELSE ARRAY[]::text[] END AS tags
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
GROUP BY 1;
Cela suppose que les balises sont de type text
(ou l’une de ses variantes); modifiez le casting si nécessaire.
L'astuce ici est que le premier (et unique) élément d'un tableau [NULL]
a une longueur de 0. Par conséquent, si des données sont renvoyées à partir de tags
, vous renvoyez l'agrégat, sinon créez un tableau vide du type approprié.
Incidemment, la documentation relative à l'utilisation de coalesce()
est un peu dégoûtante: si vous ne voulez pas de NULL
, vous pouvez utiliser coalesce()
pour le transformer en 0
ou en une autre sortie de votre choix. Mais vous devez appliquer cela aux éléments array au lieu du tableau, qui, dans votre cas, ne fournirait pas de solution.