【问题标题】:Using sum(case when...) over partition of woy and hh24在 woy 和 hh24 的分区上使用 sum(case when...)
【发布时间】:2018-07-22 05:36:39
【问题描述】:

使用 PostgreSQL 版本 9.4.18。

以下是针对 non_zero_year_count 和 percent_years_count_not_zero 返回意外结果的查询:

表数据:请注意,06-25 及以后的任何日期都在第 26 年的一周内。因此,在 2016 年、2017 年和 2018 年这三年中,每年都会出现 woyhh 2607。 在 sqlfiddle 测试数据库中,我刚刚完成了我正在使用的数据库的一个小样本。 2016-2018 年与下表。 http://sqlfiddle.com/#!17/f6251/1

CREATE TABLE ltg_data
("time" timestamp with time zone)
/

INSERT INTO ltg_data
("time")
VALUES
('2018-06-23 07:19:00'),
('2018-06-24 07:19:00'),
('2018-06-25 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-24 07:19:00'),
('2018-06-25 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-24 07:19:00'),
('2018-06-25 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-24 07:19:00'),
('2018-06-25 07:19:00'),
('2018-06-26 07:19:00'),
('2018-06-25 17:19:00'),
('2018-06-25 17:19:00'),
('2017-06-25 19:19:00'),
('2017-06-25 20:19:00'),
('2017-06-26 07:19:00'),
('2017-06-26 07:19:00'),
('2017-06-24 07:19:00'),
('2017-06-24 07:19:00'),
('2017-06-23 21:19:00'),
('2017-06-23 21:19:00'),
('2017-06-24 07:19:00'),
('2016-06-26 07:19:00'),
('2016-06-25 07:19:00'),
('2016-06-25 07:19:00'),
('2016-06-27 07:19:00'),
('2016-06-26 07:19:00'),
('2016-06-26 07:19:00')

所以下面的查询应该返回一些关于表数据的基本统计信息。我认为,挑战在于尝试在一年中的一周和小时内进行分区,同时以某种方式合并一年。错误数据涉及查询的一部分,该部分试图确定一年中一周和小时 (woyhh) 的计数 > 0 的年数。这是查询使用的查询和函数(用于每年标准化闰年的 woy 函数)。我使用 generate 系列是因为我想要一整年的 woyhh,即使在某个 woyhh 中没有计数。

功能:(可能不重要,但如果我遗漏了某些东西并且它们确实与问题相关)想要包括在内)

create or replace function IsLeapYear(int)
returns boolean as $$
select $1 % 4 = 0 and ($1 % 100 <> 0 or $1 % 400 = 0)
$$ LANGUAGE sql IMMUTABLE STRICT; 

create or replace function f_woyhh(timestamp with time zone)
returns int language plpgsql as $$
declare
currentYear int = extract (year from $1);
LeapYearShift int = 1 + (IsLeapYear(currentYear) and $1 > make_date  (currentYear, 2, 28))::int;
begin
return CONCAT(((extract(doy from $1)::int)- LeapYearShift) / 7+ 1, to_char   ($1, 'HH24'));
end;
$$;

查询:

WITH
CTE_Dates
AS
(
SELECT  f_woyhh(d) as dt


    ,EXTRACT(YEAR FROM d::timestamp) AS dtYear from
generate_series(timestamp '2016-01-01', timestamp '2018-12-31', interval '1 hour') as d
    -- full range of possible dates
)
,CTE_WeeklyHourlyCounts
AS
(
SELECT
f_woyhh(time) as dt
    ,time
    ,count(*) AS ct
FROM
    ltg_data
    GROUP BY ltg_data.time
)

,CTE_FullStats
AS
(
SELECT
    CTE_dates.dt as woyhh

    ,COUNT(DISTINCT CTE_Dates.dtYear)  AS years_count
    ,SUM(CASE WHEN CTE_WeeklyHourlyCounts.ct > 0 THEN 1 ELSE 0 END) OVER   (PARTITION BY CTE_Dates.dt) AS nonzero_year_count
,100.0 * SUM(CASE WHEN CTE_WeeklyHourlyCounts.ct > 0 THEN 1 ELSE 0 END)   OVER (PARTITION BY CTE_Dates.dt)
    / COUNT(DISTINCT CTE_Dates.dtYear) as percent_years_count_not_zero
FROM
    CTE_Dates
    LEFT JOIN CTE_WeeklyHourlyCounts ON CTE_WeeklyHourlyCounts.dt = CTE_Dates.dt
    GROUP BY CTE_dates.dt, CTE_WeeklyHourlyCounts.ct, CTE_WeeklyHourlyCounts.dt
    )

SELECT
woyhh
,nonzero_year_count
,years_count
,percent_years_count_not_zero
FROM
CTE_FullStats
WHERE woyhh::text like '26%'
    GROUP BY woyhh,   years_count, nonzero_year_count,     percent_years_count_not_zero
    ORDER BY  woyhh

不想要的结果部分:

woyhh | nonzero_year_count | years_count| percent_years_count_not_zero
2605  | 0                  | 3          | 0
2606  | 0                  | 3          | 0
2607  | 5                  | 3          | 166.66
2608  | 0                  | 3          | 0
2609  | 0                  | 3          | 0

不适用于 woyhh 2607 的结果部分是 nonzero_year_count,它应该是 3,因为只有 3 年的数据,并且在这些年份的第 26 周和第 07 小时(任何一天)都有一个计数每月 24 日之后是第 26 周)。此外,percent_years_count_not_zero 应该是 100%,而不是 166%。 100% 是最大的期望 percent_years_count_not_zero。我希望该 woyhh 的计数在所有年份(100%)或更少......但不应该更多。

期望的结果:

woyhh | nonzero_year_count | years_count| percent_years_count_not_zero
2605  | 0                  | 3          | 0
2606  | 0                  | 3          | 0
2607  | 3                  | 3          | 100
2608  | 0                  | 3          | 0
2609  | 0                  | 3          | 0

所以我认为主要问题在于这部分查询:

,SUM(CASE WHEN CTE_WeeklyHourlyCounts.ct > 0 THEN 1 ELSE 0 END) OVER  (PARTITION BY CTE_Dates.dt) AS nonzero_year_count

如果我在 woyhh 上进行分区,但这还不够,因为我需要考虑年份。就像我需要以某种方式将年份分组以确定一年中是否发生了 woyhh,然后将其算作一个,并且在那一年不再计算。我尝试过合并年份,但遇到了更奇怪的结果。

我希望这能澄清我的问题。我在下面包含了一个更新的 sqlfiddle,以复制我用于测试表的数据/查询。感谢您的帮助!

http://sqlfiddle.com/#!17/f6251/1

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    您的 CTE_WeeklyHourlyCounts 定义不适合: 使用 GROUP BY ltg_data.time 部分,将有 5 条记录符合要求:

    2607;"2016-06-26 07:19:00+02";3
    2607;"2016-06-27 07:19:00+02";1
    2607;"2017-06-26 07:19:00+02";2
    2607;"2018-06-25 07:19:00+02";4
    2607;"2018-06-26 07:19:00+02";7
    

    这导致以下计算使 nonzero_year_count 获得值 5。 使用下面的 (GROUP BY ctYear),计数将按年份分组,这将产生所需的结果。

    ,CTE_WeeklyHourlyCounts
    AS
    (
    SELECT
    f_woyhh(time) as dt
        ,EXTRACT(YEAR FROM time) AS ctYear
        ,count(*) AS ct
    FROM
        ltg_data
        GROUP BY dt,EXTRACT(YEAR FROM time) 
    )
    

    顺便说一句 - 当使用 WITH 子句或嵌入式 SELECT 的 SQL 语句未按预期运行时,解决问题的第一步可以是检查这些 WITH 子句或嵌入式 SELECT 的结果。

    为了找到这个特定的问题,我执行了以下操作来验证发生了什么:

    SELECT  f_woyhh(d) as dt,
        EXTRACT(YEAR FROM d::timestamp) AS dtYear 
    FROM generate_series(timestamp '2016-01-01', timestamp '2018-12-31', interval '1 hour') as d
    WHERE f_woyhh(d) between 2605 and 2608; -- the WHERE clause to just limit the result.
    

    紧随其后

    WITH
    CTE_Dates
    AS
    (
    SELECT  f_woyhh(d) as dt,
        EXTRACT(YEAR FROM d::timestamp) AS dtYear from
            generate_series(timestamp '2016-01-01', timestamp '2018-12-31', interval '1 hour') as d
        -- full range of possible dates
    )
    
    SELECT
    f_woyhh(time) as dt
        ,time
        ,count(*) AS ct
    FROM
        ltg_data
        GROUP BY ltg_data.time
        ORDER BY dt, ltg_data.time;
    

    这突出了问题,因为 2607 有 5 条记录(如上所示)。

    【讨论】:

    • 就是这样。我很欣赏关于分离 select 语句的建议,我很欣赏你对查询的新看法和你提供的简单解决方案。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 2019-09-12
    • 1970-01-01
    • 2016-01-14
    相关资源
    最近更新 更多