4

beginrange と endrange の 2 つの列を持つテーブルがあります。重複する範囲は許可されません。これらの列にはインデックスがあり、次のような多くの SQL 条件を試しました。

inputBegin between beginRange and endRange or
inputEnd between beginRange and endRange 

not ( inputEnd < beginRange or inputStart > endRange )

テーブルに 5mil を超えるレコードが含まれているため、非常に遅いことを除いて、これは問題なく動作します。

とにかく効率的なオーバーラップ チェックを作成する方法はありますか?

編集:もう1つの解決策を考えました。オラクルは、インデックスを持つNOT NULL列でcount()が実行された場合にのみインデックスをカウントします。beginRange と endRange が NOT NULL で、両方にインデックスがある場合、3 つの合計を取得できます。

count(endRange) where inputBegin > endRange
+
count(beginRange) where inputEnd < beginRange
=
count(beginRange/endRange)

したがって、UNION ALL を使用すると 3 つの行が得られ、コードでは最初の 2 つの合計が 3 つ目の行と等しいかどうかを確認する必要があります。もちろん、インデックスのみがカウントされ、行にはアクセスされないと想定しています。その他の方法で?

4

4 に答える 4

1

これは答えです - 特定のアサーションを行うことができる場合:

重複する 2 つの既存の行がない と の列を持つテーブルがbeginRangeあります。endRange(beginRange, endRange)

新しい行を挿入したいのですが(inputStart, inputEnd)、テーブルの既存の行と重なっているかどうかを確認してください。

次に、高速であるはずのこの条件を使用できます-単純なインデックス on startRange:

WHERE input_Start <
      ( SELECT endRange
        FROM
          ( SELECT endRange
                 , ROW_NUMBER() OVER(ORDER BY startRange DESC) AS rn 
            FROM tableX
            WHERE startRange < input_End
          ) tmp
        WHERE rn = 1
      )


  --- TRUE  --> Overlaps
  --- FALSE --> No overlap
于 2012-04-13T23:34:03.980 に答える
1

次のことをご希望かどうかわかりません:

  1. 挿入しようとしている行が既存の行の一部と重複しているかどうかを確認するか、または
  2. 既存のすべての行を検索し、重複する行を特定しますか?

(1)の場合、本質的にすでに行っていることは...

SELECT *
FROM YOUR_TABLE
WHERE :inputEnd > beginRange AND :inputStart < endRange;

...コンポーネントが反対方向にある複合インデックスがある場合、オーバーラップが発生し、非常にパフォーマンスが高くなるはずです{beginRange ASC, endRange DESC}


(2) の場合、次のようなウィンドウ処理を利用できます。

SELECT *
FROM (
    SELECT
        YOUR_TABLE.*,
        LEAD(beginRange) OVER (ORDER BY beginRange) nextBeginRange
    FROM YOUR_TABLE
)
WHERE endRange > nextBeginRange;

これにより、次の範囲と重複するすべての範囲が得られます (「次」の意味はbeginRange順序付けのコンテキストで定義されます)。

厳密には、これには複合インデックスも必要ありません (カバーしたい場合を除きます) - 単純なインデックスをオンにするだけで、{beginRange}まともなパフォーマンスが保証されます。

于 2012-04-12T19:34:00.630 に答える
0

既存の範囲がオーバーラップしないと仮定する{beginRange}と、(プライマリまたは代替)キーである必要があり、新しい範囲が既存の範囲の一部とオーバーラップするかどうかを検出するには、次のように実行できます。

SELECT *
FROM YOUR_TABLE
WHERE beginRange = (
    SELECT MAX(beginRange)
    FROM YOUR_TABLE
    WHERE beginRange < :inputEnd
)
AND :inputStart < endRange
  • 新しい範囲が既存の範囲の一部と重複する場合、このクエリは「最も高い」範囲を返します。
  • 重複がない場合は、空の結果セットが返されます。

キーの「下」のインデックス{beginRange}は効率を上げるのに十分です(「MAXスキャン」をサポートするだけで済みます)。

于 2012-04-14T05:58:28.243 に答える
0

このクエリを満たすことができるインデックスはありません。これは実際には、2 つのインデックスを作成し、2 つのクエリを実行してから、結果を UNION するのが最善であることを意味します...

1) InputBegin でインデックスを
作成する 2) InputEnd でのインデックスを作成する
3) 次のクエリを実行する

SELECT * FROM yourTable WHERE InputEnd   < ExclusionPeriodStart 
UNION ALL
SELECT * FROM yourTable WHERE InputBegin > ExclusionPeriodEnd

最初のクエリは、InputEnd インデックスで範囲シークを使用できます。2 番目のクエリでも範囲シークを使用できますが、別のインデックスに対して行います。

クエリを分離することで、2 つの異なる要件が互いに干渉せず、最適なインデックスを使用できます。

また、(データを理解することで) 結果に重複がないこともわかっています (終了する前にレコードを開始することはできないため、両方のクエリにレコードが表示されることはありません)。これはUNION ALL、より遅い の代わりに を使用できることを意味しますUNION

私の知る限り、このクエリをこれよりも高速に実行する方法はありません。(5m レコードでは、小さなデータセットでテーブル全体をスキャンする方がおそらく高速です。)


編集:その答えは、固定範囲内に表示されないすべてのレコードを検索しようとしていることを前提としています。すべてのレコードを他のすべてのレコードに対してチェックしたい場合は、別のアプローチが必要です...

すべてのオーバーラップをチェックするのはコストがかかります。また、これらの 4 つの範囲がある場合、どれを削除するかを考え出すことは不可能です...

1 -->--> 4
      3 -->--> 6
            5 -->--> 8
                  7 -->--> 9

範囲 1 と 3、または範囲 2 と 4 を削除する必要がありますか?

できることは、別の範囲が重複しているすべての範囲を見つけることです。

そして、A が B とオーバーラップし、B が A とオーバーラップすることを見つけたくないのです。

SELECT
  *
FROM
  yourTable   AS first_range
INNER JOIN
  yourTable   AS second_range
    ON  second_range.start_date >= first_range.start_date
    AND second_range.start_date <= first_range.end_date

これは必然的にテーブル全体をスキャンして first_range を探します。ただし、2 番目の範囲の start_date のみを確認するため、衝突に対して start_date インデックスで範囲シークを使用できます。

EDIT2:または、最初の答えの反対が必要ですか?

設定された範囲と衝突するすべての範囲が必要な場合は、同じアプローチの変更が機能します。

SELECT * FROM yourTable WHERE InputEnd   >= ExclusionPeriodStart 
INTERSECT
SELECT * FROM yourTable WHERE InputBegin <= ExclusionPeriodEnd

しかし、これは素晴らしいことではないかもしれません。query1 のテーブルのパーセンテージを取得し、それをテーブルの残りのほぼすべてと交差させます。代わりに、単純なアプローチに頼ることができますが、最適化を追加します...

SELECT
  *
FROM
  yourTable
WHERE
    InputStart <= ExclusionPeriodEnd
AND InputEnd   >= ExclusionPeriodStart

WHERE 句の最初の条件は、範囲シークで解決できます。次に、結果のすべてのレコードをスキャンして、2 番目の条件をテストします。では、スキャンが必要な範囲を減らすことはできますか(currently (start of table) -> (ExclusionPeriodEnd))

追加の情報が 1 つわかっていれば可能です: 任意の 1 つの範囲の最大...

SELECT
  *
FROM
  yourTable
WHERE
    InputStart <= ExclusionPeriodEnd
AND InputStart >= ExclusionPeriodStart - (maximumLength)
AND InputEnd   >= ExclusionPeriodStart

ここで、最初の 2 つの条件が範囲シークを形成し、最後の条件をスキャンするためのはるかに小さなデータ セットを提供します。

ただし、最大長はどうやってわかりますか?テーブル全体をスキャンすることもできますが、それは最適化における自滅的な試みです。

代わりに、計算フィールドにインデックスを付けることができます。範囲の最大長を与える計算。 SELECT MAX(calculatedField) FROM yourTable次に、テーブル全体のスキャンを回避します。または、トリガーを使用して追跡することもできます。どちらが INSERTS の場合は問題ありませんが、DELETE がある場合は少し面倒です (最長の範囲を削除した場合、新しい最長の範囲を見つけるためにテーブル全体を再度スキャンしますか? おそらくそうではなく、古い最大長を維持したくなるかもしれません)代わりは)。

于 2012-04-12T14:00:22.597 に答える