Postgres возвращает [null] вместо [] для массива agg таблицы соединений

Я выбираю некоторые объекты и их теги в Postgres. Схема довольно простая, три таблицы:

объекты id

метки id | object_id | tag_id

теги id | tag

Я присоединяюсь к таблицам, как это, используя array_agg чтобы объединить теги в одно поле:

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

однако, если объект не имеет тегов, Postgres возвращает это:

[ null ]

вместо an пустой массив. как я могу вернуть пустой массив, когда нет теги? я дважды проверил, что у меня нет пустого тега возвращается.

The aggregate docs скажем: "функция coalesce может использоваться для замены нуля или пустого массива на null, когда это необходимо". Я пытался!--6--> но он все равно возвращает массив с null. Я попытался сделать второй параметр многочисленными вещами (например,COALESCE(ARRAY_AGG(tags.tag), ARRAY()), но все они приводят к ошибкам синтаксиса.

5 ответов


другой вариант может быть array_remove(..., NULL) (введен в 9.3) если tags.tag и NOT NULL (в противном случае вы можете сохранить NULL значения в массиве, но в этом случае, вы не можете отличить один существующий NULL тег и NULL тег из-за 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

если теги не найдены, возвращается пустой массив.


в документах говорится, что при агрегировании нулевых строк вы получаете нулевое значение и примечание об использовании COALESCE обращается к этому конкретному случаю.

это не относится к вашему запросу, так как LEFT JOIN ведет себя - когда он находит ноль соответствующие строки, он возвращает один строка, заполненная нулями (а совокупность одной нулевой строки-массив с одним нулевым элементом).

у вас может возникнуть соблазн слепо заменить [NULL] с [] в выходных данных, но тогда вы потеряете возможность distiguish между объекты без тегов и помеченные объекты, где tags.tag равно null. Логика приложения и / или ограничения целостности могут не разрешить этот второй случай, но это еще одна причина не подавлять нулевой тег, если ему удается проникнуть.

вы можете идентифицировать объект без тегов (или вообще, скажите, когда LEFT JOIN не найдено совпадений), проверив, включено ли поле другая сторона условия соединения-null. Поэтому в вашем случае просто замените

array_agg(tags.tag)

С

CASE
  WHEN taggings.object_id IS NULL
  THEN ARRAY[]::text[]
  ELSE array_agg(tags.tag)
END

начиная с 9.4 можно ограничить вызов агрегатной функции только строками, которые соответствуют определенному критерию:array_agg(tags.tag) filter (where tags.tag is not null)


в документации говорится, что массив, содержащий NULL возвращается. Если вы хотите преобразовать это в пустой массив, вам нужно сделать небольшую магию:

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;

это предполагает, что теги имеют значение text тип (или любой из его вариантов); измените приведение по мере необходимости.

хитрость здесь заключается в том, что первый (и единственный) элемент [NULL] массив имеет длину 0, поэтому если какие-либо данные возвращается из tags вы возвращаете агрегат, в противном случае строите пустой массив нужного типа.

кстати, заявление в документации об использовании coalesce() немного вшиво: что означает, что если вы не хотите NULL в результате, вы можете использовать coalesce() чтобы превратить это в 0 или какой-либо другой выход по вашему выбору. Но вам нужно применить это к элементы массива вместо массива, который, в вашем случае, не решит проблемы.


я узнал, что это он:

COALESCE(ARRAY_AGG(tags.tag), ARRAY[]::TEXT[])

...предполагая, что tags.tag - Это тип текста.

не уверен, что, возможно, это не будет работать в старых версиях Postgres, но я использую его в ver. 9.6 и он, кажется, работает и менее громоздким, чем CASE WHEN x IS NULL... GROUP BY... решение, предложенное ранее.