2009-12-05 6 views
1

J'ai essayé de résoudre ce problème en postant d'autres questions connexes ici qui se concentraient sur des parties de la requête. Cependant, je pourrais tout aussi bien afficher le tout et voir si quelqu'un peut aider. Je les tableaux ci-dessous avec les champs suivants:T-SQL - Comment écrire une jointure conditionnelle complexe incluant une jointure plusieurs-à-plusieurs

tblPerson - personID, NomPersonne
tblGroup - GroupID, Nom
tblGroupMembership - PersonID, GROUPID
tblPersonCities - CityID, personID, Ville

Il est un assez simple installer. Nous avons la personne et le groupe et le GroupMembership est un nombre de beaucoup à se joindre entre les deux. Et puis les gens peuvent avoir plusieurs villes.

Ce que j'essaie de faire est d'écrire une procédure stockée pour rechercher ces données basées sur quelques paramètres différents. La procédure stockée ne renvoie que des informations sur la personne. La procédure stockée doit prendre 3 paramètres:

@PersonName - nom ou d'une partie d'une personne du nom d'une personne
@GroupIDList - une liste délimitée par des virgules groupids
@city - un nom de ville ou d'une partie d'un nom de ville

Je souhaite que la procédure stockée ne puisse pas exiger de valeurs pour l'un des paramètres. Donc, si tous les paramètres étaient NULL, il devrait retourner tous les enregistrements Person. Si une liste d'ID de groupe est transmise, elle ne doit renvoyer que les enregistrements de personne qui correspondent à TOUS les groupes dans la liste transmise. J'espère avoir expliqué cela correctement. Je sais que c'est une longue question, mais je ne peux pas l'expliquer autrement. J'ai du code qui fonctionne presque. Le seul problème est que cela ne semble pas fonctionner si tous les paramètres sont NULL. (Et je n'ai pas compris comment amener dans la ville) Voici mon exemple de code. (REMARQUE: fnSplit est une fonction personnalisée qui prend une chaîne délimitée par des virgules et retourne une table avec les différentes valeurs)

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 + '%')) 

Répondre

3

Je me penche vers l'utilisation dynamique SQL dans ces situations, parce que l'utilisation de OR s pour accueillir cette logique est horrible pour la performance et la sargability. L'exemple suivant est destiné à 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 

esprit que sp_executesql se cache le plan de requête - par The curse and blessings of dynamic SQL.

+0

J'avais toujours supposé que SQL dynamique était mauvais parce qu'il ne serait pas mis en cache par le serveur et serait beaucoup plus lent. Je veux bien essayer. –

0

Quand vous dites « tout paramètre » est nulle, ce que cela comprend @grouplist ainsi?

Si tel est le cas, cela peut être dû au fait que vous effectuez une jointure interne au tableau du filtre. Si @grouplist est null, alors il n'y aura pas de lignes pour rencontrer cette jointure car je suppose que fnSplit ne renverra aucune ligne et donc l'instruction dans ne sera jamais vraie. Je regarde juste à ce point ...

+0

Oui, même @grouplist peut être NULL. Vous avez raison. C'est pourquoi je n'obtiens aucun résultat. Je me demandais s'il y avait un moyen d'ignorer complètement cette jointure si @grouplist était nul. Je ne connais pas assez SQL pour savoir si c'est possible. Si @grouplist est null alors ne faites pas cette jointure; Si a une valeur, alors faites la jointure. –

1

essayez ceci:

La première est une fonction de transformer la liste des groupids délimité par des virgules dans une variable de table ...

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 

Ensuite, voici la procédure stockée

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)) 
Questions connexes