Comment avoir plus de 100 entrées dans l'instruction case en tant que variable

11

J'ai écrit une déclaration de cas avec> 100 choix où j'utilise la même déclaration à 4 endroits dans une simple requête.

La même requête deux fois avec une union entre eux mais effectue également un comptage et donc le groupe par contient également l'instruction case.

Il s'agit de réétiqueter certains noms de société où différents enregistrements pour la même société sont orthographiés différemment.

J'ai essayé de déclarer une variable comme VarChar (MAX)

declare @CaseForAccountConsolidation varchar(max)

SET @CaseForAccountConsolidation = 'CASE 
       WHEN ac.accountName like ''AIR NEW Z%'' THEN ''AIR NEW ZEALAND''
       WHEN ac.accountName LIKE ''AIR BP%'' THEN ''AIR BP''
       WHEN ac.accountName LIKE ''ADDICTION ADVICE%'' THEN ''ADDICTION ADVICE''
       WHEN ac.accountName LIKE ''AIA%'' THEN ''AIA''
       ...

Lorsque je suis allé l'utiliser dans mon instruction select - la requête a simplement renvoyé l'instruction case sous forme de texte et ne l'a pas évaluée.

J'ai également été incapable de l'utiliser dans le groupe par - j'ai reçu ce message d'erreur:

Each GROUP BY expression must contain at least one column that is not an outer reference.

Idéalement, j'aimerais avoir le CASE dans un seul endroit - afin qu'il n'y ait aucune chance que je mette à jour une ligne et que je ne le reproduise pas ailleurs.

Y a-t-il un moyen de le faire?

Je suis ouvert à d'autres façons (comme peut-être une fonction - mais je ne sais pas comment les utiliser comme ça)

Voici un échantillon du SELECT que j'utilise actuellement

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
WHERE a.datecreated = CONVERT(DATE,now())
GROUP BY
   dl.FirstDateOfMonth
   ,dl.FinancialYear
   ,dl.FirstDateOfWeek
   ,CONVERT(Date,c.date_charged)
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END

UNION

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
WHERE a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))
GROUP BY
   dl.FirstDateOfMonth
   ,dl.FinancialYear
   ,dl.FirstDateOfWeek
   ,CONVERT(Date,c.date_charged)
   ,CASE 
       WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
       WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
       WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
       WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       ELSE ac.accountName
   END

Le but de cette UNION est de renvoyer toutes les données pour une période de temps, et AUSSI de retourner les données pour la même période de 12 mois auparavant

EDIT: Ajout d'un "CATCH-ALL"
manquant EDIT2: Ajout d'un deuxième ½ de la déclaration UNION
EDIT3: Correction du GROUP BY pour inclure d'autres éléments nécessaires

kiltannen
la source
En quoi les 2 parties de l'UNION diffèrent-elles? Ils semblent assez similaires, à l'exception des conditions O WH légèrement différentes.
ypercubeᵀᴹ
Voilà la principale différence. Les deux conditions OERE différentes à la date donnent aujourd'hui et la même date il y a 12 mois. Cela signifie que je peux ensuite comparer les chiffres de ce jour et du même jour il y a 12 mois dans la couche de présentation - mais en exécutant la seule requête SQL.
kiltannen
3
Pourquoi pas un seul SELECT avec WHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))?
ypercubeᵀᴹ
@ ypercubeᵀᴹ La réponse simple est que lors de la construction de ceci au début, je copiais comme je l'ai fait ailleurs en utilisant UNION. Le plus compliqué est que le limiteur de date est en fait un buit plus complexe qu'aujourd'hui et à la même date il y a 12 mois. La plage de dates pour laquelle je sélectionne va du 1er juillet à la date actuelle + du 1er juillet avant cette date à il y a exactement 12 mois. (Exercice à ce jour VS dernier exercice YTD il y a 12 mois - cela donne une comparaison de la croissance ou non pour l'exercice). MAIS comme AndryM et vous le suggérez, je vais essayer sans l'UNION
kiltannen

Réponses:

11

Une manière simple d'éliminer la répétition de l'expression CASE est d'utiliser CROSS APPLY comme ceci:

SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,x.accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   CROSS APPLY
   (
    SELECT 
       CASE 
           WHEN ac.accountName like 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
           WHEN ac.accountName LIKE 'AIR BP%' THEN 'AIR BP'
           WHEN ac.accountName LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
           WHEN ac.accountName LIKE 'AIA%' THEN 'AIA'
       END AS accountName
   ) AS x
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
GROUP BY
   dl.FirstDateOfMonth
   ,x.AccountName

À l'aide de CROSS APPLY, vous attribuez un nom à votre expression CASE de manière à ce qu'il puisse être référencé n'importe où dans votre instruction. Cela fonctionne car à proprement parler, vous définissez la colonne calculée dans un SELECT imbriqué - le SELECT FROM-less qui suit le CROSS APPLY.

Cela revient à référencer une colonne aliasée d'une table dérivée - ce qui est techniquement ce SELECT imbriqué. Il s'agit à la fois d'une sous-requête corrélée et d'une table dérivée. En tant que sous-requête corrélée, elle est autorisée à référencer les colonnes de la portée externe et, en tant que table dérivée, elle permet à la portée externe de référencer les colonnes qu'elle définit.

Pour une requête UNION qui utilise la même expression CASE, vous devez la définir dans chaque jambe, il n'y a pas de solution pour cela, sauf pour utiliser une méthode de remplacement complètement différente au lieu de CASE. Cependant, dans votre cas spécifique, il est possible de récupérer les résultats sans UNION.

Les deux jambes diffèrent dans la condition O only seulement. On a ceci:

WHERE a.datecreated = CONVERT(DATE,now())

et l'autre ceci:

WHERE a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))

Vous pouvez les combiner comme ceci:

WHERE a.datecreated IN (
                        CONVERT(DATE,now()),
                        DATEADD(YEAR,-1,CONVERT(DATE,now()))
                       )

et l'appliquer au SELECT modifié au début de cette réponse.

Andriy M
la source
Bravo Andriy - +1! Inspiré par vous :-), j'ai ajouté une autre approche à ma réponse - a CTE- je ne sais pas quelle est la meilleure approche!
Vérace
Salut Andriy, j'aime le look de cette solution. J'ai mentionné que j'avais un syndicat - mais j'étais assez stupide pour ne pas l'inclure dans mon exemple. Je l'ai fait maintenant. Je soupçonne que ce x de CROSS APPLY ne sera probablement pas disponible pour la seconde moitié de l'UNION, n'est-ce pas? Donc, cela signifierait que je serais toujours coincé avec 2 copies du CASE, n'est-ce pas? (Je vérifierai demain quand je retournerai au travail)
kiltannen
@kiltannen Supprimez la UNIONet incluez simplement la datecreatedcolonne dans votre GROUP BYclause (et mettez à jour la WHEREclause pour inclure les deux dates qui vous intéressent).
Scott M
@ScottM: Je ne pense pas que l'OP doive inclure la datecreatedcolonne dans le GROUP BY. En dehors de cela, je suis entièrement d'accord, ils peuvent simplement combiner les clauses WHERE et abandonner l'UNION.
Andriy M
@ scott-m Je vais devoir essayer ça demain MAIS je suppose que ça ne marche pas si bien. Ce n'est pas réellement un jour - c'est potentiellement plusieurs mois. Je pense que ce que j'ai rencontré était d'avoir jusqu'à 11 mois de données quotidiennes - donc où avait début et fin ET ensuite j'ai dû exécuter un OU pour la même période 12 mois auparavant. Je pense que cela a abouti à un coup sûr. Je devrais réessayer - mais je me souviens avoir rencontré des problèmes que je n'avais pas lors de la gestion de l'UNION. Bien sûr, cela pose ses propres problèmes. Comme celui avec
lequel
22

Mettez les données dans un tableau

CREATE TABLE AccountTranslate (wrong VARCHAR(50), translated(VARCHAR(50));

INSERT INTO AccountTranslate VALUES ('ADDICTION ADVICE%','ADDICTION ADVICE');
INSERT INTO AccountTranslate VALUES ('AIR BP%','AIR BP');
INSERT INTO AccountTranslate VALUES ('AIR NEW Z%', 'AIR NEW ZEALAND');

et rejoignez-le.

SELECT ...,COALESCE(AccountTranslate.translated, ac.accountName) AS accountName
FROM
...., 
account_code ac left outer join 
AccountTranslate at on ac.accountName LIKE AccountTranslate.wrong

De cette façon, vous pouvez éviter de garder les données à jour à plusieurs endroits. Utilisez simplement l' COALESCEendroit où vous en avez besoin. Vous pouvez l'incorporer dans CTE ou VIEWs selon les autres suggestions.

LoztInSpace
la source
4

Une autre option, je pense que si vous devez la réutiliser à plusieurs endroits, une fonction valorisée de la table Inline sera une bonne option.

CREATE FUNCTION dbo.itvf_CaseForAccountConsolidation
    ( @au_lname VARCHAR(8000) ) 
RETURNS TABLE 
RETURN 
SELECT  
  CASE
    WHEN UPPER(@au_lname) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(@au_lname) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(@au_lname) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '****ERROR****'  -- you may or may not need this! 
                         -- If converting every record, then yes, if not, then no!
                         -- Errors should stand out on browsing and it's easy to search for!
  END AS wrong

--Copied from verace

Votre sélection sera comme ça.

  SELECT 
   SUM(c.charge_amount) AS GSTExcl
   ,dl.FirstDateOfMonth AS MonthBilled
   ,dl.FirstDateOfWeek AS WeekBilled
   ,dd.wrong AS accountName
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged) AS date_charged
FROM [accession] a
   LEFT JOIN account_code ac ON a.account_code_id = ac.account_code_id
   LEFT Join charge c ON a.accession_id = c.accession_id
   LEFT JOIN dateLookup dl ON convert(date,c.date_charged) = dl.date
   CROSS APPLY  dbo.itvf_CaseForAccountConsolidation( ac.accountName)dd
GROUP BY
   dl.FirstDateOfMonth 
   ,dl.FirstDateOfWeek 
   ,wrong 
   ,dl.FinancialYear
   ,CONVERT(Date,c.date_charged)

De plus, je n'ai pas testé cela et les performances du code doivent également être déterminées.

EDIT1 : Je pense qu'andriy en a déjà donné un qui utilise cross apply qui expurge le code. Eh bien, celui-ci peut être centralisé car tout changement dans la fonction se reflétera dans l'ensemble car vous répétez la même chose dans d'autres parties du code.

Biju jose
la source
3

J'utiliserais un VIEWpour faire ce que vous essayez de faire. Vous pouvez, bien sûr, corriger les données sous-jacentes, mais fréquemment sur ce site, ceux qui posent des questions (consultants / dbas /) n'ont pas le pouvoir de le faire. L'utilisation d'un VIEWpeut résoudre ce problème! J'ai également utilisé la UPPERfonction - un moyen peu coûteux de résoudre les erreurs dans des cas comme celui-ci.

Maintenant, vous ne déclarez qu'une seule VIEWfois et vous pouvez l'utiliser n'importe où! De cette façon, vous n'avez qu'un seul endroit où votre algorithme de conversion de données est stocké et exécuté, augmentant ainsi la fiabilité et la robustesse de votre système.

Vous pouvez également utiliser un CTE ( Common Table Expression ) - voir en bas de la réponse!

Pour répondre à votre question, j'ai fait ce qui suit:

Créez un exemple de table:

CREATE TABLE my_error (wrong VARCHAR(50));

Insérez quelques exemples d'enregistrements:

INSERT INTO my_error VALUES ('Addiction Advice Services Ltd.');
INSERT INTO my_error VALUES ('AIR BP_and-mistake');
INSERT INTO my_error VALUES ('AIR New Zealand Airlines');

Ensuite, créez un VIEWcomme suggéré:

CREATE VIEW my_error_view AS 
SELECT 
  CASE
    WHEN UPPER(wrong) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(wrong) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(wrong) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '***ERROR****' -- You may or may not need this.
                        -- It's attention grabbing (report) and easy to search for (SQL)!
  END AS wrong
FROM my_error;

Ensuite, à SELECT partir de votre VIEW:

SELECT * FROM my_error_view
ORDER BY wrong;

Résultat:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

Et voilà!

Vous pouvez trouver tout cela sur le violon ici .

L' CTEapproche:

Comme ci-dessus, sauf que le CTEest remplacé par le VIEWsuivant:

WITH my_cte AS
(
  SELECT 
  CASE
    WHEN UPPER(wrong) LIKE 'ADDICTION ADVICE%' THEN 'ADDICTION ADVICE'
    WHEN UPPER(wrong) LIKE 'AIR BP%'  THEN 'AIR BP'
    WHEN UPPER(wrong) LIKE 'AIR NEW Z%' THEN 'AIR NEW ZEALAND'
    ELSE '****ERROR****'  -- you may or may not need this! 
                         -- If converting every record, then yes, if not, then no!
                         -- Errors should stand out on browsing and it's easy to search for!
  END AS wrong
  FROM my_error
)
SELECT * FROM my_cte;

Le résultat est le même. Vous pouvez alors traiter le CTEcomme vous le feriez pour n'importe quelle autre table - pour SELECTs seulement! Violon disponible ici .

Dans l'ensemble, je pense que l' VIEWapproche est meilleure dans ce cas!

Vérace
la source
0

Table intégrée

select id, tag, trans.val 
  from [consecutive] c
  join ( values ('AIR NEW Z%', 'AIR NEW ZEALAND'),
                ('AIR BP%',    'AIR BP')
       ) trans (lk, val)
    on c.description like trans.lk 

Sautez l'union et utilisez un ORdans le où suggéré par d'autres.

paparazzo
la source