Sql 对由特定列划分的上一时间范围中的行进行计数

Sql 对由特定列划分的上一时间范围中的行进行计数,sql,postgresql,sql-update,time-series,window-functions,Sql,Postgresql,Sql Update,Time Series,Window Functions,我的数据集由来自不同行业的不同公司的每日实际工作日时间序列组成,我使用PostgreSQL。我的数据集中有一个指示符变量,取值为1,-1,大多数时候为0。为了提高问题的可读性,我将指标变量不等于零的天数作为指标事件 因此,对于之前三个工作日内同一行业的其他指标事件之前的所有指标事件,指标变量应更新为零 我们可以考虑以下示例数据集: day company industry indicator 2012-01-12 A fi

我的数据集由来自不同行业的不同公司的每日实际工作日时间序列组成,我使用PostgreSQL。我的数据集中有一个指示符变量,取值为1,-1,大多数时候为0。为了提高问题的可读性,我将指标变量不等于零的天数作为指标事件

因此,对于之前三个工作日内同一行业的其他指标事件之前的所有指标事件,指标变量应更新为零

我们可以考虑以下示例数据集:

day              company    industry       indicator
2012-01-12       A          financial      1
2012-01-12       B          consumer       0 
2012-01-13       A          financial      1 
2012-01-13       B          consumer       -1
2012-01-16       A          financial      0 
2012-01-16       B          consumer       0 
2012-01-17       A          financial      0
2012-01-17       B          consumer       0
2012-01-17       C          consumer       0
2012-01-18       A          financial      0
2012-01-18       B          consumer       0
2012-01-18       C          consumer       1
因此,应更新为零的指标值在2012年1月13日为A公司的分录,在2012年1月18日为C公司的分录,因为在同一行业的3个工作日内,它们之前有另一个指标事件

我试着用以下方法来完成它:

UPDATE test SET indicator = 0 
WHERE (day, industry) IN (
SELECT day, industry
  FROM (
       SELECT industry, day,
       COUNT(CASE WHEN indicator <> 0 THEN 1 END) 
          OVER (PARTITION BY industry ORDER BY day 
                ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) As cnt
       FROM test
       ) alias
  WHERE cnt >= 2) 
我的想法是按行业划分,计算当天和前三天的指标事件。如果计数大于1,则将指示器值更新为零

弱点在于,到目前为止,它计算的是之前按行业划分的三行,而不是之前的三个营业日。因此,在示例数据中,它无法在2012-01-18更新公司C,因为它计算行业=消费者的最后三行,而不是过去三个工作日行业=消费者的所有行

我尝试了不同的方法,比如在代码的最后三行中添加另一个子查询,或者在最后三行之后添加WHERE-EXISTS-子句,以确保代码在前面三个日期都有效。但什么都没用。我真的不知道该怎么做,我只是学习使用PostgreSQL

你有什么办法修理它吗


或者我的想法完全错了,你知道如何解决我的问题的另一种方法吗?

与此同时,我自己也找到了一种可能的解决方法,我希望这不会违反论坛的礼仪

请注意,这只是一种可能的解决方案。非常欢迎您对其进行评论或开发 改进,如果你想

对于第一部分,函数addbusinessdays可以向 给定日期,我指的是: 我只是稍微修改了一下,因为我不喜欢假期,只喜欢周末

    CREATE OR REPLACE FUNCTION addbusinessdays(date, integer)
      RETURNS date AS
    $BODY$ 
    with alldates as (
        SELECT i,
        $1 + (i * case when $2 < 0 then -1 else 1 end) AS date
        FROM generate_series(0,(abs($2) + 5)*2) i
    ),
    days as (
        select i, date, extract('dow' from date) as dow
        from alldates
    ),
    businessdays as (
        select i, date, d.dow from days d
        where d.dow between 1 and 5
        order by i
    )

    select date from businessdays where
            case when $2 > 0 then date >=$1 when $2 < 0 then date <=$1 else date =$1 end
        limit 1
        offset abs($2)
    $BODY$
      LANGUAGE 'sql' VOLATILE
      COST 100;
    ALTER FUNCTION addbusinessdays(date, integer) OWNER TO postgres;
对于第二部分,我指的是这个相关的问题,其中我应用了Erwin Brandstetter的相关子查询方法:

数据库设计 首先,你的桌子应该正常化。行业应该是一个小的外键列,通常是引用行业表的行业id的整数。也许你已经把它简化了,只是为了这个问题。您的实际表定义将大有帮助

由于带有指示符的行很少,但非常有趣,因此创建一个可能覆盖部分索引以加快任何解决方案:

CREATE INDEX tbl_indicator_idx ON tbl (industry, day)
WHERE  indicator <> 0;
缩短周末很方便

将此功能集成到您的更新中:


这应该比使用相关子查询和每行函数调用的解决方案快得多。即使这是基于我之前的回答,也不适合这种情况。

如果每三个工作日就有一个相同行业的指标,会发生什么?除第一个指示器外,是否重置所有指示器?你不应该有一个静态网格吗?比如,只选择Mon的第一个事件结婚。第一个来自Thu没错,在这种情况下,除了第一个指示器,我会重置所有指示器。因此,您的解决方案非常有效,非常感谢@Erwinbrandstetter非常感谢您的精彩回答,也感谢您对我如何进一步改进工作的建议。我真的很感激@欧文布兰德斯特
CREATE INDEX tbl_indicator_idx ON tbl (industry, day)
WHERE  indicator <> 0;
WITH x AS (               -- only with indicator
   SELECT DISTINCT industry, day
   FROM   tbl t 
   WHERE  indicator <> 0
   )
SELECT industry, day
FROM  (
   SELECT i.industry, d.day, x.day IS NOT NULL AS incident
        , count(x.day) OVER (PARTITION BY industry ORDER BY day_nr
                             ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS ct
   FROM  (
      SELECT *, row_number() OVER (ORDER BY d.day) AS day_nr
      FROM  (
         SELECT generate_series(min(day), max(day), interval '1d')::date AS day
         FROM   x
         ) d
      WHERE  extract('ISODOW' FROM d.day) < 6
      ) d
   CROSS  JOIN (SELECT DISTINCT industry FROM x) i
   LEFT   JOIN x USING (industry, day)
   ) sub
WHERE  incident
AND    ct > 1
ORDER  BY 1, 2;
WITH x AS (               -- only with indicator
   SELECT DISTINCT industry, day
   FROM   tbl t 
   WHERE  indicator <> 0
   )
UPDATE tbl t
SET    indicator = 0 
FROM  (
   SELECT i.industry, d.day, x.day IS NOT NULL AS incident
        , count(x.day) OVER (PARTITION BY industry ORDER BY day_nr
                             ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS ct
   FROM  (
      SELECT *, row_number() OVER (ORDER BY d.day) AS day_nr
      FROM  (
         SELECT generate_series(min(day), max(day), interval '1d')::date AS day
         FROM   x
         ) d
      WHERE  extract('isodow' FROM d.day) < 6
      ) d
   CROSS  JOIN (SELECT DISTINCT industry FROM x) i
   LEFT   JOIN x USING (industry, day)
   ) u
WHERE  u.incident
AND    u.ct > 1
AND    t.industry = u.industry
AND    t.day = u.day;