PostgreSQL - объединение нескольких строк с несколькими атрибутами в одну строку? - PullRequest
0 голосов
/ 12 сентября 2018

У меня есть такая таблица:

DATE        ID    ScoreA    ScoreB    ScoreC
20180101    001   91        92        25
20180101    002   81        82        35
20180101    003   71        52        45
20180102    001   82        15        66
20180102    002   69        67        77
...
20180131    003   88        65        73

Возьмем, например, данные за месяц, я хочу объединить их в отчет о показателях MAX и MIN только с одной строкой для каждого ID. Прямо как:

ID    ScoreA       Date_A              ScoreB        Date_B            ...
001   MAX(ScoreA)  MAX(ScoreA).DATE    MAX(ScoreB)   MAX(ScoreB).DATE  ...
002   MAX(ScoreA)  MAX(ScoreA).DATE    MAX(ScoreB)   MAX(ScoreB).DATE  ...
003   MAX(ScoreA)  MAX(ScoreA).DATE    MAX(ScoreB)   MAX(ScoreB).DATE  ...

Где MAX(ScoreA).DATE означает DATE, когда появилась соответствующая максимальная или минимальная оценка (если максимальная оценка появлялась за несколько дат, просто выберите один случайным образом)

Не похоже на обычные случаи с объединенными строками, он включает несколько столбцов одновременно. И так как будет МНОГИЕ ID с и СТО из Score с (я имею в виду ScroeA ScroreB ... ScoreZ ... Score1 Score2 ... Score100 ...), я надеюсь избежать использования операций, таких как таблица JOIN. Так какие-нибудь хорошие идеи?

Ответы [ 3 ]

0 голосов
/ 12 сентября 2018

Попробуйте это

with max_score as
(
    Select    distinct id
        , max(ScoreA) over( partition by id ) as max_ScoreA
        , max(ScoreB) over( partition by id ) as max_ScoreB
        , max(ScoreC) over( partition by id ) as max_Scorec
    from TABLE_NAME
)
Select 
    cte.id
    , max_ScoreA, tbl_a.DATE
    , max_ScoreB, tbl_b.DATE
    , max_ScoreC, tbl_c.DATE
from 
max_score cte
join TABLE_NAME tbl_a
on cte.id = tbl_a.id
and cte.max_ScoreA = tbl_a.ScoreA
join TABLE_NAME tbl_b
on cte.id = tbl_b.id
and cte.max_ScoreB = tbl_b.ScoreB
join TABLE_NAME tbl_c
on cte.id = tbl_c.id
and cte.max_ScoreC = tbl_c.ScoreC
order by 1
0 голосов
/ 12 сентября 2018

Это еще один пример кода, который предоставит вам все необходимые данные:

select *
from (select
        distinct on (id) id,
        first_value(scorea) over w as a_min,
        last_value(scorea) over w as a_max,
        first_value(date) over w as a_min_d,
        last_value(date) over w as a_max_d
    from the_table
    window w as (partition by id order by scorea)
    order by 1,3 desc) a
join (select
        distinct on (id) id,
        first_value(scoreb) over w as b_min,
        last_value(scoreb) over w as b_max,
        first_value(date) over w as b_min_d,
        last_value(date) over w as b_max_d
    from the_table
    window w as (partition by id order by scoreb)
    order by 1,3 desc) b using(id)
join (select
        distinct on (id) id,
        first_value(scorec) over w as c_min,
        last_value(scorec) over w as c_max,
        first_value(date) over w as c_min_d,
        last_value(date) over w as c_max_d
    from the_table
    window w as (partition by id order by scorec)
    order by 1,3 desc) c using(id)

Обратите внимание, что есть 3 отдельных подзапроса, по одному для каждого столбца оценки.Здесь происходит некоторая магия , касающаяся оконных функций и разделов , о которых было бы полезно прочитать.Одна сложная часть здесь заключается в том, что различные разделы будут мешать друг другу, если будут помещены в один и тот же запрос (по крайней мере, на моей странице 9.3.22).

0 голосов
/ 12 сентября 2018

Если вы хотите избежать объединений, я бы предложил такую ​​конструкцию

WITH cte AS (
    SELECT DATE, ID, ScoreA, ScoreB, ScoreC,
        row_number() over (partition by ID order by ScoreA desc) rnA,
        row_number() over (partition by ID order by ScoreB desc) rnB,
        row_number() over (partition by ID order by ScoreC desc) rnC,
    FROM ...
    WHERE DATE BETWEEN ... AND ...
), ids AS (
    SELECT DISTINCT ID FROM cte
)
SELECT ID, 
    (SELECT ScoreA FROM cte t2 WHERE t2.ID = t.ID AND rnA = 1) ScoreA, 
    (SELECT DATE FROM cte t2 WHERE t2.ID = t.ID AND rnA = 1) Date_A,
    (SELECT ScoreB FROM cte t2 WHERE t2.ID = t.ID AND rnB = 1) ScoreB, 
    (SELECT DATE FROM cte t2 WHERE t2.ID = t.ID AND rnB = 1) Date_B,
    (SELECT ScoreC FROM cte t2 WHERE t2.ID = t.ID AND rnC = 1) ScoreC, 
    (SELECT DATE FROM cte t2 WHERE t2.ID = t.ID AND rnC = 1) Date_C
FROM ids t

Когда вам нужна дата или какой-либо другой атрибут значения max / min, разумно использовать нумерацию строк вместо агрегирующих функций:row_number() over (...) as rn с последующим условием rn = 1

UPD

Как только @TaurusDang захочет создать код, я могу разрешить postgres выполнять почти всеработа:

WITH cols AS
(
    SELECT column_name
    FROM information_schema.columns
    WHERE table_schema = 'your_schema'
      AND table_name   = 'your_table'
      AND column_name like 'Score%'
)
-- first part: rows for cte subquery
SELECT ',row_number() over (partition by ID order by ' || column_name || ' desc) rn' || column_name
FROM cols
UNION ALL
-- second part: rows for final query
SELECT ',(SELECT ' || column_name || ' FROM cte t2 WHERE t2.ID = t.ID AND rn' || column_name || ' = 1) ' || column_name || ', (SELECT DATE FROM cte t2 WHERE t2.ID = t.ID AND rn' || column_name || ' = 1) Date_' || column_name
FROM cols

Просто скопируйте сгенерированные строки в начальный запрос: первую половину cte, а вторую - основной запрос

...