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
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.
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 VIEW
s selon les autres suggestions.
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.
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!
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.