2013-06-18 4 views
0

У меня есть поиск с тремя полями ввода (для аргументов, скажем, LastName, Last4Ssn и DateOfBirth). Эти три поля ввода находятся в динамической сетке, где пользователь может выбрать поиск одной или нескольких комбинаций этих трех полей. Например, пользователь может поиск на основе ниже представления:Как запросить с неизвестной комбинацией дополнительных параметров в SQL Server без курсоров?

LastName Last4Ssn DateOfBirth 
-------- -------- ----------- 
Smith  NULL  1/1/1970 
Smithers 1234  NULL 
NULL  5678  2/2/1980 

В примере, первая строка представляет собой поиск по ЬазШате и DateOfBirth. Второй, по LastName и Last4Ssn. И, в-третьих, по Last4Ssn и DateOfBirth. Этот пример немного надуман, поскольку в реальном мире сценарий имеет четыре поля. По крайней мере, два поля должны быть заполнены (не беспокойтесь о том, как проверять) с данными поиска, и возможно, что заполнены поля все поля.

Без использования курсоров, как использовать эти данные для соединения с существующими таблицами с использованием значений с учетом значений в каждой строке в качестве фильтра? В настоящее время у меня есть курсор, который проходит через каждую строку вышеприведенной таблицы, выполняет соединение на основе столбцов, имеющих значения, и вставляет найденные данные в временную таблицу. Что-то вроде этого:

CREATE TABLE #results (
    Id INT, 
    LastName VARCHAR (26), 
    Last4Ssn VARCHAR (4), 
    DateOfBirth DATETIME 
) 

DECLARE @lastName VARCHAR (26) 
DECLARE @last4Ssn VARCHAR (4) 
DECLARE @dateOfBirth DATETIME 

DECLARE search CURSOR FOR 
    SELECT LastName, Last4Ssn, DateOfBirth 
    FROM #searchData 

OPEN search 
    FETCH NEXT FROM search 
    INTO @lastName, @last4Ssn, @dateOfBirth 

    WHILE @@FETCH_STATUS = 0 
    BEGIN   
     INSERT INTO #results 
      SELECT s.Id, s.LastName, s.Last4Ssn, s.DateOfBirth 
      FROM SomeTable s 
      WHERE Last4Ssn = ISNULL(@last4Ssn, Last4Ssn) 
       AND DateOfBirth = ISNULL(@dateOfBirth, DateOfBirth) 
       AND (
        LastName = ISNULL(@lastName, LastName) 
        OR LastName LIKE @lastName + '%' 
       ) 
     FETCH NEXT FROM search 
     INTO @lastName, @last4Ssn, @dateOfBirth 
    END 
CLOSE search 
DEALLOCATE search 

Я надеялся, что там был какой-то способ, чтобы избежать курсор, чтобы сделать код немного более читаемым. Производительность не является проблемой, поскольку таблица, используемая для поиска, никогда не будет содержать более 5-10 записей, но я думаю, что для более чем нескольких было бы более эффективно запрашивать данные одновременно, а не один по очереди. Таблица SomeData в моем примере может быть очень большой.

ответ

1

Я не понимаю, почему вы не можете просто соединить две таблицы вместе:

CREATE TABLE #results (
    Id INT, 
    LastName VARCHAR (26), 
    Last4Ssn VARCHAR (4), 
    DateOfBirth DATETIME 
) 

INSERT INTO #results 
select s.id, s.lastname, s.last4ssn, s.dateofbirth 
from SomeTable s 
join #searchData d 
    ON s.last4ssn = isnull(d.last4ssn, s.last4ssn) 
    AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth) 
    AND (s.lastname = isnull(d.lastname, s.lastname) OR 
     OR s.lastname like d.lastname + '%') 

EDIT:

Поскольку данные велико, мы нужны хорошие показатели. Один индекс недостаточно хорош, так как вы фактически имеете 3 предложения OR'd вместе. Таким образом, первый шаг заключается в создании этих индексов:

CREATE TABLE SomeData (
    Id INT identity(1,1), 
    LastName VARCHAR (26), 
    Last4Ssn VARCHAR (4), 
    DateOfBirth DATETIME 
) 

create nonclustered index ssnlookup on somedata (last4ssn) 
create nonclustered index lastnamelookup on somedata (lastname) 
create nonclustered index doblookup on somedata (dateofbirth) 

Следующий шаг включает крафт запроса на использование этих индексов. Я не уверен, что лучший способ здесь, но я думаю, что это должны быть 4 запросов union'd вместе:

with searchbyssn as (
    select somedata.* from somedata join #searchData 
    on somedata.last4ssn = #searchData.last4ssn 
), searchbyexactlastname as (
    select somedata.* from somedata join #searchData 
    on somedata.lastname = #searchData.lastname 
), searchbystartlastname as (
    select somedata.* from somedata join #searchData 
    on somedata.lastname like #searchdata.lastname + '%' 
), searchbydob as (
    select somedata.* from somedata join #searchData 
    on somedata.dateofbirth = #searchData.dateofbirth 
), s as (
    select * from searchbyssn 
    union select * from searchbyexactlastname 
    union select * from searchbystartlastname 
    union select * from searchbydob 
) 
select s.id, s.lastname, s.last4ssn, s.dateofbirth 
from s 
join #searchData d 
    ON (d.last4ssn is null or s.last4ssn = d.last4ssn) 
    AND s.dateofbirth = isnull(d.dateofbirth, s.dateofbirth) 
    AND (s.lastname = isnull(d.lastname, s.lastname) 
     OR s.lastname like d.lastname + '%') 

Вот скрипка, показывающей индекс 4 изыскивает: http://sqlfiddle.com/#!6/3741d/3

Это показывает значительный ресурс использование для объединения, но я думаю, что это было бы незначительно по сравнению с индексами для больших таблиц. Это не позволило мне создать более нескольких сотен строк выборочных данных. Так как число строк результата невелико, то в конец #searchData в конце нет ничего дороже и снова фильтровать все результаты.

+0

То, что я начал делать, прежде чем вы ответили. Поскольку таблица 'SomeData' является большой, она занимает около 4 секунд для запуска и в настоящее время выполняет сканирование индекса, поскольку есть только один индекс, который, по-видимому, использует, который охватывает связанные с ним поля. – Sumo

+0

@sumo Я добавил новый запрос и 3 индекса, которые работают вместе, чтобы заставить SomeData получать доступ только к индексу. –

+0

Новый запрос, похоже, не отвечает исходным требованиям, поскольку исходный запрос использует 'AND' между необязательными параметрами. Используя соединение там, вы фактически делаете 'OR', который не даст правильных результатов. Запрос должен 'И' как минимум два из поставляемых параметров, поэтому его особенно сложно оптимизировать. – Sumo