分组以及该子句外部的列

问题描述 投票:5回答:7

我有一个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列的长度。

这有解决方案吗?

sql postgresql group-by
7个回答
2
投票

我已经使用AGE函数来确定第一次访问的月份的开始日期和访问的实际日期的结束日期之间的差异,给出一个可以被公平地视为一个月的间隔,然后如上所述将其加1。这给出了预期的结果。

first_access在CTE中单独计算,因为它是每个id的单个值,而不是每个id,month,year。

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
投票

使用子查询,如下所示:

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
投票

我认为您需要先按Id选择Id和min(月)组,这样您才能获得每个Id的第一个日期。然后另一个选择像你做的那个加上我建议的选择。


0
投票

下面的查询为您提供了几个月的确切持续时间。如果时间差小于30天,请记住上面的示例输入,将使length_in_months持续时间为0。乘以-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
投票

另一种方法

现场测试: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
投票

使用以下内容

现场测试: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
投票

如果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

© www.soinside.com 2019 - 2024. All rights reserved.