Сгруппировать вместе со столбцами вне пункта - PullRequest
5 голосов
/ 29 марта 2019

У меня есть таблица SQL следующим образом

    id    |date_accessed     
----------+------------
     1    | 16/10/2014
     1    | 28/10/2014
     1    | 25/11/2014
     1    | 16/12/2014
     2    | 30/09/2014
     2    | 03/10/2014
     2    | 17/10/2014
     2    | 03/01/2015

Мне нужно сгруппировать данные по месяцу и году, но я также хочу знать, сколько месяцев прошло с тех пор, как пользователь впервые получил доступ к системе

    id    |   month    |   year     |   length_in_month
----------+------------+------------+-------------------
     1    |    10      |   2014     |          1
     1    |    11      |   2014     |          2     
     1    |    12      |   2014     |          3
     2    |    09      |   2014     |          1
     2    |    10      |   2014     |          2
     2    |    01      |   2015     |          5

Мой запрос выглядит следующим образом

select 
    id, 
    Extract(MONTH from "date_accessed") as month, 
    Extract(year from "date_accessed") as year 
from 
    exampleTable 
group by 
    1, 2, 3 
order by 
    1, 3, 2 

Но у меня нет доступа к min(date_accessed), когда я делаю группу по, чтобы получить длину столбца length_in_month.

Есть ли решение для этого?

Ответы [ 7 ]

2 голосов
/ 29 марта 2019

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

first_access рассчитывается отдельно в CTE, поскольку это одно значение для каждого идентификатора, а не для идентификатора, месяца, года.

with m AS
(
select id, min(date_accessed)
                    as first_access from t
group by id
)
select t.id, Extract(MONTH from "date_accessed") as month, 
             Extract(year from  "date_accessed") as year,
            EXTRACT ( month from 
                      MIN( AGE( date_trunc('month', date_accessed) 
                                + interval '1 month - 1 day',  --last day of month
                             date_trunc('month', first_access) --first day of month
                         ))
                    ) + 1 as length_in_month
from t join m on t.id = m.id 
group by t.id,month,year 
order by 1,3,2;

DEMO

1 голос
/ 29 марта 2019

Используйте подзапрос, как показано ниже:

SELECT 
    exampleTable.id, 
    EXTRACT(month FROM "date_accessed") AS month, 
    EXTRACT(year FROM "date_accessed") AS year,
    /* Calculate # months since the user accessed the system for the 1st time */
    (EXTRACT(year from "date_accessed") - EXTRACT(year from firstTimeAccessDatesTable.firstAccessDate)) * 12
    + (EXTRACT(month from "date_accessed") - EXTRACT(month from firstTimeAccessDatesTable.firstAccessDate)) + 1 AS length_in_month
FROM 
    /* Join exampleTable with firstTimeAccessDatesTable by id */
    exampleTable
INNER JOIN(
    /* Perform subquery to obtain the date a given user accessed the system for the first time */
    SELECT
        id,
        MIN("date_accessed") AS firstAccessDate
    FROM
        exampleTable
    GROUP BY
        1
    ) AS firstTimeAccessDatesTable
ON exampleTable.id = firstTimeAccessDatesTable.id
GROUP BY
    1, 2, 3, 4
ORDER BY
    1, 3, 2
0 голосов
/ 29 марта 2019

Кратчайший запрос, если Postgres имеет встроенный DATEDIFF.

Запрос можно сделать более идиоматичным, используя DISTINCT ON.

DISTINCT ON относится только к Postgres. Он отбрасывает дублирующиеся строки и сохраняет только одну, а также сортирует строки на основе переданных ему параметров.

-- http://www.sqlines.com/postgresql/how-to/datediff
create or replace function month_diff (start_month date, end_month date) 
returns int as $$
begin
    return (date_part('year', end_month) - date_part('year', start_month))*12 +
            date_part('month', end_month) - date_part('month', start_month);
end;
$$ language 'plpgsql' immutable;


select
    distinct on (id, date_trunc('month', date_accessed))

    id, 
    date_part('year', date_accessed) as year,
    date_part('month', date_accessed) as month,

    month_diff( min(date_accessed) over(partition by id), date_accessed ) + 1 
        as length_in_month
from t;

Выход:

enter image description here

0 голосов
/ 29 марта 2019

Другой подход

Тест в реальном времени: http://sqlfiddle.com/#!17/7c833/2

-- drop table t;

/*
create table t as
select id, date_accessed::date
from (values
     (1, '2014-10-16'),
     (1,  '2014-10-28'),
     (1,  '2014-11-25'),
     (1,  '2014-12-16'),
     (2,  '2014-09-30'),
     (2, '2014-10-03'),
     (2, '2014-10-17'),
     (2, '2015-01-03')
) as x(id, date_accessed)
*/

with unique_months as
(
    select 
        id, 
        extract(year from date_accessed) "year",
        extract(month from date_accessed) "month",
        min(date_accessed) as month_representative
    from t 
    group by id, year, month

)
, compute_length as
(                   
    select 
        id, year, month,

        ( 
            ( 
                extract(year from month_representative) - extract(year from min(month_representative) over(partition by id)) 
            ) * 12 
        )
        +
        ( 
            extract(month from month_representative) - extract(month from min(month_representative) over(partition by id))
        )
        +
        1 as length_in_month


    from unique_months
)
select * 
from compute_length
order by id, year, month

Результаты:

| id | year | month | length_in_month |
|----|------|-------|-----------------|
|  1 | 2014 |    10 |               1 |
|  1 | 2014 |    11 |               2 |
|  1 | 2014 |    12 |               3 |
|  2 | 2014 |     9 |               1 |
|  2 | 2014 |    10 |               2 |
|  2 | 2015 |     1 |               5 |
0 голосов
/ 29 марта 2019

Используйте следующее

Тест в реальном времени: http://sqlfiddle.com/#!17/7c833/6

-- drop table t;

/*
create table t as
select id, date_accessed::date
from (values
     (1, '2014-10-16'),
     (1,  '2014-10-28'),
     (1,  '2014-11-25'),
     (1,  '2014-12-16'),
     (2,  '2014-09-30'),
     (2, '2014-10-03'),
     (2, '2014-10-17'),
     (2, '2015-01-03')
) as x(id, date_accessed)
*/

with unique_months as
(
    select 
        id,

        date_trunc('month', date_accessed) as monthify
    from t 
    group by id, monthify

)
, compute_length as
(                   
    select 
        id, monthify,

        ( 
            ( 
                extract(year from monthify) - extract(year from min(monthify) over(partition by id)) 
            ) * 12 
        )
        +
        ( 
            extract(month from monthify) - extract(month from min(monthify) over(partition by id))
        )
        +
        1 as length_in_month


    from unique_months
)
select id, 
  extract(year from monthify) "year", 
  extract(month from monthify) "month",
  length_in_month
from compute_length
order by id, monthify

Результаты:

| id | year | month | length_in_month |
|----|------|-------|-----------------|
|  1 | 2014 |    10 |               1 |
|  1 | 2014 |    11 |               2 |
|  1 | 2014 |    12 |               3 |
|  2 | 2014 |     9 |               1 |
|  2 | 2014 |    10 |               2 |
|  2 | 2015 |     1 |               5 |
0 голосов
/ 29 марта 2019

Приведенный ниже запрос дает точную продолжительность в месяцах.Учитывая вышеприведенный пример ввода, запрос даст вам продолжительность length_in_months как 0, если разница во времени составляет менее 30 дней.Умножение на -1 служит для преобразования отрицательной длительности, отображаемой в виде положительных значений.

create table Test(id integer, date_accessed date);
insert into Test values(1, "2014-10-16");
insert into Test values(1, "2014-10-28");
insert into Test values(1, "2014-11-25");
insert into Test values(1, "2014-12-16");
insert into Test values(2, "2014-09-30");
insert into Test values(2, "2014-10-03");
insert into Test values(2, "2014-10-17");
insert into Test values(2, "2015-10-16");


select a.id, a.month, a.year, a.date_accessed, (timestampdiff(MONTH, 
a.date_accessed, a.min_date)) * -1 as length_in_month from (
select id, EXTRACT(MONTH FROM date_accessed) as MONTH, EXTRACT(YEAR FROM 
date_accessed) as YEAR, date_accessed, (select MIN(date_accessed) from Test) as 
min_date from Test order by date_accessed) a order by a.id asc;

Output
1   10  2014    2014-10-16  0
1   10  2014    2014-10-28  0
1   11  2014    2014-11-25  1
1   12  2014    2014-12-16  2
2   9   2014    2014-09-30  0
2   10  2014    2014-10-03  0
2   10  2014    2014-10-17  0
2   10  2015    2015-10-16  12
0 голосов
/ 29 марта 2019

Я думаю, что вам нужно сначала выбрать Id и min (месяц) группу по Id, поэтому вы получите первую дату для каждого Id.Затем выберите другой, как тот, который вы сделали, плюс вариант, который я предлагаю выше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...