Puis-je utiliser l'instruction CASE dans une condition JOIN?

141

L'image suivante fait partie des vues système de Microsoft SQL Server 2008 R2. De l'image, nous pouvons voir que la relation entre sys.partitionset sys.allocation_unitsdépend de la valeur de sys.allocation_units.type. Donc, pour les réunir, j'écrirais quelque chose de similaire à ceci:

SELECT  *
FROM    sys.indexes i
        JOIN sys.partitions p
            ON i.index_id = p.index_id 
        JOIN sys.allocation_units a
            ON CASE
               WHEN a.type IN (1, 3)
                   THEN a.container_id = p.hobt_id 
               WHEN a.type IN (2)
                   THEN a.container_id = p.partition_id
               END 

Mais le code supérieur donne une erreur de syntaxe. Je suppose que c'est à cause de la CASEdéclaration. Quelqu'un peut-il aider à expliquer un peu?


Ajouter un message d'erreur:

Msg 102, niveau 15, état 1, ligne 6 Syntaxe incorrecte près de '='.

c'est l'image

Juste un apprenant
la source
36
Quel logiciel avez-vous utilisé pour créer ce magnifique diagramme DB?
LearnByReading
2
@LearnByReading avez-vous déjà découvert quel logiciel était utilisé?
User632716
1
@ User632716 non malheureusement pas!
LearnByReading
2
@ User632716 Bien que je pense vraiment que c'était MySQL Workbench. Mais je n'ai jamais reçu de réponse
LearnByReading
@LearnByReading Je n'ai aucune idée. Il est fourni par Microsoft.
Juste un apprenant le

Réponses:

223

Une CASEexpression renvoie une valeur de la THENpartie de la clause. Vous pouvez l'utiliser ainsi:

SELECT  * 
FROM    sys.indexes i 
    JOIN sys.partitions p 
        ON i.index_id = p.index_id  
    JOIN sys.allocation_units a 
        ON CASE 
           WHEN a.type IN (1, 3) AND a.container_id = p.hobt_id THEN 1
           WHEN a.type IN (2) AND a.container_id = p.partition_id THEN 1
           ELSE 0
           END = 1

Notez que vous devez faire quelque chose avec la valeur retournée, par exemple la comparer à 1. Votre instruction a tenté de renvoyer la valeur d'une affectation ou d'un test d'égalité, aucun des deux n'a de sens dans le contexte d'une clause CASE/ THEN. (Si BOOLEANétait un type de données, alors le test d'égalité aurait du sens.)

HABO
la source
@HABO merci qui a fonctionné pour moi ... mais le problème est que lorsque je fais cela, les conditions tombent ... s'il vous plaît dites-moi comment puis-je le briser?
Sagar Tandel
1
@SagarTandel - Désolé, je ne comprends pas "faire une chute" et "comment puis-je le casser". Pouvez-vous clarifier votre commentaire? (Récemment fait surface après une plongée au large de Saba. Peut-être le Nitrox.)
HABO
Il vérifie toutes les conditions dont je ne veux pas. Je veux qu'il quitte le cas une fois qu'il correspond à une condition.
Sagar Tandel
3
@SagarTandel - De MSDN : "L'instruction CASE évalue ses conditions de manière séquentielle et s'arrête avec la première condition dont la condition est satisfaite.". Si vous voulez que toutes les lignes jointes ne correspondent à aucune des conditions explicitement énoncées, changez simplement la fin de la fin de = 1à = 0, mais je ne pense pas que vous aimerez le résultat.
HABO
JOIN sys.allocation_units a ON CASE WHEN a.type IN (1, 3) AND a.container_id = p.hobt_id THEN 1 WHEN a.type IN (2) AND a.container_id = p.partition_id THEN 1 ELSE 0 END = 1 pourriez-vous l'écrire dans le cadre d'entité ci-dessus de votre solution
r.hamd
36

Au lieu de cela, vous rejoignez simplement les deux tables et, dans votre clause SELECT, retournez les données de celle qui correspond:

Je vous suggère de passer par ce lien Jointures conditionnelles dans SQL Server et T-SQL Case Statement dans une clause JOIN ON

par exemple

    SELECT  *
FROM    sys.indexes i
        JOIN sys.partitions p
            ON i.index_id = p.index_id 
        JOIN sys.allocation_units a
            ON a.container_id =
            CASE
               WHEN a.type IN (1, 3)
                   THEN  p.hobt_id 
               WHEN a.type IN (2)
                   THEN p.partition_id
               END 

Edit: selon les commentaires.

Vous ne pouvez pas spécifier la condition de jointure comme vous le faites .. Vérifiez la requête ci-dessus qui n'a pas d'erreur. J'ai retiré la colonne commune et la valeur de la colonne de droite sera évaluée sous condition.

Niranjan Singh
la source
1
que conditional joinsignifie? Chaque jointure (hors croisement) est une condition. En quoi ce cas est-il différent des autres? Votre exemple a une jointure interne avec une condition, et une requête OP a une jointure avec une condition.
zerkms
@zerkms: Je suis d'accord, cela semble déroutant. Je crois qu'une jointure conditionnelle dans ce contexte signifie une jointure dont la condition dépend d'une autre condition.
Andriy M
@Andriy M: une raison de penser à un autre terme pour une condition triviale avec OR?
zerkms
@zerkms: Euh, oui, c'est parce que cela semble déroutant. :) Mais je crois que vous vouliez demander s'il y avait une raison de ne pas penser à un autre terme, auquel cas je ne peux pas être sûr. Si vous demandez mes raisons, eh bien, je pense que je ne pourrais pas être assez dérangé. :) Que diriez-vous d'une jointure avec des conditions alternatives ? J'ai peur de ne pas être très doué pour créer des termes. Notez cependant que, conceptuellement, il s'agit de "condition conditionnelle" et non de "condition avec OR". L'utilisation ORn'est qu'une façon de le mettre en œuvre.
Andriy M
@Andriy M: d'accord. Mais personnellement, je ne vois toujours pas la raison de donner un nom à la condition triviale avec 1 ORet 2 ANDsou avec 1 CASE. C'est une requête de routine qui n'a rien qui la diffère des autres.
zerkms
17

Essaye ça:

...JOIN sys.allocation_units a ON 
  (a.type=2 AND a.container_id = p.partition_id)
  OR (a.type IN (1, 3) AND a.container_id = p.hobt_id)
richardtallent
la source
Même si cela fonctionnerait, la requête de la question semble parfaitement valide. Donc, n'explique toujours pas ce qui ne va pas avec le code d'OP
zerkms
10

Je pense que vous avez besoin de deux déclarations de cas:

SELECT  *
FROM    sys.indexes i
    JOIN sys.partitions p
        ON i.index_id = p.index_id 
    JOIN sys.allocation_units a
        ON 
        -- left side of join on statement
            CASE
               WHEN a.type IN (1, 3)
                   THEN a.container_id
               WHEN a.type IN (2)
                   THEN a.container_id
            END 
        = 
        -- right side of join on statement
            CASE
               WHEN a.type IN (1, 3)
                   THEN p.hobt_id
               WHEN a.type IN (2)
                   THEN p.partition_id
            END             

Ceci est dû au fait:

  • l'instruction CASE renvoie une valeur unique à END
  • l'instruction ON compare deux valeurs
  • votre instruction CASE faisait la comparaison à l' intérieur de l'instruction CASE. Je suppose que si vous mettez votre instruction CASE dans votre SELECT, vous obtiendrez un booléen '1' ou '0' indiquant si l'instruction CASE évaluée à True ou False
Donkey Kong
la source
5

J'ai pris votre exemple et je l'ai édité:

SELECT  *
FROM    sys.indexes i
    JOIN sys.partitions p
        ON i.index_id = p.index_id 
    JOIN sys.allocation_units a
        ON a.container_id = (CASE
           WHEN a.type IN (1, 3)
               THEN p.hobt_id 
           WHEN a.type IN (2)
               THEN p.partition_id
           ELSE NULL
           END)
Gont
la source
1

Cela semble sympa

https://bytes.com/topic/sql-server/answers/881862-joining-different-tables-based-condition

FROM YourMainTable
LEFT JOIN AirportCity DepCity ON @TravelType = 'A' and DepFrom =  DepCity.Code
LEFT JOIN AirportCity DepCity ON @TravelType = 'B' and SomeOtherColumn = SomeOtherColumnFromSomeOtherTable

la source
En essayant cela, j'obtiens une erreur: le nom de corrélation «xx» est spécifié plusieurs fois dans une clause FROM.
Etienne
1

Oui, vous pouvez. Voici un exemple.

SELECT a.*
FROM TableA a
LEFT OUTER JOIN TableB j1 ON  (CASE WHEN LEN(COALESCE(a.NoBatiment, '')) = 3 
                                THEN RTRIM(a.NoBatiment) + '0' 
                                ELSE a.NoBatiment END ) = j1.ColumnName 
Stefan Gabor
la source
1
Bien que ce code puisse résoudre la question, inclure une explication sur comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre publication et entraînerait probablement plus de votes à la hausse. N'oubliez pas que vous répondez à la question des lecteurs à l'avenir, pas seulement à la personne qui la pose maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limites et des hypothèses applicables.
double bip
0

Prenons l'exemple de DonkeyKong.

Le problème est que je devais utiliser une variable déclarée. Cela permet d'indiquer votre côté gauche et droit de ce que vous devez comparer. Il s'agit de prendre en charge un rapport SSRS dans lequel différents champs doivent être liés en fonction de la sélection par l'utilisateur.

Le cas initial définit le choix du champ en fonction de la sélection, puis je peux définir le champ que je dois associer pour la jointure.

Une deuxième déclaration de cas pourrait être ajoutée pour le côté droit si la variable est nécessaire pour choisir parmi différents champs

LEFT OUTER JOIN Dashboard_Group_Level_Matching ON
       case
         when @Level  = 'lvl1' then  cw.Lvl1
         when @Level  = 'lvl2' then  cw.Lvl2
         when @Level  = 'lvl3' then  cw.Lvl3
       end
    = Dashboard_Group_Level_Matching.Dashboard_Level_Name
Kenneth Wilson
la source
0

Ici, j'ai comparé la différence entre deux ensembles de résultats différents:

SELECT main.ColumnName, compare.Value PreviousValue,  main.Value CurrentValue
FROM 
(
    SELECT 'Name' AS ColumnName, 'John' as Value UNION ALL
    SELECT 'UserName' AS ColumnName, 'jh001' as Value UNION ALL
    SELECT 'Department' AS ColumnName, 'HR' as Value UNION ALL
    SELECT 'Phone' AS ColumnName, NULL as Value UNION ALL
    SELECT 'DOB' AS ColumnName, '1993-01-01' as Value UNION ALL
    SELECT 'CreateDate' AS ColumnName, '2017-01-01' as Value UNION ALL
    SELECT 'IsActive' AS ColumnName, '1' as Value
) main
INNER JOIN
(
    SELECT 'Name' AS ColumnName, 'Rahul' as Value UNION ALL
    SELECT 'UserName' AS ColumnName, 'rh001' as Value UNION ALL
    SELECT 'Department' AS ColumnName, 'HR' as Value UNION ALL
    SELECT 'Phone' AS ColumnName, '01722112233' as Value UNION ALL
    SELECT 'DOB' AS ColumnName, '1993-01-01' as Value UNION ALL
    SELECT 'CreateDate' AS ColumnName, '2017-01-01' as Value UNION ALL
    SELECT 'IsActive' AS ColumnName, '1' as Value
) compare
ON main.ColumnName = compare.ColumnName AND
CASE 
    WHEN main.Value IS NULL AND compare.Value IS NULL THEN 0
    WHEN main.Value IS NULL AND compare.Value IS NOT NULL THEN 1
    WHEN main.Value IS NOT NULL AND compare.Value IS NULL THEN 1
    WHEN main.Value <> compare.Value THEN 1
END = 1 
Cheikh Kawser
la source