2014-02-27 43 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) 
相關問題