仮定/明確化
infinity
上限と上限を区別する必要はありません(upper(range) IS NULL
)。(どちらの方法でも使用できますが、この方法の方が簡単です。)
以来date
、離散型で、すべての範囲は、デフォルト持って[)
境界を。
ドキュメントごと:
組み込みの範囲タイプint4range
、int8range
およびdaterange
すべての使用下限と除外が上限含む正準形。つまり、[)
。
他のタイプ(tsrange
!など)については、可能であれば同じことを強制します。
純粋なSQLを使用したソリューション
明確にするためにCTEを使用:
WITH a AS (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
)
, b AS (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM a
)
, c AS (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM b
)
SELECT daterange(min(startdate), max(enddate)) AS range
FROM c
GROUP BY grp
ORDER BY 1;
または、サブクエリの場合も同じですが、高速ですが、読みにくいです:
SELECT daterange(min(startdate), max(enddate)) AS range
FROM (
SELECT *, count(step) OVER (ORDER BY range) AS grp
FROM (
SELECT *, lag(enddate) OVER (ORDER BY range) < startdate OR NULL AS step
FROM (
SELECT range
, COALESCE(lower(range),'-infinity') AS startdate
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
FROM test
) a
) b
) c
GROUP BY grp
ORDER BY 1;
または、サブクエリレベルを1つ減らして、並べ替え順序を反転させます。
SELECT daterange(min(COALESCE(lower(range), '-infinity')), max(enddate)) AS range
FROM (
SELECT *, count(nextstart > enddate OR NULL) OVER (ORDER BY range DESC NULLS LAST) AS grp
FROM (
SELECT range
, max(COALESCE(upper(range), 'infinity')) OVER (ORDER BY range) AS enddate
, lead(lower(range)) OVER (ORDER BY range) As nextstart
FROM test
) a
) b
GROUP BY grp
ORDER BY 1;
- 2番目のステップでウィンドウを
ORDER BY range DESC NULLS LAST
(でNULLS LAST
)ソートして、ソート順序を完全に逆にします。これは、より安価で(作成が容易で、推奨インデックスのソート順と完全に一致する)、でのコーナーケースで正確でなければなりませんrank IS NULL
。
説明する
a
:で並べ替えながら、ウィンドウ関数で上限()の最大値をrange
計算します。
NULL境界(無制限)を+/-に置き換えて、単純化するだけです(特別なNULLケースはありません)。enddate
infinity
b
:同じソート順で、前のものenddate
がstartdate
ギャップよりも早く、新しい範囲を開始する場合(step
)。
上限は常に除外されることに注意してください。
c
:grp
別のウィンドウ関数でステップをカウントすることにより、グループ()を形成します。
外側のSELECT
ビルドでは、各グループの下限から上限までの範囲です。ボイラ。
SOに関する密接に関連した回答と詳細な説明:
plpgsqlによる手続き型ソリューション
任意のテーブル/列名に対して機能しますが、タイプに対してのみ機能しdaterange
ます。
ループを使用した手続き型のソリューションは一般に低速ですが、この特別なケースでは、単一の順次スキャンのみを必要とするため、関数が大幅に高速になると予想されます。
CREATE OR REPLACE FUNCTION f_range_agg(_tbl text, _col text)
RETURNS SETOF daterange AS
$func$
DECLARE
_lower date;
_upper date;
_enddate date;
_startdate date;
BEGIN
FOR _lower, _upper IN EXECUTE
format($$SELECT COALESCE(lower(t.%2$I),'-infinity') -- replace NULL with ...
, COALESCE(upper(t.%2$I), 'infinity') -- ... +/- infinity
FROM %1$I t
ORDER BY t.%2$I$$
, _tbl, _col)
LOOP
IF _lower > _enddate THEN -- return previous range
RETURN NEXT daterange(_startdate, _enddate);
SELECT _lower, _upper INTO _startdate, _enddate;
ELSIF _upper > _enddate THEN -- expand range
_enddate := _upper;
-- do nothing if _upper <= _enddate (range already included) ...
ELSIF _enddate IS NULL THEN -- init 1st round
SELECT _lower, _upper INTO _startdate, _enddate;
END IF;
END LOOP;
IF FOUND THEN -- return last row
RETURN NEXT daterange(_startdate, _enddate);
END IF;
END
$func$ LANGUAGE plpgsql;
コール:
SELECT * FROM f_range_agg('test', 'range'); -- table and column name
ロジックはSQLソリューションに似ていますが、1回のパスで対応できます。
SQLフィドル。
関連:
動的SQLでユーザー入力を処理するための通常のドリル:
索引
これらの各ソリューションではrange
、大きなテーブルでのパフォーマンスを向上させるために、プレーン(デフォルト)のbtreeインデックスが役立ちます。
CREATE INDEX foo on test (range);
btreeインデックスは範囲タイプに対して限定的に使用されますが、事前にソートされたデータを取得でき、インデックスのみのスキャンでさえ取得できます。