як виключити нульові значення в 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.