10

背景(入力)

Global Historical Climatology Networkは、気象測定値のコレクションで無効または誤ったデータにフラグを立てました。これらの要素を削除した後、連続した日付のセクションがなくなった一連のデータがあります。データは次のようになります。

"2007-12-01";14 -- Start of December
"2007-12-29";8
"2007-12-30";11
"2007-12-31";7
"2008-01-01";8 -- Start of January
"2008-01-02";12
"2008-01-29";0
"2008-01-31";7
"2008-02-01";4 -- Start of February
... entire month is complete ...
"2008-02-29";12
"2008-03-01";14  -- Start of March
"2008-03-02";17
"2008-03-05";17

問題(出力)

欠落しているデータを推定して (たとえば、他の年から平均することによって) 連続した範囲を提供することは可能ですが、システムを簡素化するために、月を埋める連続した日付範囲があるかどうかに基づいて、連続していないセグメントにフラグを立てたいと考えています。

D;"2007-12-01";14 -- Start of December
D;"2007-12-29";8
D;"2007-12-30";11
D;"2007-12-31";7
D;"2008-01-01";8 -- Start of January
D;"2008-01-02";12
D;"2008-01-29";0
D;"2008-01-31";7
"2008-02-01";4 -- Start of February
... entire month is complete ...
"2008-02-29";12
D;"2008-03-01";14  -- Start of March
D;"2008-03-02";17
D;"2008-03-05";17

1843 年にいくつかの測定が行われました。

質問

すべての測候所について、1 日以上欠落している月のすべての日をどのようにマークしますか?

ソースコード

データを選択するコードは次のようになります。

select
  m.id,
  m.taken,
  m.station_id,
  m.amount
from
  climate.measurement

関連するアイデア

連続した日付で満たされたテーブルを生成し、それらを測定データの日付と比較します。

アップデート

この問題は、このセクションの SQL を使用して再現できます。

テーブル

テーブルは次のように作成されます。

CREATE TABLE climate.calendar
(
  id serial NOT NULL,
  n character varying(2) NOT NULL,
  d date NOT NULL,
  "valid" boolean NOT NULL DEFAULT true,
  CONSTRAINT calendar_pk PRIMARY KEY (id)
)
WITH (
  OIDS=FALSE
);

データの生成

次の SQL は、テーブルにデータを挿入します ( id[int]、name [varchar]、date [date]、valid[boolean]):

insert into climate.calendar (n, d) 
    select 'A', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d) 
    select 'B', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d) 
    select 'C', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d) 
    select 'D', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d) 
    select 'E', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
insert into climate.calendar (n, d) 
    select 'F', (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n

~の値は、特定の日に測定を行った気象観測所の名前'A'を表します。'F'

ランダムな行を削除

次のようにいくつかの行を削除します。

delete from climate.calendar where id in (select id from climate.calendar order by random() limit 5000);

試み #1

次の例では、月に 1 日以上欠落しているすべての日に対してvalidフラグを切り替えません。false

UPDATE climate.calendar
SET valid = false
WHERE date_trunc('month', d) IN (
    SELECT DISTINCT date_trunc('month', d)
    FROM climate.calendar A
    WHERE NOT EXISTS (
        SELECT 1
        FROM climate.calendar B
        WHERE A.d - 1 = B.d
   )
);

試み #2

次の SQL は、空の結果セットを生成します。

with gen_calendar as (
    select (date('1982-01-1') + (n || ' days')::interval)::date cal_date
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
)
select gc.cal_date
from gen_calendar gc
left join climate.calendar c on c.d = gc.cal_date
where c.d is null;

試み #3

次の SQL は、駅名と日付の可能な組み合わせをすべて生成します。

select
  distinct( cc.n ), t.d
from
  climate.calendar cc,
  (
    select (date('1982-01-1') + (n || ' days')::interval)::date d
    from generate_series(0, date('2011-04-9') - date('1982-01-1') ) n
  ) t
order by
  cc.n

ただし、実際のデータには数百の測点があり、日付は 1800 年代半ばまでさかのぼるため、すべての測点のすべての日付のデカルトは大きすぎます。このようなアプローチは、十分な時間があればうまくいくかもしれません...もっと速い方法があるはずです。

試み #4

PostgreSQL にはウィンドウ関数があります。

postgres でウィンドウ関数を使用して特定の変更を選択する方法

ありがとうございました!

4

3 に答える 3

5

generate_series()

PostgreSQL のgenerate_series()関数は、日付の連続したリストを含むビューを作成できます。

with calendar as (
    select ((select min(date) from test)::date + (n || ' days')::interval)::date cal_date
    from generate_series(0, (select max(date) - min(date) from test)) n
)
select cal_date
from calendar c
left join test t on t.date = c.cal_date
where t.date is null;

表情select max(date) - min(date) from testが一つずれているかもしれません。

月ごとの日数を数える

無効な月を識別する 1 つの方法は、2 つのビューを作成することです。1 つ目は、各ステーションが各月に生成する必要がある 1 日の測定値の数をカウントします。climate.calendar(は に変換されることに注意してくださいclimate_calendar。) 2 番目は、各ステーションが 1 か月に生成した実際の毎日の測定値を返します。

駅ごとの月間最大日数

このビューは、ステーションごとに 1 か月の実際の日数を返します。(たとえば、2 月は常に 28 日または 29 日になります。)

create view count_max_station_calendar_days as 
with calendar as (
    select ((select min(d) from climate_calendar)::date + (n || ' days')::interval)::date cal_date
    from generate_series(0, (select max(d) - min(d) from climate_calendar)) n
)
select n, extract(year from cal_date) yr, extract(month from cal_date) mo, count(*) num_days
from stations cross join calendar
group by n, yr, mo
order by n, yr, mo

ステーションごとの月ごとの実際の日数

返される合計日数は、集計より少なくなります。(たとえば、1 月は常に 31 日以下になります。)

create view count_actual_station_calendar_days as
select n, extract(year from d) yr, extract(month from d) mo, count(*) num_days
from climate_calendar
group by n, yr, mo
order by n, yr, mo;

ORDER BY本番環境で句を削除します (開発に役立ちます) 。

ビューの比較

2 つのビューを結合して、フラグを立てる必要があるステーションと月を特定し、新しいビューにします。

create view invalid_station_months as 
select m.n, m.yr, m.mo, m.num_days - a.num_days num_days_missing
from count_max_station_calendar_days m
inner join count_actual_station_calendar_days a
       on (m.n = a.n and m.yr = a.yr and m.mo = a.mo and m.num_days <> a.num_days)

n   yr    mo  num_days_missing
--
A   1982  1   1
E   2007  3   1

num_days_missingは必須ではありませんが、あると便利です。

更新する必要がある行は次のとおりです。

select cc.* 
from climate_calendar cc
inner join invalid_station_months im 
        on (cc.n = im.n and 
            extract(year from cc.d) = im.yr and
            extract(month from cc.d) = im.mo)
where valid = true

データベースを更新する

それらを更新するには、idキーが便利です。

update climate_calendar
set valid = false
where id in (
    select id
    from climate_calendar cc
    inner join invalid_station_months im 
        on (cc.n = im.n and 
            extract(year from cc.d) = im.yr and
            extract(month from cc.d) = im.mo)
    where valid = true
);
于 2011-05-06T00:56:56.443 に答える
1

1日に複数の行が存在できないと仮定すると、これは、行数がその月の日数と等しくないすべての月を返す必要があります。

SELECT station_id, DATE_TRUNC('month', d)
FROM climate.calendar
GROUP BY station_id, DATE_TRUNC('month', d)
HAVING COUNT(*) <> 
  DATE_PART('month',
            DATE_TRUNC('month', d) + INTERVAL '1 month' - INTERVAL '1 day')
于 2011-05-08T00:25:11.310 に答える
1

is_contiguous という BOOLEAN フィールドがあると仮定して、これを行う 1 つの方法を次に示します。必要に応じて変更します。

UPDATE measurement
SET is_contiguous = FALSE
WHERE NOT EXISTS (
  SELECT 1
    FROM measurement B
   WHERE measurement.taken - 1 = B.taken
);

編集:

私はあなたの要求を誤解したと思います。連続していない個々の日付にフラグを立てたいと思っていました。しかし、どうやら、1 か月分の日付が欠落している場合、不連続としてフラグを立てたいと考えているようです。

編集2:

これは、日が欠落している個別の月を選択する、元の (間違った) クエリの修正版です。

UPDATE measurement
SET is_contiguous = FALSE
WHERE date_trunc('month', taken) IN (
    SELECT DISTINCT date_trunc('month', taken)
    FROM measurement A
    WHERE NOT EXISTS (
        SELECT 1
        FROM measurement B
        WHERE A.taken - 1 = B.taken
   )
);
于 2011-05-06T00:00:15.960 に答える