2009-07-09 8 views
2

Pour une structure de table assez simple, c.-à-d. Person, Criteria, et PersonCriteria (la combi-table), j'ai mis en place une requête à l'instant qui sélectionne toutes les personnes qui possèdent tous les critères sélectionnés.Requête avancée (?) AND/OR

La requête elle-même ressemble à ceci au moment:

SELECT 
    p.PersonID 
FROM 
    Person p,  
    (SELECT DISTINCT PersonID, CriteriaID 
    FROM PersonCriteria 
    WHERE CriteriaID in (#list_of_ids#)  
) k  
WHERE 
    p.PersonID= k.PersonID  
GROUP BY 
    p.PersonID  
HAVING 
    Count(*) = #Listlength of list_of_ids# 

Jusqu'à présent, aucun problème et tout fonctionne bien.

Maintenant, je veux offrir la possibilité à l'utilisateur d'ajouter des variables AND et OR dans leur recherche, à savoir. quelqu'un pourrait dire:

Je cherche une personne qui possède: Critère 1 ET 3 ET 4 (qui serait couvert par la requête ci-dessus) et (5 ou 6 ou 7) et (8 ou 9) et ainsi de suite ...

Je ne sais pas par où commencer avec ce niveau supplémentaire. J'espère que quelqu'un d'autre fait .. :-)

Répondre

2

Je dois dire - je suis perplexe. Je ne peux pas penser à une solution qui serait proche. J'essaierais de chercher une solution dans ces directions:

  • Fonctions d'agrégat définies par l'utilisateur. Peut-être que vous pouvez faire une fonction qui prend comme argument l'expression désirée (dans une syntaxe simplifiée) et les lignes pour une seule personne. La fonction analyse ensuite l'expression et la compare aux lignes. Hmm ... peut-être que MySQL inclut une fonction d'agrégation de concaténation et une fonction de correspondance de regex? Cela pourrait être une solution (mais probablement pas très rapide).
  • Fonctions analytiques. Je ne prétends pas les comprendre, mais autant que je sache, je pense qu'ils vont généralement dans cette direction. Bien que je ne sais pas s'il y aura une fonction qui répondra à ce besoin.

Ajouté: Ahh, je pense que je l'ai eu! Bien que je pense que la performance sera misérable. Mais cela va fonctionner! Par exemple, si vous avez l'obligation de rechercher 1 AND 2 AND (3 OR 4) vous écririez:

SELECT 
    * 
FROM 
    Persons A 
WHERE 
    EXISTS (Select * from PersonCriteria B WHERE A.PersonID=B.PersonID AND CriteriaID=1) 
    AND 
    EXISTS (Select * from PersonCriteria B WHERE A.PersonID=B.PersonID AND CriteriaID=2) 
    AND 
    (
     EXISTS (Select * from PersonCriteria B WHERE A.PersonID=B.PersonID AND CriteriaID=3) 
     OR 
     EXISTS (Select * from PersonCriteria B WHERE A.PersonID=B.PersonID AND CriteriaID=4) 
    ) 

Ajouté 2: Voici un autre, bien que la performance sera probablement encore pire:

SELECT p.* FROM Person p 
    JOIN (select PersonID from PersonCriteria WHERE CriteriaID=1) c1 ON p.PersonID=c1.PersonID 
    JOIN (select PersonID from PersonCriteria WHERE CriteriaID=2) c2 ON p.PersonID=c2.PersonID 
    JOIN (select PersonID from PersonCriteria WHERE CriteriaID IN (3,4)) c3 ON p.PersonID=c3.PersonID 

Ajouté 3: Ceci est une variation de n ° 2, mais cela pourrait en fait avoir une chance d'une performance décente!

SELECT p.* FROM 
    Person p 
    JOIN PersonCriteria c1 on (p.PersonID=c1.PersonID AND c1.CriteriaID=1) 
    JOIN PersonCriteria c2 on (p.PersonID=c2.PersonID AND c2.CriteriaID=2) 
    JOIN PersonCriteria c3 on (p.PersonID=c3.PersonID AND c3.CriteriaID IN (3,4)) 

Si vous ajoutez un index PersonCriteria sur des colonnes (PersonID, IdCritères) (exactement dans cet ordre!), Je pense qu'il est à peu près aussi vite que vous allez obtenir dans tous les cas.

+0

Votre numéro "3" fait bien l'affaire. J'ai dû ajouter Distinct au select, et la performance ne semble pas être trop mauvaise du tout. Merci! –

+0

Oui, j'ai oublié la partie DISTINCT. :) –

1

Vous pouvez simplifier beaucoup, par exemple en faisant:

SELECT DISTINCT personID DE PersonCriteria OÙ IdCritères IN (1,2) OU IdCritères IN (8,9)

Pensez également à utiliser rejoignons au lieu de sous-requêtes (pour la performance)

+0

Je pense que vous avez manqué le point. Relisez et réfléchissez bien ... –

1

Je comprends ce que vous demandez cela devrait fonctionner. Je ne garantis pas que je comprends ce que vous demandez, car il est clair que plusieurs personnes ont déjà eu différentes interprétations.

SELECT p.PersonID 
FROM Person p 
JOIN  
(SELECT DISTINCT PersonID  
FROM PersonCriteria  
WHERE CriteriaID in (1,2,3) and count(criteriaID) = 3) k 
     on p.PersonID = k.PersonID 
JOIN 
    (SELECT DISTINCT PersonID  
FROM PersonCriteria  
WHERE CriteriaID in (4,5)) k2 on p.PersonID = k2.PersonID 
JOIN 
    (SELECT DISTINCT PersonID  
FROM PersonCriteria  
WHERE CriteriaID in (5,6,7)) k3 on p.PersonID = k3.PersonID 
JOIN 
    (SELECT DISTINCT PersonID  
FROM PersonCriteria  
WHERE CriteriaID in (8,9)) k4 on p.PersonID = k4.PersonID 

La façon dont j'interprète cela. la première jointure est à une table dérivée qui obtient n'importe qui qui a les trois conditions spécifiées. Alors que les tables dérivées suivantes trouvent les personnes qui remplissent l'une de ces conditions (essentiellement la partie OU) en se joignant au reste des tables dérivées, nous accomplissons la partie ET de la requête. Je sais également que la syntaxe passe la vérification de syntaxe pour SQL Server, il pourrait avoir besoin de peaufiner pour MYSQL.

0

Si vous avez besoin d'une approche plus "dynamique" pour rechercher vos données, le SQL deviendra vraiment moche et long et ne sera pas vraiment complètement dynamique et ... ai-je mentionné "moche"? J'utilise les frameworks ORM pour la tâche, et ils gèrent parfaitement le travail.

Mais si votre structure est seulement comme vous avez décrit (avec ou emballage nombreux et conditions), puis en supposant que votre position actuelle et seule la mise en œuvre est en une UDF appelée dbo.getPersonForAndCriteria(...), vous pouvez mettre en œuvre ou à l'aide simplement UNION:

dbo.getPersonForAndCriteria(@myListOfIDs1) --// works for AND 
UNION -- replaces OR 
dbo.getPersonForAndCriteria(@myListOfIDs2) --// works for AND 
UNION -- replaces OR 
dbo.getPersonForAndCriteria(@myListOfIDs3) --// works for AND 

Remarque: ceci est uniquement illustratif, mais j'embaquerais votre procédure dans un fichier UDF sympa qui prend la liste des paramètres (ID) sous la forme d'une table (XML ou chaîne délimitée par des virgules, analysée dans le fichier UDF) , puis faites juste une belle JOIN sur cette liste/table au lieu de WHERE ... IN partie, et la dernière partie devient COUNT (*) = COUNT (SELECT ID FROM myFilterTable).

Questions connexes