Хорошо, позвольте мне повторить мое понимание вашего вопроса: вам нужна хранимая процедура, которая может принимать переменное число параметров и возвращать верхнюю строку, которая соответствует параметрам в весовом порядке предпочтения, передаваемому в SQL Server 2005.
В идеале он будет использовать предложения WHERE для предотвращения полных сканирований таблиц, а также использовать преимущества индексов и будет «замыкать» поиск - вы не хотите искать все возможные комбинации, если они могут быть найдены раньше. Возможно, мы также можем использовать другие компараторы, кроме =, например> = для дат, LIKE для строк и т. Д.
Один из возможных способов - передать параметры в виде XML, как в этой статье , и использовать хранимые процедуры .Net, но давайте пока оставим это простым T-SQL.
Это выглядит для меня как бинарный поиск по параметрам: поиск по всем параметрам, затем удаление последнего, затем второй, но последний, и т. Д.
Давайте передадим параметры в виде строки с разделителями, поскольку хранимые процедуры не позволяют передавать массивы в качестве параметров. Это позволит нам получить переменное число параметров в нашей хранимой процедуре, не требуя хранимой процедуры для каждого варианта параметров.
Чтобы разрешить какое-либо сравнение, мы пропустим весь список предложений WHERE, например, так: title вроде '%thing%'
Передача нескольких параметров означает их разделение в строке. Мы будем использовать символ тильды ~ для разграничения параметров, например: author = 'Chris Latta' ~ title наподобие '%thing%' ~ pages> = 100
Тогда это просто вопрос выполнения бинарного взвешенного поиска для первой строки, которая соответствует нашему упорядоченному списку параметров (надеюсь, хранимая процедура с комментариями не требует пояснений, но если нет, дайте мне знать). Обратите внимание, что вам всегда гарантирован результат (при условии, что в вашей таблице есть хотя бы одна строка), поскольку последний поиск не имеет параметров.
Вот код хранимой процедуры:
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.
Пара небольших проблем с этим:
- для вызывающего абонента необходимо знать, что они должны оставить пробел после имени поля для правильной работы параметра
- вы не можете иметь имена полей с пробелами в них - это можно исправить с некоторыми усилиями
- предполагается, что соответствующий порядок сортировки всегда возрастает
- следующий программист, который должен взглянуть на эту процедуру, подумает, что вы сошли с ума:)