4

私は 2 つのテーブルを持っています。1 つ目は大きなテーブル (数百万行) で、最も興味深い列は "キー" と呼ぶ整数です。ただし、このソリューションは、日付または日時の範囲でも同じであると思います。

2 番目のテーブルははるかに小さく (数千行)、キーの範囲で定義された興味深い属性が多数含まれています。次の構造があります。

key_lower_bound : int key_upper_bound : int Interesting_value1 : float Interest_value2 : int Interest_value3 : varchar(50) ...

最初のテーブルのすべての値を検索し、最初のテーブルのキーが間隔 [key_lower_bound, key_upper_bound) 内にあるかどうかに基づいて、それらを 2 番目のテーブルと「結合」したいと考えています。

これは、数学的にはスパース内積またはスパース ドット積のようなものですが、これらの範囲が 2 番目のテーブルに含まれているため、少し奇妙です。それでも、これをコードで書くとしたら、O(|最初のテーブル| + |2番目のテーブル|) アルゴリズムになります。最初のテーブルの各キーが 2 番目のテーブルの範囲に属しているかどうかを判断するために、両方の (並べ替えられた) リストへのポインターを保持し、それぞれを調べます。秘訣は、両方のリストがソートされているため、最初のテーブルのキーを調べるたびに 2 番目のリストを反復処理しないことです。

最も明白な SQL クエリ (キーが > key_lower_bound および < key_upper_bound であることを確認することを含む) を作成すると、非常に時間がかかります。

実際には、2番目のテーブルがkey_lower_boundsでソートされている場合、クエリエンジンは2番目のテーブルの各行に対して各比較を行っていると思うため、その単純なクエリで何らかの二次的な動作が行われている必要はありません。したがって、目的の O(|first table| + |second table|) の動作ではなく、O(|first table| x |second table|) のような動作を取得しています。

これを行うために線形 SQL クエリを取得することは可能ですか?

4

4 に答える 4

6

さて、私は問題で遊んで、いくつかの提案があります。しかし、最初にヘルパーテーブルを作成しましょう

CREATE TABLE dbo.Numbers(n INT NOT NULL PRIMARY KEY)
GO
DECLARE @i INT;
SET @i = 1;
INSERT INTO dbo.Numbers(n) SELECT 1;
WHILE @i<1024000 BEGIN
  INSERT INTO dbo.Numbers(n)
    SELECT n + @i FROM dbo.Numbers;
  SET @i = @i * 2;
END;
GO

およびテスト データ、1 年間の毎分 1 分間のコマーシャル、および同じ年の 1 分間に 1 回の顧客コール:

CREATE TABLE dbo.Commercials(
  StartedAt DATETIME NOT NULL 
    CONSTRAINT PK_Commercials PRIMARY KEY,
  EndedAt DATETIME NOT NULL,
  CommercialName VARCHAR(30) NOT NULL);
GO
INSERT INTO dbo.Commercials(StartedAt, EndedAt, CommercialName)
SELECT DATEADD(minute, n - 1, '20080101')
    ,DATEADD(minute, n, '20080101')
    ,'Show #'+CAST(n AS VARCHAR(6))
  FROM dbo.Numbers
  WHERE n<=24*365*60;
GO
CREATE TABLE dbo.Calls(CallID INT 
  CONSTRAINT PK_Calls NOT NULL PRIMARY KEY,
  AirTime DATETIME NOT NULL,
  SomeInfo CHAR(300));
GO
INSERT INTO dbo.Calls(CallID,
  AirTime,
  SomeInfo)
SELECT n 
    ,DATEADD(minute, n - 1, '20080101')
    ,'Call during Commercial #'+CAST(n AS VARCHAR(6))
  FROM dbo.Numbers
  WHERE n<=24*365*60;
GO
CREATE UNIQUE INDEX Calls_AirTime
  ON dbo.Calls(AirTime) INCLUDE(SomeInfo);
GO

年の半ばの 3 時間のコマーシャル中にかけられたすべての通話を選択する最初の試みは、非常に遅いです。

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO

SELECT COUNT(*) FROM(
SELECT s.StartedAt, s.EndedAt, c.AirTime
FROM dbo.Commercials s JOIN dbo.Calls c 
  ON c.AirTime >= s.StartedAt AND c.AirTime < s.EndedAt
WHERE c.AirTime BETWEEN '20080701' AND '20080701 03:00'
) AS t;

SQL Server parse and compile time: 
   CPU time = 15 ms, elapsed time = 30 ms.

(1 row(s) affected)
Table 'Calls'. Scan count 1, logical reads 11, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 3338264, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Commercials'. Scan count 2, logical reads 7166, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 71704 ms,  elapsed time = 36316 ms.

理由は簡単です。コマーシャルが重複しないことがわかっているため、1 つの呼び出しが多くても 1 つのコマーシャルに収まりますが、オプティマイザーはそれを知りません。コマーシャルが短いことはわかっていますが、オプティマイザーもそれを知りません。どちらの仮定も制約として適用できますが、オプティマイザーはそれを実行しません。

コマーシャルが 15 分以内であると仮定すると、オプティマイザーにそれを伝えることができ、クエリは非常に高速です。

SELECT COUNT(*) FROM(
SELECT s.StartedAt, s.EndedAt, c.AirTime
FROM dbo.Commercials s JOIN dbo.Calls c 
  ON c.AirTime >= s.StartedAt AND c.AirTime < s.EndedAt
WHERE c.AirTime BETWEEN '20080701' AND '20080701 03:00'
AND s.StartedAt BETWEEN '20080630 23:45' AND '20080701 03:00'
) AS t;

SQL Server parse and compile time: 
   CPU time = 15 ms, elapsed time = 15 ms.

(1 row(s) affected)
Table 'Worktable'. Scan count 1, logical reads 753, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Calls'. Scan count 1, logical reads 11, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Commercials'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 31 ms,  elapsed time = 24 ms.

コマーシャルが重複しないため、1 つの呼び出しが多くても 1 つのコマーシャルに収まると仮定すると、オプティマイザーにそれを伝えることができ、クエリは再び非常に高速になります。

SELECT COUNT(*) FROM(
SELECT s.StartedAt, s.EndedAt, c.AirTime
FROM dbo.Calls c CROSS APPLY(
  SELECT TOP 1 s.StartedAt, s.EndedAt FROM dbo.Commercials s 
  WHERE c.AirTime >= s.StartedAt AND c.AirTime < s.EndedAt
  ORDER BY s.StartedAt DESC) AS s
WHERE c.AirTime BETWEEN '20080701' AND '20080701 03:00'
) AS t;

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 7 ms.

(1 row(s) affected)
Table 'Commercials'. Scan count 181, logical reads 1327, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Calls'. Scan count 1, logical reads 11, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
   CPU time = 31 ms,  elapsed time = 31 ms.
于 2009-06-30T05:58:40.070 に答える
1

最初のテーブルでは、「キー」にクラスター化インデックスを配置します。2 番目のテーブルでは、"key_lower_bound" にクラスター化インデックスを配置します。それから私は試してみます:

select *
from FirstTable f
inner join SecondTable s 
    on f.key between s.key_lower_bound and s.key_upper_bound

次に、「key_upper_bound」に 2 つ目の非クラスター化インデックスを追加して、パフォーマンスが向上するかどうかを確認します。

于 2009-06-29T21:24:28.793 に答える
0

私の経験では、簡単で堅牢なソリューションはありません。私は、key_lower_bound と key_upper_bound を大きなテーブルにコピーし、外部キーを大きなテーブルから間隔のあるテーブルに参照させる、多くの同様のケースで非正規化をうまく使用しました。(key > key_lower_bound および key < key_upper_bound) を確認するためのチェック制約も作成しますが、このチェックは 1 つのテーブルの列のみを対象とするため、問題なく動作します。これは間違いなく非正規化ですが、FK 制約により、大きなテーブルの (key_lower_bound, key_upper_bound) が親テーブルの間隔と一致することが保証されるため、データが同期しなくなることはありません。結合が必要ないため、select は非常に高速に実行されます。

非正規化によって解決される同様の問題:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2009/03/08/storing-intervals-of-time-with-no-overlaps.aspx

完全な DDL が必要な場合はお知らせください。作成は非常に簡単です。

于 2009-06-30T03:01:03.857 に答える