web-dev-qa-db-ja.com

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

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

同じクエリを2回結合して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を修正して他の必要な要素を含めました

11
kiltannen

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式に名前を割り当てます。厳密に言えば、計算列をnested 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に適用します。

11
Andriy M

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

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を使用するだけです。他の提案に従って、これをCTEまたはVIEWsに組み込むことができます。

22
LoztInSpace

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

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:andriyは、コードを編集するクロス適用を使用するものをすでに提供していると思います。まあ、コードの他の部分で同じことを繰り返しているので、関数の変更はすべて反映されるため、これは集中化できます。

4
Biju jose

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

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

CTEを使用することもできます( 共通テーブル式 )-回答の下部を参照してください!

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

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

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

エボラ!

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

CTEアプローチ:

上記と同じですが、CTEVIEWの代わりに次のように使用されます。

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を他のテーブルと同様に扱うことができます-SELECTsの場合のみ! Fiddle利用可能 ここ

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

3
Vérace

埋め込みテーブル

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を使用します。

0
paparazzo