1

ストアド プロシージャ内のテーブルで優先順位の一致を実行しようとしています。要件を説明するのは少し難しいですが、うまくいけばこれは理にかなっています。id、author、title、date、および pages フィールドを持つ、books というテーブルがあるとします。

また、クエリをテーブル内の 1 つの行に一致させるストアド プロシージャもあります。

プロシージャの署名は次のとおりです。

create procedure match
  @pAuthor varchar(100)
  ,@pTitle varchar(100)
  ,@pDate varchar(100)
  ,@pPages varchar(100)

 as

 ...

優先ルールは次のとおりです。

  • まず、4 つのパラメーターすべてを一致させてみます。一致するものが見つかった場合。
  • 次に、任意の 3 つのパラメーターを使用して一致を試みます。ここでは、1 番目のパラメーターの優先順位が最も高く、4 番目のパラメーターの優先順位が最も低くなります。一致が見つかった場合は、一致を返します。
  • 次に、2 つのパラメーターが一致するかどうかを確認し、最後にいずれかが一致するかどうかを確認します (パラメーターの順序の優先規則に従います)。

私はこれをケースバイケースで実装しました。例えば:

 select @lvId = id 
 from books
 where
  author = @pAuthor 
 ,title = @pTitle 
 ,date = @pDate 
 ,pages = @pPages

if @@rowCount = 1 begin
  select @lvId
  return
end

 select @lvId = id 
  from books
 where
  author = @pAuthor 
 ,title = @pTitle 
 ,date = @pDate 

 if @@rowCount = 1 begin
  select @lvId
  return
end

....

ただし、テーブルの新しい列ごとに、個々のチェックの数が 2 倍になります。これを X 個の列に一般化したいと思います。しかし、私はスキームを思いつくのに苦労しています。

読んでいただきありがとうございます。必要な追加情報を提供できます。


追加した:

Dave and Others、私はあなたのコードを実装しようとしましたが、すべてのカウントを追加する最初の Order by Clause で窒息しています。無効な列名エラーが表示されます。合計数をコメントアウトし、個々のエイリアスだけで並べ替えると、proc は正常にコンパイルされます。

誰にもアイデアはありますか?

これはMicrosoft Sql Server 2005にあります

4

7 に答える 7

2

あなたが取り組んでいる答えは、これまでで最も単純なものだと思います。しかし、SQL サーバーでは、常にフル テーブル スキャンになると思います。(IN Oracle では、テーブルが多数の同時 DML を受けていない場合、ビットマップ インデックスを使用できます)

より複雑なソリューションですが、はるかにパフォーマンスの高いソリューションは、独自のインデックスを作成することです。SQL Server インデックスではなく、独自のものです。

3 つの列 (ルックアップ ハッシュ、ランク、行 ID) を持つテーブル (ハッシュ インデックス) を作成します。

検索する列が 3 つあるとします。A、B、C

Books に行を追加するたびに、トリガーまたは CRUD プロシージャを介して、hash_index に 7 行を挿入します。

まず、

insert into hash_index 
SELECT HASH(A & B & C), 7 , ROWID
FROM Books

& は連結演算子、HASH は関数です。

次に、A & B、A & C、および B & C のハッシュを挿入します。これで、ある程度の柔軟性が得られ、それらすべてに同じランクを与えることができます。または、A & B が B & C よりも優れている場合は、それらに a を与えることができます。より高いランク。

そして、A のハッシュを単独で挿入し、B と C を同じランクの選択で挿入します...すべて同じ数またはすべて異なる... A での一致は、B と C での一致よりも高い選択であるとさえ言えます。 . このソリューションにより、多くの柔軟性が得られます。

もちろん、これにより多くの INSERT オーバーヘッドが追加されますが、Books の DML が低い場合やパフォーマンスが関係ない場合は問題ありません。

検索に行くと、@A、@B、および @C の HASH のテーブルを返す関数を作成します。ハッシュ インデックス テーブルのルックアップ ハッシュに結合する 7 つの値の小さなテーブルができます。これにより、可能なすべての一致と、場合によってはいくつかの誤った一致が得られます (これはハッシュの性質です)。その結果、ランク列の順序の説明を取得します。次に、最初の行 ID を book テーブルに戻し、@A @B @C のすべての値が実際にその行にあることを確認します。そうではなく、誤検知が発生した場合は、次の行 ID を確認する必要があります。

この「独自のロール」でのこれらの各操作はすべて非常に高速です。

  • 3 つの値を小さな 7 行のテーブル変数にハッシュする = 非常に高速です。
  • Hash_index テーブルのインデックスにそれらを結合する = 非常に高速なインデックス検索
  • 結果セットをループすると、ROWID による 1 つまたは 2 つまたは 3 つのテーブル アクセスが発生する = 非常に高速

もちろん、これらすべてを合わせると、FTS よりも遅くなる可能性があります... しかし、FTS はますます遅くなり続けます。これよりFTSが遅いサイズもあるでしょう。あなたはそれで遊ぶ必要があります。

于 2008-12-23T21:23:48.433 に答える
1

複数の結果が到達した特定のパラメーターのセットに一致する場合に何が起こるかを説明していないため、これらのビジネス ルールを考慮してこれを変更する必要があります。現在、後のパラメーターで一致する本を、そうでない本よりも先に返すように設定しました。たとえば、著者、タイトル、およびページの一致は、著者とタイトルのみの一致よりも前になります。

RDBMS では "TOP" の処理方法が異なる場合があるため、その調整も必要になる場合があります。

SELECT TOP 1
     author,
     title,
     date,
     pages
FROM
     Books
WHERE
     author = @author OR
     title = @title OR
     date = @date OR
     pages = @pages OR
ORDER BY
     CASE WHEN author = @author THEN 1 ELSE 0 END +
     CASE WHEN title = @title THEN 1 ELSE 0 END +
     CASE WHEN date = @date THEN 1 ELSE 0 END +
     CASE WHEN pages = @pages THEN 1 ELSE 0 END DESC,

     CASE WHEN author = @author THEN 8 ELSE 0 END +
     CASE WHEN title = @title THEN 4 ELSE 0 END +
     CASE WHEN date = @date THEN 2 ELSE 0 END +
     CASE WHEN pages = @pages THEN 1 ELSE 0 END DESC
于 2008-12-22T19:58:23.443 に答える
1

クエリを書き出す時間はありませんが、このアイデアはうまくいくと思います。

述語には、「author = @pAuthor OR title = @ptitle ...」を使用して、すべての候補行を取得します。

次のように、CASE 式などを使用して、結果セットに仮想列を作成します。

SELECT CASE WHEN author = @pAuthor THEN 1 ELSE 0 END author_match,
       ...

次に、この order by を追加して、返される最初の行を取得します。

ORDER BY (author_match+title_match+date_match+page_match) DESC,
         author_match DESC,
         title_match DESC,
         date_match DESC
         page_match DESC

新しい列ごとに拡張する必要がありますが、少しだけです。

于 2008-12-22T19:58:33.067 に答える
0
      select id, 
               CASE WHEN @pPages = pages 
                    THEN 1 ELSE 0 
               END
             +  Case WHEN @pAuthor=author 
                    THEN 1 ELSE 0 
                END AS 
             /* +  Do this for each attribute. If each of your 
attributes are just as important as the other 
for example matching author is jsut as a good as matching title then 
leave the values alone, if different matches are more 
important then change the values */ as MatchRank  
        from books 

        where  author = @pAuthor OR
               title = @pTitle OR
               date = @pDate

     ORDER BY  MatchRank DESC

編集済み

このクエリ (自分のテーブルの 1 つに適合するようにのみ変更) を実行すると、SQL2005 で正常に動作します。

where句をお勧めしますが、これをいじってパフォーマンスへの影響を確認したいと思うでしょう。OR句を使用する必要があります。そうしないと、潜在的な一致が失われます

于 2008-12-22T20:02:48.177 に答える
0

さて、あなたの質問に対する私の理解をもう一度述べさせてください。可変数のパラメーターを取り、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 で何ができるかを示しています。

これにはいくつかの小さな問題があります:

  • パラメータが正しく機能するためには、フィールド名の後にスペースを残す必要があることを呼び出し元が認識している必要があります。
  • スペースを含むフィールド名を使用することはできません-多少の努力で修正できます
  • 関連するソート順は常に昇順であると想定しています
  • この手順を見なければならない次のプログラマーは、あなたが正気ではないと思うでしょう:)
于 2008-12-23T07:53:04.403 に答える
0

コンパイルに失敗する Order By 句に関して:

recursive が (コメントで) 述べたように、alias' は Order By 句で使用される式内にない場合があります。これを回避するために、行を返すサブクエリを使用し、外側のクエリで並べ替えました。このようにして、order by 句でエイリアスを使用できます。少し遅くなりますが、よりクリーンになります。

于 2008-12-23T13:41:24.513 に答える
0

これを試して:

ALTER PROCEDURE match  
  @pAuthor varchar(100)  
 ,@pTitle varchar(100)  
 ,@pDate varchar(100)  
 ,@pPages varchar(100)  
-- exec match 'a title', 'b author', '1/1/2007', 15  
AS

SELECT  id,

        CASE WHEN author = @pAuthor THEN 1 ELSE 0 END
        + CASE WHEN title = @pTitle THEN 1 ELSE 0 END
        + CASE WHEN bookdate = @pDate THEN 1 ELSE 0 END
        + CASE WHEN pages = @pPages THEN 1 ELSE 0 END AS matches,

        CASE WHEN author = @pAuthor THEN 4 ELSE 0 END
        + CASE WHEN title = @pTitle THEN 3 ELSE 0 END
        + CASE WHEN bookdate = @pDate THEN 2 ELSE 0 END
        + CASE WHEN pages = @pPages THEN 1 ELSE 0 END AS score
FROM books
WHERE author = #pAuthor 
    OR title = @pTitle 
    OR bookdate = @PDate 
    OR pages = @pPages
ORDER BY matches DESC, score DESC

ただし、もちろん、これによりテーブル スキャンが発生します。これを回避するには、CTE と 4 つの WHERE 句の結合を各プロパティに 1 つずつ作成します。重複はありますが、とにかく TOP 1 を取得できます。

編集: WHERE ... OR 句を追加しました。だったらもっと楽なのに

SELECT ... FROM books WHERE author = @pAuthor
UNION
SELECT ... FROM books WHERE title = @pTitle
UNION
...
于 2008-12-23T08:58:29.297 に答える