T-SQL - Как написать сложное условное объединение, включая объединение «многие ко многим» - PullRequest
1 голос
/ 05 декабря 2009

Я попытался решить эту проблему, разместив здесь другие связанные вопросы, посвященные частям запроса. Однако я мог бы также опубликовать все это и посмотреть, если кто-нибудь может помочь. У меня есть следующие таблицы со следующими полями:

tblPerson - PersonID, PersonName
tblGroup - GroupID, Имя
tblGroupMembership - PersonID, GroupID
tblPersonCities - CityID, PersonID, City

Это довольно простая установка. У нас есть Персона и Группа, а членство в Группе - это соединение многих со многими. И тогда у людей может быть несколько городов.

Что я пытаюсь сделать, это написать хранимую процедуру для поиска этих данных, основанную на нескольких различных параметрах. Хранимая процедура будет возвращать только информацию о человеке. Хранимая процедура должна принимать 3 параметра:

@ PersonName - имя человека или часть имени человека
@GroupIDList - список идентификаторов групп, разделенных запятыми
@City - название города или часть названия города

Я бы хотел, чтобы хранимая процедура не требовала значений ни для одного из параметров. Таким образом, если все параметры были NULL, он должен возвращать все записи Person. Если передается список GroupID, то он должен возвращать только записи Person, которые соответствуют ВСЕМ группам в переданном списке. Я надеюсь, что объяснил это правильно. Я знаю, что это длинный вопрос, но я не могу объяснить это по-другому. У меня есть код, который почти работает. Единственная проблема заключается в том, что он не работает, если все параметры равны NULL. (И я не понял, как привезти в город) Вот мой пример кода. (ПРИМЕЧАНИЕ: fnSplit - это пользовательская функция, которая принимает строку с разделителями-запятыми и возвращает таблицу с другими значениями)

declare @name varchar(50)
declare @city varchar(50)
declare @grouplist varchar(50)

set @name = null
set @city = null
set @grouplist = null

select distinct
p.PersonID,
p.PersonName,
c.City
from
tblPerson p left join tblCities c on p.PersonID = c.PersonID
join
    (
       select m.PersonID
       from tblGroupMembership m
       where (m.GroupID in (select item from fnSplit(@grouplist, ',')))
       group by m.PersonID
       having (count(*) = (select count(*) from fnSplit(@grouplist, ',')))
    ) as filter
    on (@grouplist is not null) and (p.PersonID = filter.PersonID)
where
((@name is null) or (p.PersonName like '%' + @name + '%')) and
((@city is null) or (c.City like '%' + @city + '%'))

Ответы [ 3 ]

3 голосов
/ 05 декабря 2009

Я склоняюсь к использованию динамического sql в этих ситуациях, потому что использование OR s для размещения этой логики ужасно для производительности и управляемости. Следующий пример предназначен для SQL Server 2005 +:

DECLARE @SQL NVARCHAR(4000)

SET @SQL = 'SELECT DISTINCT
                   p.personid,
                   p.personname,
                   c.city
              FROM TBLPERSON p
         LEFT JOIN TBLCITIES c ON c.personid = p.personid '

SET @SQL = @SQL + CASE 
                    WHEN @grouplist IS NOT NULL THEN
                      ' JOIN (SELECT m.PersonID
                                FROM TBLGROUPMEMBERSHIP m
                               WHERE m.GroupID IN (SELECT item FROM fnSplit(@grouplist, ',')))
                            GROUP BY m.PersonID
                              HAVING COUNT(*) = (SELECT COUNT(*) FROM fnSplit(@grouplist, ',')))) g ON g.personid = p.personid '
                    ELSE
                      ' '
                  END

  SET @SQL = @SQL + ' WHERE 1 = 1 ' --trick to make contatentating WHERE clause easier

IF @name IS NOT NULL
  SET @SQL = @SQL + ' AND p.personname LIKE '%' + @name + '% '

IF @city IS NOT NULL
  SET @SQL = @SQL + ' AND c.city LIKE '%' + @city + '% '  

BEGIN

  EXEC sp_executesql @SQL N'@grouplist varchar(50), @grouplist varchar(50), @name varchar(50), @city varchar(50)',
                      @grouplist, @grouplist, @name, @city

END

Помните, что sp_executesql будет кэшировать план запроса - за Проклятие и благословения динамического SQL .

1 голос
/ 05 декабря 2009

попробуйте это:

Первая - это функция, которая превращает список групповых идентификаторов, разделенных запятыми, в табличную переменную ...

CREATE FUNCTION [dbo].[ParseString] (@S Text, @delim VarChar(5))
Returns @tOut Table 
    (ValNum Integer Identity Primary Key, 
     sVal VarChar(8000))
As
Begin 
Declare @dLLen TinyInt      -- Length of delimiter
Declare @sWin  VarChar(8000)-- Will Contain Window into text string
Declare @wLen  Integer      -- Length of Window
Declare @wLast TinyInt      -- Boolean to indicate processing Last Window
Declare @wPos  Integer      -- Start Position of Window within Text String
Declare @sVal  VarChar(8000)-- String Data to insert into output Table
Declare @BtchSiz Integer    -- Maximum Size of Window
    Set @BtchSiz = 7900     -- (Reset to smaller values to test routine)
Declare @dPos Integer       -- Position within Window of next Delimiter
Declare @Strt Integer       -- Start Position of each data value within Window
-- -------------------------------------------------------------------------

    -- ---------------------------
    If @delim is Null Set @delim = '|'
    If DataLength(@S) = 0 Or
        Substring(@S, 1, @BtchSiz) = @delim Return
    -- ---------------------------
    Select @dLLen = Len(@delim),
        @Strt = 1, @wPos = 1,
        @sWin = Substring(@S, 1, @BtchSiz)
    Select @wLen = Len(@sWin),
          @wLast = Case When Len(@sWin) = @BtchSiz
                    Then 0 Else 1 End,
          @dPos = CharIndex(@delim, @sWin, @Strt)
    -- ----------------------------
    While @Strt <= @wLen
        Begin
            If @dPos = 0 Begin    -- No More delimiters in window
                If @wLast = 1 Set @dPos = @wLen + 1 
                Else Begin
                    Set @wPos = @wPos + @Strt - 1
                    Set @sWin = Substring(@S, @wPos, @BtchSiz)
                    -- ----------------------------------------
                    Select @wLen = Len(@sWin), @Strt = 1,
                           @wLast = Case When Len(@sWin) = @BtchSiz
                                    Then 0 Else 1 End,
                           @dPos = CharIndex(@delim, @sWin, 1)
                    If @dPos = 0 Set @dPos = @wLen + 1 
                End
            End
            -- -------------------------------
            Set @sVal = LTrim(Substring(@sWin, @Strt, @dPos - @Strt))
            Insert @tOut (sVal) Values (@sVal)
            -- -------------------------------
            -- Move @Strt to char after last delimiter
            Set @Strt = @dPos + @dLLen 
            Set @dPos = CharIndex(@delim, @sWin, @Strt)
        End
    Return
End

Тогда вот хранимая процедура

  Create Procedure GetPersons
  @PersonName varChar(50) = nULL,
  @City varChar(50) = Null,
  @GroupIDList varChar(5000)
  As 
  Set NoCOunt On

        Declare @Groups Table (GId Integer Primary Key Not Null)
        If Len(@GroupIDList) = 0
           Insert @Groups(GId)
           Select GroupId From tblGroup
        Else
           Insert @Groups(GId)
           Select Cast(sVal as Integer)
           From dbo.ParseString(@GroupIDList, ',')

        Select PersonId, PersonName
        From tblPerson p 
        Where Exists (Select * From tblGroupMembership gm 
                         Join @Groups g On g.GId = gm.GroupId
                      Where PersonId = p.PersonId)
          And Exists (Select * From tblPersonCities 
                      Where PersonId = p.PersonId
                         And City =  IsNull(@City, City))
0 голосов
/ 05 декабря 2009

Когда вы говорите, что «любой параметр» имеет значение null, это включает в себя также @ grouplist ?

Если это так, это может быть связано с тем, что вы выполняете внутреннее соединение с таблицей filter . Если @ grouplist равен нулю, то не будет строк, которые могли бы встретить это объединение, так как я предполагаю, что fnSplit не будет возвращать строк, и поэтому оператор in никогда не будет истинным. Я просто смотрю на это с этой точки зрения ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...