web-dev-qa-db-fra.com

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

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 certains autres éléments nécessaires

11
kiltannen

Une façon 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 imbriqué SELECT - le SELECT de moins 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.

11
Andriy M

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 le COALESCE là où vous en avez besoin. Vous pouvez l'incorporer dans CTE ou VIEWs selon les autres suggestions.

22
LoztInSpace

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.

4
Biju jose

J'utiliserais un VIEW pour 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 VIEW peut résoudre ce problème! J'ai également utilisé la fonction UPPER - un moyen bon marché de résoudre les erreurs dans des cas comme celui-ci.

Maintenant, vous ne déclarez le VIEW qu'une seule fois 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 le 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');

Créez ensuite un VIEW comme 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 depuis 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'approche CTE:

Comme ci-dessus, sauf que le CTE est substitué au VIEW comme suit:

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 CTE comme vous le feriez pour n'importe quelle autre table - pour les SELECT uniquement! Fiddle available ici .

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

3
Vérace

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 

Ignorez l'union et utilisez un OR à l'endroit comme suggéré par d'autres.

0
paparazzo