説明や詳細を気にしない場合は、「黒魔術バージョン」を使用してください 以下。
これまでに他の回答で提示されたすべてのクエリは、パラメータ化できない条件で動作します -インデックスを使用することはできず、一致する行を見つけるためにベーステーブルのすべての行の式を計算する必要があります。小さなテーブルではあまり問題になりません。問題(たくさん )大きなテーブルで。
次の簡単な表があるとします:
CREATE TABLE event (
event_id serial PRIMARY KEY
, event_date date
);
クエリ
以下のバージョン1および2は、次の形式の単純なインデックスを使用できます。
CREATE INDEX event_event_date_idx ON event(event_date);
ただし、次のすべてのソリューションは、インデックスなしでさらに高速になります 。
1。シンプルバージョン
SELECT *
FROM (
SELECT ((current_date + d) - interval '1 year' * y)::date AS event_date
FROM generate_series( 0, 14) d
CROSS JOIN generate_series(13, 113) y
) x
JOIN event USING (event_date);
サブクエリx
CROSS JOIN
から、指定された年の範囲で可能なすべての日付を計算します 2つのgenerate_series()
の 呼び出します。選択は、最後の単純な結合で行われます。
2。高度なバージョン
WITH val AS (
SELECT extract(year FROM age(current_date + 14, min(event_date)))::int AS max_y
, extract(year FROM age(current_date, max(event_date)))::int AS min_y
FROM event
)
SELECT e.*
FROM (
SELECT ((current_date + d.d) - interval '1 year' * y.y)::date AS event_date
FROM generate_series(0, 14) d
,(SELECT generate_series(min_y, max_y) AS y FROM val) y
) x
JOIN event e USING (event_date);
年の範囲はテーブルから自動的に推定されるため、生成される年は最小限に抑えられます。
できます さらに一歩進んで、ギャップがある場合は既存の年のリストを抽出します。
有効性は、日付の分布に依存します。それぞれが多くの行を持つ数年は、このソリューションをより便利にします。何年もの間、それぞれの行が少ないため、有用性が低くなります。
シンプルなSQLフィドル 遊ぶ。
3。黒魔術バージョン
2016年を更新して、H.O.Tをブロックする「生成された列」を削除しました。更新;よりシンプルで高速な関数。
2018年に更新され、 IMMUTABLE
を使用してMMDDを計算できるようになりました。 関数のインライン化を可能にする式。
integer
を計算するための単純なSQL関数を作成します パターン'MMDD'
から :
CREATE FUNCTION f_mmdd(date) RETURNS int LANGUAGE sql IMMUTABLE AS
'SELECT (EXTRACT(month FROM $1) * 100 + EXTRACT(day FROM $1))::int';
to_char(time、'MMDD')
がありました 最初は、上記の式に切り替えました。これは、Postgres9.6および10の新しいテストで最速であることが証明されました。
db<>ここでフィドル
EXTRACT(xyz FROM date)
であるため、関数のインライン化が可能です。 IMMUTABLE
で実装されます 関数date_part(text、date)
初めの。そして、それは IMMUTABLE
でなければなりません 次の必須の複数列式インデックスで使用できるようにするため:
CREATE INDEX event_mmdd_event_date_idx ON event(f_mmdd(event_date), event_date);
複数列 いくつかの理由で:
ORDER BY
を支援できます または指定された年から選択して。ここを読んでください。インデックスの追加コストはほとんどありません。 日付コード> データアライメントのためにパディングで失われる4バイトに収まります。こちらをお読みください。
また、両方のインデックス列が同じテーブル列を参照しているため、H.O.T。に関して問題はありません。 更新。ここを読んでください。
それらすべてを支配する1つのPL/pgSQLテーブル関数
今年の変わり目をカバーするために、2つのクエリのうちの1つにフォークします。
CREATE OR REPLACE FUNCTION f_anniversary(date = current_date, int = 14)
RETURNS SETOF event AS
$func$
DECLARE
d int := f_mmdd($1);
d1 int := f_mmdd($1 + $2 - 1); -- fix off-by-1 from upper bound
BEGIN
IF d1 > d THEN
RETURN QUERY
SELECT *
FROM event e
WHERE f_mmdd(e.event_date) BETWEEN d AND d1
ORDER BY f_mmdd(e.event_date), e.event_date;
ELSE -- wrap around end of year
RETURN QUERY
SELECT *
FROM event e
WHERE f_mmdd(e.event_date) >= d OR
f_mmdd(e.event_date) <= d1
ORDER BY (f_mmdd(e.event_date) >= d) DESC, f_mmdd(e.event_date), event_date;
-- chronological across turn of the year
END IF;
END
$func$ LANGUAGE plpgsql;
電話 デフォルトの使用:「今日」から14日:
SELECT * FROM f_anniversary();
「2014-08-23」から7日間お電話ください:
SELECT * FROM f_anniversary(date '2014-08-23', 7);
SQLフィドル EXPLAIN ANALYZE
の比較 。
2月29日
記念日や「誕生日」を扱うときは、うるう年の特別なケース「2月29日」の扱い方を定義する必要があります。
日付の範囲をテストする場合は、2月29日
現在の年がうるう年ではない場合でも、通常は自動的に含まれます 。この日をカバーすると、日数の範囲が1つ遡って延長されます。
一方、今年がうるう年で、15日を探したい場合は、14日の結果が得られる可能性があります。データがうるう年以外のものである場合は、うるう年の日数。
たとえば、ボブは2月29日に生まれます。
私のクエリ1.および2.には、うるう年の2月29日のみが含まれます。ボブの誕生日は約4年ごとです。
私のクエリ3.には2月29日が含まれています。ボブは毎年誕生日を迎えます。
魔法の解決策はありません。すべてのケースに必要なものを定義する必要があります。
テスト
私の主張を実証するために、提示されたすべてのソリューションを使用して広範なテストを実行しました。各クエリを指定されたテーブルに適合させ、 ORDER BY
なしで同じ結果を生成しました。 。
良いニュース:それらはすべて正しい 構文エラーがあったGordonのクエリと、年が終わると失敗する@wildplasserのクエリ(修正が簡単)を除いて、同じ結果が得られます。
20世紀のランダムな日付の108000行を挿入します。これは、生きている人々(13歳以上)の表に似ています。
INSERT INTO event (event_date)
SELECT '2000-1-1'::date - (random() * 36525)::int
FROM generate_series (1, 108000);
〜8%を削除して、いくつかのデッドタプルを作成し、テーブルをより「現実的な」ものにします。
DELETE FROM event WHERE random() < 0.08;
ANALYZE event;
私のテストケースは99289行、4012ヒットでした。
C-キャットコール
WITH anniversaries as (
SELECT event_id, event_date
,(event_date + (n || ' years')::interval)::date anniversary
FROM event, generate_series(13, 113) n
)
SELECT event_id, event_date -- count(*) --
FROM anniversaries
WHERE anniversary BETWEEN current_date AND current_date + interval '14' day;
C1-Catcallのアイデアが書き直されました
マイナーな最適化を除いて、主な違いは、正確な年数のみを追加することです。 date_trunc('year'、age(current_date + 14、event_date))
今年の記念日を取得するために、CTEの必要性を完全に回避します:
SELECT event_id, event_date
FROM event
WHERE (event_date + date_trunc('year', age(current_date + 14, event_date)))::date
BETWEEN current_date AND current_date + 14;
D-ダニエル
SELECT * -- count(*) --
FROM event
WHERE extract(month FROM age(current_date + 14, event_date)) = 0
AND extract(day FROM age(current_date + 14, event_date)) <= 14;
E1-アーウィン1-
上記の「1.シンプルバージョン」を参照してください。
E2-アーウィン2-
上記の「2.アドバンストバージョン」を参照してください。
E3-Erwin 3
上記の「3.黒魔術バージョン」を参照してください。
G-ゴードン
SELECT * -- count(*)
FROM (SELECT *, to_char(event_date, 'MM-DD') AS mmdd FROM event) e
WHERE to_date(to_char(now(), 'YYYY') || '-'
|| (CASE WHEN mmdd = '02-29' THEN '02-28' ELSE mmdd END)
,'YYYY-MM-DD') BETWEEN date(now()) and date(now()) + 14;
H --a_horse_with_no_name
WITH upcoming as (
SELECT event_id, event_date
,CASE
WHEN date_trunc('year', age(event_date)) = age(event_date)
THEN current_date
ELSE cast(event_date + ((extract(year FROM age(event_date)) + 1)
* interval '1' year) AS date)
END AS next_event
FROM event
)
SELECT event_id, event_date
FROM upcoming
WHERE next_event - current_date <= 14;
W-ワイルドプラサー
CREATE OR REPLACE FUNCTION this_years_birthday(_dut date) RETURNS date AS
$func$
DECLARE
ret date;
BEGIN
ret :=
date_trunc( 'year' , current_timestamp)
+ (date_trunc( 'day' , _dut)
- date_trunc( 'year' , _dut));
RETURN ret;
END
$func$ LANGUAGE plpgsql;
他のすべてと同じものを返すように簡略化:
SELECT *
FROM event e
WHERE this_years_birthday( e.event_date::date )
BETWEEN current_date
AND current_date + '2weeks'::interval;
W1-wildplasserのクエリが書き直されました
上記は、(このすでにかなりの規模の投稿の範囲を超えて)多くの非効率的な詳細に悩まされています。書き直されたバージョンは多く より速く:
CREATE OR REPLACE FUNCTION this_years_birthday(_dut INOUT date) AS
$func$
SELECT (date_trunc('year', now()) + ($1 - date_trunc('year', $1)))::date
$func$ LANGUAGE sql;
SELECT *
FROM event e
WHERE this_years_birthday(e.event_date)
BETWEEN current_date
AND (current_date + 14);
テスト結果
PostgreSQL 9.1.7で一時テーブルを使用してこのテストを実行しました。結果は、 EXPLAIN ANALYZE
で収集されました。 、ベスト5。
結果
Without index C: Total runtime: 76714.723 ms C1: Total runtime: 307.987 ms -- ! D: Total runtime: 325.549 ms E1: Total runtime: 253.671 ms -- ! E2: Total runtime: 484.698 ms -- min() & max() expensive without index E3: Total runtime: 213.805 ms -- ! G: Total runtime: 984.788 ms H: Total runtime: 977.297 ms W: Total runtime: 2668.092 ms W1: Total runtime: 596.849 ms -- ! With index E1: Total runtime: 37.939 ms --!! E2: Total runtime: 38.097 ms --!! With index on expression E3: Total runtime: 11.837 ms --!!
他のすべてのクエリは、非引数を使用するため、インデックスの有無にかかわらず同じように実行されます 式。
結論
-
これまでのところ、@Danielのクエリが最速でした。
-
@wildplassers(書き直された)アプローチも許容範囲内で実行されます。
-
@Catcallのバージョンは、私の逆のアプローチのようなものです。テーブルが大きくなると、パフォーマンスはすぐに手に負えなくなります。
ただし、書き直されたバージョンはかなりうまく機能します。私が使用する式は、@wildplassserのthis_years_birthday()
のより単純なバージョンのようなものです。 機能。 -
私の「シンプルバージョン」はインデックスがなくても高速です 、必要な計算が少ないためです。
-
min()
であるため、インデックスを使用すると、「詳細バージョン」は「単純バージョン」とほぼ同じ速度になります。 およびmax()
非常にになる インデックスで安い。どちらも、インデックスを使用できない残りの部分よりも大幅に高速です。 -
私の「黒魔術バージョン」は、インデックスの有無にかかわらず最速です 。そして、それは非常に 電話するのは簡単です。
-
生命表ではインデックス さらに大きく 違い。列が多いほどテーブルが大きくなり、順次スキャンのコストが高くなりますが、インデックスサイズは同じままです。