在woy和hh24的分区上使用sum(case when ...)

时间:2018-07-22 05:36:39

标签: sql postgresql

使用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')

因此,以下查询应返回一些有关表数据的基本统计信息。我认为,挑战在于尝试以一年中的几个小时和小时为单位进行划分,同时以某种方式合并年份。错误的数据涉及查询的一部分,该部分试图确定某年的某周和某小时(每小时)的计数> 0的年数。这是查询所使用的查询和功能(将标准化年份逐年纳入leap年的虚函数)。我正在使用“生成系列”,因为我希望获得一整年的价值,即使某个价值没有任何计数。

功能:(可能不重要,但想在我遗漏某些东西且确实与问题有关的情况下包括在内)

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

不适用于2607的部分结果为nonzero_year_count,应为3,因为只有3年的数据,并且每个年份的第26周和第07小时都有计数(任何一天)该月24日之后的第26周)。另外,percent_years_count_not_zero应该是100%,而不是166%。 100%是最大期望的percent_years_count_not_zero。我希望所有年份(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

如果我要分区,但这还不够,因为我需要考虑年份。就像我需要以某种方式对年份进行分组,以确定一年中是否发生过一次问题,然后将其视为该年份中的一年而已。我尝试合并年份,但遇到了甚至更奇怪的结果。

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

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

1 个答案:

答案 0 :(得分:2)

您的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。 使用下面的(按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子句或嵌入式SELECTs的结果。

要找到此特定问题,我执行了以下操作以验证正在发生的情况:

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条记录(如上所示)。