Сделать столбцы со значениями 1-4 на группу - PullRequest
0 голосов
/ 15 мая 2019

У меня есть база данных postgres 9.6, содержащая таблицу с лицами и национальностями, которая выглядит следующим образом:

  person_id   nationality  
 ----------- ------------- 
          1   American     
          2   British      
          3   Canadian     
          3   Dutch        
          3   Ethiopian    
          3   French       
          3   German       

Я создаю таблицу для целей анализа, которая содержит по одной строке на человека.Я хочу добавить четыре столбца для первых четырех национальностей на человека.Это мой ожидаемый результат:

  person_id    nat_a     nat_b     nat_c     nat_d   
 ----------- ---------- ------- ----------- -------- 
          1   American                               
          2   British                                
          3   Canadian   Dutch   Ethiopian   French  

Пятого гражданства человека 3 (немца) не видно из-за того, что он пятый.Национальности от B до D лиц 1 и 2: NULL s.

В настоящее время я создаю эту таблицу следующим образом:

SELECT DISTINCT
    person_id,
    nth_value(nationality, 1) OVER w AS nat_a,
    nth_value(nationality, 2) OVER w AS nat_b,
    nth_value(nationality, 3) OVER w AS nat_c,
    nth_value(nationality, 4) OVER w AS nat_d
FROM nationalities
WINDOW w AS (PARTITION BY person_id ORDER BY nationality ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)

Этот запрос дает ожидаемый результат.Однако я не совсем доволен его подходом.Поскольку nth_value является оконной функцией, мне нужно указать окно, а затем применить операцию DISTINCT.Я бы предпочел использовать GROUP BY или что-то подобное.

Есть ли более эффективный способ сделать это?

Ответы [ 2 ]

0 голосов
/ 15 мая 2019

Если вы не хотите использовать функции WINDOW, вы можете использовать LATERAL подзапросы Postgres :

SELECT DISTINCT person_id, a.nat_a, b.nat_b, c.nat_c, d.nat_d
FROM nationalities
    -- --------------------------------------------------------
    -- A
    INNER JOIN LATERAL
    (
        SELECT person_id, MIN(nationality)
        FROM nationalities
        GROUP BY person_id
    ) AS a(person, nat_a) ON a.person = nationalities.person_id
    -- --------------------------------------------------------
    -- B
    LEFT JOIN LATERAL
    (
        SELECT person_id, MIN(nationality)
        FROM nationalities
        WHERE nationality > a.nat_a
        GROUP BY person_id
    ) AS b(person, nat_b) ON b.person = nationalities.person_id
    -- --------------------------------------------------------
    -- C
    LEFT JOIN LATERAL
    (
        SELECT person_id, MIN(nationality)
        FROM nationalities
        WHERE nationality > b.nat_b
        GROUP BY person_id
    ) AS c(person, nat_c) ON b.person = nationalities.person_id
    -- --------------------------------------------------------
    -- D
    LEFT JOIN LATERAL
    (
        SELECT person_id, MIN(nationality)
        FROM nationalities
        WHERE nationality > c.nat_c
        GROUP BY person_id
    ) AS d(person, nat_d) ON d.person = nationalities.person_id

Поскольку вы сортируете по алфавиту, nat_aвсегда будет MIN(nationality).Последовательные боковые объединения (использующие LEFT JOIN, для людей, которые имеют только 1 гражданство) могут смотреть на «следующий MIN» национальности.

0 голосов
/ 15 мая 2019

Вы написали, что хотите избежать использования оконной функции. Тем не менее, этот ответ использует функцию row_number, но не нуждается в DISTINCT. Может быть, это поможет.

Как написано в комментарии: вы хотите создать сводную таблицу. Но вам нужно создать критерий, который сообщает запросу, какой элемент попадает в первый, второй, ... новый столбец. Например, номер строки в каждой группе. Это можно просто сделать с помощью оконной функции.

демо: дБ <> скрипка

WITH ordered AS (
    SELECT 
        *,
        row_number() OVER (PARTITION BY person_id ORDER BY nationality)
    FROM
        nationalities
)
SELECT
    person_id,
    MAX(nationality) FILTER (WHERE row_number = 1) AS nat_a,
    MAX(nationality) FILTER (WHERE row_number = 2) AS nat_b,
    MAX(nationality) FILTER (WHERE row_number = 3) AS nat_c,
    MAX(nationality) FILTER (WHERE row_number = 4) AS nat_d
FROM
    ordered
GROUP BY person_id
ORDER BY person_id

Решение без оконной функции:

демо: дб <> скрипка

WITH ordered AS (
    SELECT 
        *
    FROM (
        SELECT 
            person_id,
            array_agg(nationality ORDER BY nationality) AS a
        FROM
            nationalities
        GROUP BY person_id
    ) s,
    unnest(a) WITH ORDINALITY AS a(nationality, ordinality)
)
SELECT
    person_id,
    MAX(nationality) FILTER (WHERE ordinality = 1) AS nat_a,
    MAX(nationality) FILTER (WHERE ordinality = 2) AS nat_b,
    MAX(nationality) FILTER (WHERE ordinality = 3) AS nat_c,
    MAX(nationality) FILTER (WHERE ordinality = 4) AS nat_d
FROM
    ordered
GROUP BY person_id
ORDER BY person_id

Этот запрос объединяет все национальности по идентификатору и выводит его с порядковым номером. Это также генерирует номер строки.

Но эта версия намного медленнее: demo: db <> fiddle На самом деле ваша версия кажется самой быстрой в этом случае.

...