変数としてのCaseステートメントで100を超えるエントリを持つ方法


11

単純なクエリの4か所で同じステートメントを使用している> 100の選択肢を含むcaseステートメントを記述しました。

同じクエリを2回実行し、それらを結合したものもカウントしているため、group byにもcaseステートメントが含まれています。

これは、同じ会社の異なるレコードの綴りが異なるいくつかの会社名のラベルを変更するためです。

変数を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''
       ...

selectステートメントで使用すると、クエリはcaseステートメントをテキストとして返し、評価しませんでした。

また、グループで使用することもできませんでした-次のエラーメッセージが表示されました。

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

理想的には、CASEを1か所に配置したいのですが、1つの行を更新して他の場所に複製しない可能性はありません。

これを行う方法はありますか?

私は他の方法を受け入れます(多分関数のようですが、このように使用する方法がわかりません)。

これは私が現在使用しているSELECTのサンプルです

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

このUNIONの目的は、ある期間のすべてのデータを返すことと、以前の12か月間の同じ期間のデータを返すことです。

編集:不足している「CATCH-ALL」を
追加
EDIT2:UNIONステートメントの2番目のAddedを追加EDIT3:他のいくつかの必要な要素を含めるようにGROUP BYを修正


UNIONの2つの部分はどう違うのですか?わずかに異なるWHERE条件を除いて、それらはかなり似ています。
ypercubeᵀᴹ

それが大きな違いです。日付の2つの異なるWHERE条件は、12か月前の今日と同じ日付を示します。つまり、プレゼンテーションレイヤーでその日と同じ日の12か月前の数値を比較できますが、単一のSQLクエリを実行しています。
キルタンネン

3
なぜ単一のSELECTではありWHERE a.datecreated = CONVERT(DATE,now()) OR a.datecreated = DATEADD(YEAR,-1,CONVERT(DATE,now()))ませんか?
ypercubeᵀᴹ

@ypercubeᵀᴹ簡単な答えは、これを最初に作成したときに、UNIONを使用する別の場所でコピーした方法をコピーしていたときです。もう少し複雑なのは、日付リミッターは実際には今日よりもかなり複雑で、12か月前の同じ日付であるということです。私が選択している日付範囲は、7月1日から現在の日付+その前の7月1日からちょうど12か月前の日付までです。(12か月前の会計年度累計対最終年度YTD-これは、会計年度の成長またはその他の比較を示します)。しかし、AndryMとあなたが提案するように、私はUNIONを引いて試してみるつもりです
kiltannen

回答:


11

CASE式の繰り返しを排除する簡単な方法の1つは、次のようにCROSS APPLYを使用することです。

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

CROSS APPLYを使用して、ステートメント内のどこからでも参照できるように、CASE式に名前を割り当てます。厳密に言うと、ネストされた SELECT(CROSS APPLYに続くFROMなしのSELECT)で計算列を定義しているためです。

これは、派生テーブルのエイリアスされた列を参照することと同じです–技術的には、この入れ子になったSELECTと同じです。これは、相関サブクエリと派生テーブルの両方です。相関サブクエリとして、外部スコープの列を参照でき、派生テーブルとして、外部スコープが定義した列を参照できます。

同じCASE式を使用するUNIONクエリの場合、各レッグで定義する必要があります。CASEの代わりに完全に異なる置換方法を使用することを除いて、その回避策はありません。ただし、特定のケースでは、UNIONなしで結果をフェッチすることが可能です。

2つのレッグは、WHERE条件のみが異なります。1つはこれを持っています:

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

そして他のこれ:

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

次のように組み合わせることができます。

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

この回答の冒頭にある変更されたSELECTに適用します。


素敵なAndriy-+1!あなたに触発されて:-)、私は私の答えに別のアプローチを追加しました-a-私はCTEどちらが最良のアプローチかわかりません!
Vérace

こんにちはAndriy、私はこのソリューションの外観が好きです。私はUNIONがあると述べましたが、私はそれを私の例に含めないほど愚かでした。私は今そうしました。CROSS APPLYのこのxは、UNIONの後半では使用できない可能性があると思いますか?つまり、CASEのコピーが2つ残ったままになるということです。(私が仕事に戻ったら、明日チェックします)
キルタネン

@kiltannenをドロップし、句に列をUNION含めるだけです(そして、関心のある両方の日付を含むように句を更新します)。datecreatedGROUP BYWHERE
スコットM

@ScottM:OPがdatecreatedGROUP BYに列を含める必要はないと思います。それ以外は、私は完全に同意します。彼らはWHERE句を組み合わせてUNIONを破棄することができます。
Andriy M

@ scott-m明日これを試さなければなりませんが、うまくいかないと思います。実際には1日ではなく、数か月かかる可能性があります。私が遭遇したのは、最大11か月の毎日のデータを持っていると思います。つまり、どこで開始と終了が行われたか、そして同じ期間に12か月前にORを実行する必要があったのです。これはパフォーマンスのヒットに終わったと思います。再試行する必要がありますが、UNIONの実行時に発生しなかった問題が発生したことを覚えています。もちろん、それ自体に問題があります。私が現在
取り組ん

22

データをテーブルに入れる

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');

それに参加します。

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

これにより、複数の場所でデータを最新の状態に保つことを回避できます。COALESCE必要な場所で使用してください。VIEW他の提案に従って、これをCTEまたはに組み込むことができます。


4

別のオプションをいくつか再利用する必要がある場合は、インラインテーブル値関数を使用するとよいでしょう。

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

あなたの選択はこのようになります。

  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)

また、これはテストしていません。コードのパフォーマンスも確認する必要があります。

EDIT1:私はアンドリーがすでに使用されているが、コードをredactsた適用渡り1を与えていると思います。コードの他の部分でも同じことを繰り返しているため、関数の変更はすべて反映されるため、これを集中化できます。


3

私はVIEWあなたがやろうとしていることをするのにa を使うでしょう。もちろん、基礎となるデータを修正することもできますが、このサイトでは頻繁に、質問をする人(コンサルタント/ dbas /)にはこれを行う権限がありません。を使用すると、VIEWこの問題を解決できます。私はUPPER関数も使用しました-このような場合のエラーを解決する安価な方法。

これで、VIEW一度だけ宣言して、どこでも使用できるようになります!このように、データ変換アルゴリズムが保存および実行される場所が1つしかないため、システムの信頼性と堅牢性が向上します。

CTE(Common Table Expression)を使用することもできます-回答の下部を参照してください!

あなたの質問に答えるために、私は次のことをしました:

サンプルテーブルを作成します。

CREATE TABLE my_error (wrong VARCHAR(50));

いくつかのサンプルレコードを挿入します。

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');

次に、VIEW提案どおりにを作成します。

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;

次に、SELECT あなたからVIEW

SELECT * FROM my_error_view
ORDER BY wrong;

結果:

ADDICTION ADVICE
AIR BP
AIR NEW ZEALAND

エボラ!

これらすべては、ここのフィドル見つけることができます。

CTEアプローチ:

上記と同じCTEですがVIEW、次のように置き換えられます。

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;

結果は同じです。次に、CTEを他のテーブルと同じように扱うことができます- SELECTのみ!フィドルはこちらから入手できます

全体として、VIEWこの場合はアプローチが優れていると思います。


0

埋め込みテーブル

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 

ユニオンをスキップし、OR他の人が提案する場所で使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.