さて、あなたの質問に対する私の理解をもう一度述べさせてください。可変数のパラメーターを取り、SQL Server 2005 で渡された優先度の重み付けされた順序でパラメーターに一致する一番上の行を返すことができるストアド プロシージャが必要です。
理想的には、WHERE 句を使用してテーブル全体のスキャンを防ぎ、インデックスを利用して検索を「短絡」します。可能な組み合わせが早期に見つかる場合は、すべての可能な組み合わせを検索したくありません。おそらく、日付の場合は >= 、文字列の場合は LIKE など、= 以外のコンパレータを許可することもできます。
考えられる 1 つの方法は、この記事のようにパラメーターを XML として渡し、.Net ストアド プロシージャを使用することですが、ここではプレーンなバニラ T-SQL のままにしておきます。
これは、パラメーターのバイナリ検索のように見えます。すべてのパラメーターを検索してから、最後のパラメーターを削除し、最後から 2 番目のパラメーターを削除しますが、最後のパラメーターを含めます。
ストアド プロシージャでは配列をパラメーターとして渡すことができないため、パラメーターを区切り文字列として渡しましょう。これにより、パラメータのバリエーションごとにストアド プロシージャを必要とせずに、可変数のパラメータをストアド プロシージャに取得できます。
あらゆる種類の比較を可能にするために、次のように WHERE 句リスト全体を渡します: title like '%something%'
複数のパラメーターを渡すということは、それらを文字列で区切ることを意味します。次のように、チルダ ~ 文字を使用してパラメーターを区切ります。 author = 'Chris Latta'~title like '%something%'~pages >= 100
次に、パラメーターの順序付けられたリストを満たす最初の行をバイナリ重み付け検索を実行するだけです (コメント付きのストアド プロシージャは一目瞭然ですが、そうでない場合はお知らせください)。最後の検索はパラメーターなしであるため、常に結果が保証されることに注意してください (テーブルに少なくとも 1 つの行があると仮定します)。
ストアド プロシージャのコードは次のとおりです。
CREATE PROCEDURE FirstMatch
@SearchParams VARCHAR(2000)
AS
BEGIN
DECLARE @SQLstmt NVARCHAR(2000)
DECLARE @WhereClause NVARCHAR(2000)
DECLARE @OrderByClause NVARCHAR(500)
DECLARE @NumParams INT
DECLARE @Pos INT
DECLARE @BinarySearch INT
DECLARE @Rows INT
-- Create a temporary table to store our parameters
CREATE TABLE #params
(
BitMask int, -- Uniquely identifying bit mask
FieldName VARCHAR(100), -- The field name for use in the ORDER BY clause
WhereClause VARCHAR(100) -- The bit to use in the WHERE clause
)
-- Temporary table identical to our result set (the books table) so intermediate results arent output
CREATE TABLE #junk
(
id INT,
author VARCHAR(50),
title VARCHAR(50),
printed DATETIME,
pages INT
)
-- Ill use tilde ~ as the delimiter that separates parameters
SET @SearchParams = LTRIM(RTRIM(@SearchParams))+ '~'
SET @Pos = CHARINDEX('~', @SearchParams, 1)
SET @NumParams = 0
-- Populate the #params table with the delimited parameters passed
IF REPLACE(@SearchParams, '~', '') <> ''
BEGIN
WHILE @Pos > 0
BEGIN
SET @NumParams = @NumParams + 1
SET @WhereClause = LTRIM(RTRIM(LEFT(@SearchParams, @Pos - 1)))
IF @WhereClause <> ''
BEGIN
-- This assumes your field names dont have spaces and that you leave a space between the field name and the comparator
INSERT INTO #params (BitMask, FieldName, WhereClause) VALUES (POWER(2, @NumParams - 1), LTRIM(RTRIM(LEFT(@WhereClause, CHARINDEX(' ', @WhereClause, 1) - 1))), @WhereClause)
END
SET @SearchParams = RIGHT(@SearchParams, LEN(@SearchParams) - @Pos)
SET @Pos = CHARINDEX('~', @SearchParams, 1)
END
END
-- Set the binary search to search from all parameters down to one in order of preference
SET @BinarySearch = POWER(2, @NumParams)
SET @Rows = 0
WHILE (@BinarySearch > 0) AND (@Rows = 0)
BEGIN
SET @BinarySearch = @BinarySearch - 1
SET @WhereClause = ' WHERE '
SET @OrderByClause = ' ORDER BY '
SELECT @OrderByClause = @OrderByClause + FieldName + ', ' FROM #params WHERE (@BinarySearch & BitMask) = BitMask ORDER BY BitMask
SET @OrderByClause = LEFT(@OrderByClause, LEN(@OrderByClause) - 1) -- Remove the trailing comma
SELECT @WhereClause = @WhereClause + WhereClause + ' AND ' FROM #params WHERE (@BinarySearch & BitMask) = BitMask ORDER BY BitMask
SET @WhereClause = LEFT(@WhereClause, LEN(@WhereClause) - 4) -- Remove the trailing AND
IF @BinarySearch = 0
BEGIN
-- If nothing found so far, return the top row in the order of the parameters fields
SET @WhereClause = ''
-- Use the full order sequence of fields to return the results
SET @OrderByClause = ' ORDER BY '
SELECT @OrderByClause = @OrderByClause + FieldName + ', ' FROM #params ORDER BY BitMask
SET @OrderByClause = LEFT(@OrderByClause, LEN(@OrderByClause) - 1) -- Remove the trailing comma
END
-- Find out if there are any results for this search
SET @SQLstmt = 'SELECT TOP 1 id, author, title, printed, pages INTO #junk FROM books' + @WhereClause + @OrderByClause
Exec (@SQLstmt)
SET @Rows = @@RowCount
END
-- Stop the result set being eaten by the junk table
SET @SQLstmt = REPLACE(@SQLstmt, 'INTO #junk ', '')
-- Uncomment the next line to see the SQL you are producing
--PRINT @SQLstmt
-- This gives the result set
Exec (@SQLstmt)
END
このストアド プロシージャは次のように呼び出されます。
FirstMatch 'author = ''Chris Latta''~pages > 100~title like ''%something%'''
これで、完全に拡張可能で最適化された、優先順位の重み付けされた上位の結果の検索ができました。これは興味深い問題であり、ネイティブ T-SQL で何ができるかを示しています。
これにはいくつかの小さな問題があります:
- パラメータが正しく機能するためには、フィールド名の後にスペースを残す必要があることを呼び出し元が認識している必要があります。
- スペースを含むフィールド名を使用することはできません-多少の努力で修正できます
- 関連するソート順は常に昇順であると想定しています
- この手順を見なければならない次のプログラマーは、あなたが正気ではないと思うでしょう:)