2014-02-27 42 views
1

我的数据集包含来自不同行业的不同公司的每日(实际上是工作日)时间序列,并且我使用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 

所以这应更新为0的指标值2012-01-13用于公司A的条目,2012-01-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) 

我的想法是计算当天的指标活动,并通过行业划分的3前段日子。如果它计数超过1,它将指标值更新为零。

弱点是,到目前为止,它在前三行(按行业划分)而不是前三个工作日。因此,在示例数据中,它无法在2012年1月18日更新公司C,因为它计算的是行业=消费者的最后三行,而不是计算最近三个工作日内行业=消费者的所有行。

我尝试了不同的方法,例如在代码的第三行添加另一个子查询,或者在第三行之后添加一个WHERE EXISTS - 以确保代码统计上述三个日期。但没有任何工作。我真的不知道该怎么做(我只是学习使用PostgreSQL)。

你有什么想法如何解决它?

或者我正在考虑一个完全错误的方向,你知道另一种方法如何解决我的问题?

+0

如果每三个工作日有相同行业的指标会发生什么情况?你是否重置了每个指标,但第一个?你不应该有一个静态网格吗?例如,“只挑选星期一至星期三的第一起事件,以及星期四至星期五的第一起事件”。 –

+0

是的,在那种情况下,我会重置每个指标,但第一个。因此,您的解决方案非常完美,非常感谢您。 @ErwinBrandstetter – user3319629

回答

1

数据库设计

击退,你的表应该正常化。 industry应该是一个小型的外键列(通常为integer),其引用industry表的industry_id。也许你已经这样做了,只是为了这个问题而简化了。您的实际表格定义将会走很长的路。

由于有指示器行是罕见的,但非常有趣的,建立一个(可能是“覆盖”)局部索引可以使任何溶液更快:

CREATE INDEX tbl_indicator_idx ON tbl (industry, day) 
WHERE indicator <> 0; 

Equality first, range last.
假设indicator定义NOT NULL。如果industryinteger,则此索引将是完全有效的。

查询

此查询标识行被复位:

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; 

SQL Fiddle.

ISODOW as extract() parameter便于截断周末。

UPDATE整合这样的:

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; 

这应该是比相关子查询和函数调用的每一行的解决方案大大加快。即使这是基于我自己以前的答案,它不是完美的这个的情况。

+0

非常感谢您的宝贵答案,并感谢您的建议,我可以如何进一步改进我的工作。对此,我真的非常感激!! @ErwinBrandstetter – user3319629

0

同时我自己找到了一个可能的解决方案(我希望这不是针对论坛的礼仪)。

请注意,这只是一种可能的解决方案。如果您愿意,欢迎您发表评论或者开发 改进。

在第一部分,功能addbusinessdays这会增加(或减少)工作日 一个给定的日期,我指的是: http://osssmb.wordpress.com/2009/12/02/business-days-working-days-sql-for-postgres-2/ (我只是略作修改,因为我不喜欢假期,只是周末)

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; 

对于第二部分,我指的是此相关的问题,在这里我将欧文Brandstetter修改的相关子查询方法:Window Functions or Common Table Expressions: count previous rows within range

UPDATE test SET indicator = 0 
    WHERE (day, industry) IN (
    SELECT day, industry 
     FROM (
      SELECT industry, day, 
        (SELECT COUNT(CASE WHEN indicator <> 0 THEN 1 END) 
        FROM test t1 
        WHERE t1.industry = t.industry 
        AND t1.day between addbusinessdays(t.day,-3) and t.day) As cnt 
      FROM test t 
      ) alias 
     WHERE cnt >= 2) 
相关问题