як виключити нульові значення в array_agg, як у string_agg, використовуючи postgres?


96

Якщо я використовую array_aggдля збору імен, я отримую свої імена, розділені комами, але якщо є nullзначення, це значення також береться як ім'я в сукупності. Наприклад :

SELECT g.id,
       array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
       array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
FROM groups g
GROUP BY g.id;

він повертається ,Larry,Philзамість просто Larry,Phil(у моєму 9.1.2 це показано NULL,Larry,Phil). як у цій скрипці

Натомість, якщо я використовую string_agg(), він показує мені лише імена (без порожніх коми або нулі), як тут

Проблема в тому, що я Postgres 8.4встановив на сервері і string_agg()не працює там. Чи є спосіб зробити array_agg подібним до string_agg ()?


Дивіться цей потік списку розсилки PostgreSQL по цій темі: postgresql.1045698.n5.nabble.com/…
Крейг Рінгер,

Мені шкода, я не думаю, що в цій темі є рішення ...
Дауд,

У цій темі є два рішення. Один полягає у створенні функції, а інший (що пропонується не показано) - це той, на який я відповів.
Clodoaldo Neto

@Clodoaldo - усі рядки матимуть канонічне в ('y', 'n') ... тому речення where здається зайвим. Проблема полягає в тому, що всередині групування, якщо значення канонічного поля дорівнює "Y", і ми збираємо "N", тоді буде зібрано і нуль.
Дауд,

Гаразд. Тепер я зрозумів. Перевірте відповідь на оновлення.
Clodoaldo Neto

Відповіді:


28

Скрипка SQL

select
    id,
    (select array_agg(a) from unnest(canonical_users) a where a is not null) canonical_users,
    (select array_agg(a) from unnest(non_canonical_users) a where a is not null) non_canonical_users
from (
    SELECT g.id,
           array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
           array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
    FROM groups g
    GROUP BY g.id
) s

Або простіше і може бути дешевшим, використовуючи array_to_stringякий виключає нульові значення:

SELECT
    g.id,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END)
        , ','
    ) canonical_users,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END)
        , ','
    ) non_canonical_users
FROM groups g
GROUP BY g.id

Скрипка SQL


Дякую. Але якщо основний (-і) запит (-и) повертає 1000 рядків, тоді 2 підзапити (з використанням unnest) запускатимуться один раз для кожного рядка .. Чи буде краще терпіти NULL, ніж виконувати 2000 додаткових запитів відбору?
Дауд,

@Daud Нова версія, яка може бути дешевшою. Візьміть пояснювальні результати обох, щоб бути впевненими.
Clodoaldo Neto

3
@Clodoaldo Якщо ви використовуєте, array_to_string(array_agg(...))ви можете також використовувати string_agg.
Крейг Рінгер,

1
@Craig Проблема у питанні 8.4
Clodoaldo Neto

@Clodoaldo Gah, старі версії. Дякую.
Крейг Рінгер,

248

За допомогою postgresql-9.3 це можна зробити;

SELECT g.id,
   array_remove(array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END), NULL) canonical_users,
   array_remove(array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END), NULL) non_canonical_users
FROM groups g 
GROUP BY g.id;

Оновлення : з postgresql-9.4;

SELECT g.id,
   array_agg(g.users) FILTER (WHERE g.canonical = 'Y') canonical_users,
   array_agg(g.users) FILTER (WHERE g.canonical = 'N') non_canonical_users
FROM groups g 
GROUP BY g.id;

5
Це працює, швидко та елегантно, це вирішило мені проблему, подібну до OP. Причина оновлення до 9,3 для тих, хто цього ще не робив. +1
Павло В.

12
9.4 ще вишуканіший. Працює як шарм
jmgarnier

2
Варіант 9.4 ще кращий, бо мені потрібно відфільтрувати нуль.
Коладикт

Спочатку я використав оновлену версію, але потім зрозумів, що мені потрібно видалити Nulls та дублікати, тому повернувся до першої пропозиції. Це великий запит, але це створення матеріалізованого подання, тож не велика проблема.
Попередній випуск

12

При вирішенні загального питання про видалення нульових значень із агрегатів масивів існує два основних способи атаки на проблему: або виконання array_agg (unnest (array_agg (x))), або створення власного агрегату.

Перший має вигляд, показаний вище :

SELECT 
    array_agg(u) 
FROM (
    SELECT 
        unnest(
            array_agg(v)
        ) as u 
    FROM 
        x
    ) un
WHERE 
    u IS NOT NULL;

Секунда:

/*
With reference to
http://ejrh.wordpress.com/2011/09/27/denormalisation-aggregate-function-for-postgresql/
*/
CREATE OR REPLACE FUNCTION fn_array_agg_notnull (
    a anyarray
    , b anyelement
) RETURNS ANYARRAY
AS $$
BEGIN

    IF b IS NOT NULL THEN
        a := array_append(a, b);
    END IF;

    RETURN a;

END;
$$ IMMUTABLE LANGUAGE 'plpgsql';

CREATE AGGREGATE array_agg_notnull(ANYELEMENT) (
    SFUNC = fn_array_agg_notnull,
    STYPE = ANYARRAY,
    INITCOND = '{}'
);

Виклик другого (природно) виглядає трохи приємніше, ніж першого:

виберіть array_agg_notnull (v) з x;


9

Я додаю це, хоча цей потік досить старий, але я зіткнувся з цим акуратним трюком, який досить добре працює на невеликих масивах. Він працює на Postgres 8.4+ без додаткових бібліотек або функцій.

string_to_array(array_to_string(array_agg(my_column)))::int[]

array_to_string()Метод фактично позбавляється від нулів.


9

Якщо ви шукаєте сучасну відповідь на загальне питання про те, як видалити NULL з масиву , це:

array_remove(your_array, NULL)

Мені було цікаво про продуктивність і я хотів порівняти це з найкращою можливою альтернативою:

CREATE OR REPLACE FUNCTION strip_nulls(
    IN array_in ANYARRAY
)
RETURNS anyarray AS
'
SELECT
    array_agg(a)
FROM unnest(array_in) a
WHERE
    a IS NOT NULL
;
'
LANGUAGE sql
;

Виконання pgbench-тесту довело (з високою впевненістю), що array_remove () є трохи більше ніж удвічі швидшим . Я провів тест на числа подвійної точності з різними розмірами масивів (10, 100 та 1000 елементів) та випадковими NULL між ними.


@VivekSinha, яку версію postgres ви використовуєте? Я щойно перевірив ваш запит, і він вивів для мене "{1,2,3}". Я використовую 12.1.
Алексі Теодор

Ах, я бачу @ alexi-theodore, що відбувається в моєму кінці. Я використовував спеціальний + модифікований драйвер postgres. Коли я запитую безпосередньо в консолі, я бачу правильний результат! Вибачте за плутанину. Видалений попередній коментар та відповідь "за"
Vivek Sinha

3

Як було запропоновано в коментарях, ви можете написати функцію для заміни нулів у масиві, однак, як також зазначено у потоці, на який посилаються в коментарях, цей вид перешкоджає ефективності функції агрегату, якщо вам потрібно створити агрегат , розділіть його, а потім знову агрегуйте.

Я думаю, що збереження нулів у масиві - це лише (можливо, небажана) особливість Array_Agg. Ви можете використовувати підзапити, щоб уникнути цього:

SELECT  COALESCE(y.ID, n.ID) ID,
        y.Users,
        n.Users
FROM    (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'Y'
            GROUP BY g.ID
        ) y
        FULL JOIN 
        (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'N'
            GROUP BY g.ID
        ) n
            ON n.ID = y.ID

SQL FIDDLE


Дякую. Але мені потрібен був "case", щоб обробляти рядки в межах даної групи, і підзапити там були б неефективними
Дауд,

0

Це дуже просто, просто насамперед створіть новий - (мінус) оператор для тексту [] :

CREATE OR REPLACE FUNCTION diff_elements_text
    (
        text[], text[] 
    )
RETURNS text[] as 
$$
    SELECT array_agg(DISTINCT new_arr.elem)
    FROM
        unnest($1) as new_arr(elem)
        LEFT OUTER JOIN
        unnest($2) as old_arr(elem)
        ON new_arr.elem = old_arr.elem
    WHERE old_arr.elem IS NULL
$$ LANGUAGE SQL IMMUTABLE;

CREATE OPERATOR - (
    PROCEDURE = diff_elements_text,
    leftarg = text[],
    rightarg = text[]
);

І просто відніміть масив [null]:

select 
    array_agg(x)-array['']
from
    (   select 'Y' x union all
        select null union all
        select 'N' union all
        select '' 
    ) x;

Це все:

{Y, N}


1
array_agg(x) FILTER (WHERE x is not null)здається набагато простішим: dbfiddle.uk/… і вам насправді не потрібна власна функція, ви можете просто використовувати array_remove() dbfiddle.uk/…
a_horse_with_no_name

-6

Більше питання полягає в тому, навіщо витягувати всі комбінації користувачів / груп одночасно. Гарантовано ваш інтерфейс не може обробляти всі ці дані. Додавання підкачки до великих даних - теж погана ідея. Запропонуйте своїм користувачам відфільтрувати набір, перш ніж вони побачать дані. Переконайтеся, що ваш набір опцій ПРИЄДНАТИ є у списку, щоб вони могли фільтрувати ефективність, якщо захочуть. Іноді 2 запити роблять користувачів щасливішими, якщо вони обидва швидко.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.