SQLクエリコードの繰り返しを防ぐために、文字列を連結したり、手続きを行ったりする代わりになりますか?


19

免責事項:データベースを使用する作業時間のごく一部としてのみ、私と一緒に我慢してください。(ほとんどの場合、仕事でC ++プログラミングを行いますが、奇数月ごとにOracleデータベースで何かを検索/修正/追加する必要があります。)

アドホッククエリとアプリケーションに組み込まれたクエリの両方で、クエリの大部分が「コード」を繰り返しただけの複雑なSQLクエリを繰り返し書く必要がありました。

伝統的なプログラミング言語で、そのような憎むべきを書く深いトラブルにあなたを得るでしょう、まだI(Iは)まだSQLクエリコードの繰り返しを防ぐために、まともな手法を見つけることができませんでした。


編集:最初に、元の例に優れた改善を提供してくれた回答者に感謝します。ただし、この質問は私の例に関するものではありません。SQLクエリの反復性についてです。そのため、これまでの回答(JackPLeigh)は、より良いクエリを作成することで反復性を減らすことができることを示す素晴らしい仕事をしています。ただし、その場合でも明らかに削除できない反復性に直面します。これは、常にSQLに悩まされます。「従来の」プログラミング言語では、コードの反復性を最小限に抑えるためにかなり多くのリファクタリングを行うことができますが、SQLでは、これを可能にする(?)ツールはないようです。

Oracleタグを再度削除したことに注意してください。これは、データベースやスクリプト言語が存在しないかどうかに興味があるためです。


今日私が一緒に石畳を作ったそのような宝石の一つです。基本的に、単一のテーブルの列セットの違いを報告します。次のコードをざっと読んでください。最後に大きなクエリ。以下に続きます。

--
-- Create Table to test queries
--
CREATE TABLE TEST_ATTRIBS (
id NUMBER PRIMARY KEY,
name  VARCHAR2(300) UNIQUE,
attr1 VARCHAR2(2000),
attr2 VARCHAR2(2000),
attr3 INTEGER,
attr4 NUMBER,
attr5 VARCHAR2(2000)
);

--
-- insert some test data
--
insert into TEST_ATTRIBS values ( 1, 'Alfred',   'a', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 2, 'Batman',   'b', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 3, 'Chris',    'c', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values ( 4, 'Dorothee', 'd', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 5, 'Emilia',   'e', 'Barfoo', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 6, 'Francis',  'f', 'Barfoo', 99, 44, 'e');
insert into TEST_ATTRIBS values ( 7, 'Gustav',   'g', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values ( 8, 'Homer',    'h', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values ( 9, 'Ingrid',   'i', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values (10, 'Jason',    'j', 'Bob',    33, 44, 'e');
insert into TEST_ATTRIBS values (12, 'Konrad',   'k', 'Bob',    66, 44, 'e');
insert into TEST_ATTRIBS values (13, 'Lucas',    'l', 'Foobar', 99, 44, 'e');

insert into TEST_ATTRIBS values (14, 'DUP_Alfred',   'a', 'FOOBAR', 33, 44, 'e');
insert into TEST_ATTRIBS values (15, 'DUP_Chris',    'c', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values (16, 'DUP_Dorothee', 'd', 'Foobar', 99, 44, 'e');
insert into TEST_ATTRIBS values (17, 'DUP_Gustav',   'X', 'Foobar', 33, 44, 'e');
insert into TEST_ATTRIBS values (18, 'DUP_Homer',    'h', 'Foobar', 66, 44, 'e');
insert into TEST_ATTRIBS values (19, 'DUP_Ingrid',   'Y', 'foo',    99, 44, 'e');

insert into TEST_ATTRIBS values (20, 'Martha',   'm', 'Bob',    33, 88, 'f');

-- Create comparison view
CREATE OR REPLACE VIEW TA_SELFCMP as
select 
t1.id as id_1, t2.id as id_2, t1.name as name, t2.name as name_dup,
t1.attr1 as attr1_1, t1.attr2 as attr2_1, t1.attr3 as attr3_1, t1.attr4 as attr4_1, t1.attr5 as attr5_1,
t2.attr1 as attr1_2, t2.attr2 as attr2_2, t2.attr3 as attr3_2, t2.attr4 as attr4_2, t2.attr5 as attr5_2
from TEST_ATTRIBS t1, TEST_ATTRIBS t2
where t1.id <> t2.id
and t1.name <> t2.name
and t1.name = REPLACE(t2.name, 'DUP_', '')
;

-- NOTE THIS PIECE OF HORRIBLE CODE REPETITION --
-- Create comparison report
-- compare 1st attribute
select 'attr1' as Different,
id_1, id_2, name, name_dup,
CAST(attr1_1 AS VARCHAR2(2000)) as Val1, CAST(attr1_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr1_1 <> attr1_2
or (attr1_1 is null and attr1_2 is not null)
or (attr1_1 is not null and attr1_2 is null)
union
-- compare 2nd attribute
select 'attr2' as Different,
id_1, id_2, name, name_dup,
CAST(attr2_1 AS VARCHAR2(2000)) as Val1, CAST(attr2_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr2_1 <> attr2_2
or (attr2_1 is null and attr2_2 is not null)
or (attr2_1 is not null and attr2_2 is null)
union
-- compare 3rd attribute
select 'attr3' as Different,
id_1, id_2, name, name_dup,
CAST(attr3_1 AS VARCHAR2(2000)) as Val1, CAST(attr3_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr3_1 <> attr3_2
or (attr3_1 is null and attr3_2 is not null)
or (attr3_1 is not null and attr3_2 is null)
union
-- compare 4th attribute
select 'attr4' as Different,
id_1, id_2, name, name_dup,
CAST(attr4_1 AS VARCHAR2(2000)) as Val1, CAST(attr4_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr4_1 <> attr4_2
or (attr4_1 is null and attr4_2 is not null)
or (attr4_1 is not null and attr4_2 is null)
union
-- compare 5th attribute
select 'attr5' as Different,
id_1, id_2, name, name_dup,
CAST(attr5_1 AS VARCHAR2(2000)) as Val1, CAST(attr5_2 AS VARCHAR2(2000)) as Val2
from TA_SELFCMP
where attr5_1 <> attr5_2
or (attr5_1 is null and attr5_2 is not null)
or (attr5_1 is not null and attr5_2 is null)
;

ご覧のとおり、「差分レポート」を生成するクエリは、同じSQL SELECTブロックを5回使用します(簡単に42回になる可能性があります!)。これは絶対に頭がおかしい(私はこれを言うことは許されますが、結局コードを書いたのです)ように感じますが、これに対する良い解決策を見つけることができませんでした。

  • これが実際のアプリケーションコードのクエリである場合、このクエリを文字列としてまとめて、クエリを文字列として実行する関数を作成できます。

    • ->文字列を構築するのは恐ろしく、テストと保守が大変です。「アプリケーションコード」がPL / SQLなどの言語で記述されている場合、非常に間違っていると感じて痛い。
  • あるいは、PL / SQLなどから使用する場合、このクエリをよりメンテナンスしやすくするための手順的な手段があると思います。

    • ->コードの繰り返しを防ぐために、単一のクエリで表現できるものを手順ステップに展開することも間違っていると感じます。
  • このクエリがデータベースのビューとして必要な場合、私が理解している限り、上記で投稿したようにビュー定義を実際に維持する以外に方法はありません。(!!?)

    • ->実際、2ページビューの定義をメンテナンスする必要がありました。明らかに、このビューで何かを変更するには、同じサブステートメントが別の行で使用されていたかどうか、およびそこで変更する必要があるかどうかについて、ビュー定義に対する正規表現テキスト検索が必要でした。

それで、タイトルが行くように- そのような憎悪を書かなければならないことを防ぐためにどんなテクニックがありますか?

回答:


13

あなたはあまりにも控えめです-あなたのSQLはあなたが引き受けている仕事を考えるとうまく、簡潔に書かれています。いくつかのポインター:

  • t1.name <> t2.name次の場合は常に真ですt1.name = REPLACE(t2.name, 'DUP_', '')-前者を削除できます
  • 通常はあなたが欲しいunion all。重複を削除するunionことを意味しunion allます。この場合、違いはないかもしれませんunion allが、重複を明示的に削除する場合を除き、常に使用することをお勧めします。
  • varchar キャストした後に数値比較を行う場合は、次のことを検討する価値があります。

    create view test_attribs_cast as 
    select id, name, attr1, attr2, cast(attr3 as varchar(2000)) as attr3, 
           cast(attr4 as varchar(2000)) as attr4, attr5
    from test_attribs;
    
    create view test_attribs_unpivot as 
    select id, name, 1 as attr#, attr1 as attr from test_attribs_cast union all
    select id, name, 2, attr2 from test_attribs_cast union all
    select id, name, 3, attr3 from test_attribs_cast union all
    select id, name, 4, attr4 from test_attribs_cast union all
    select id, name, 5, attr5 from test_attribs_cast;
    
    select 'attr'||t1.attr# as different, t1.id as id_1, t2.id as id_2, t1.name, 
           t2.name as name_dup, t1.attr as val1, t2.attr as val2
    from test_attribs_unpivot t1 join test_attribs_unpivot t2 on(
           t1.id<>t2.id and 
           t1.name = replace(t2.name, 'DUP_', '') and 
           t1.attr#=t2.attr# )
    where t1.attr<>t2.attr or (t1.attr is null and t2.attr is not null)
          or (t1.attr is not null and t2.attr is null);

    2番目のビューは一種のunpivot操作です-少なくとも11gを使用している場合は、unpivot句を使用してこれをより簡潔に行うことができます- 例についてはこちらを参照してください

  • SQLでできるなら、手続きのルートをたどらないでください。
  • 動的SQLは、テストと保守で言及した問題にもかかわらず、おそらく検討する価値があります。

-編集-

質問のより一般的な側面に答えるために、SQLでの繰り返しを減らすテクニックがあります。

クエリが読みやすいとよく書かれている場合、繰り返しは大丈夫です多くの場合、(例えば)動的SQLに頼ることが賢明だろう-しかし、あなたは直接SQLの世界にOOのアイデアを持って来ることができないだけで避けるの繰り返しに。

リーの提案された変更とビューではなくCTEを含む最終クエリは、次のようになります。

with t as ( select id, name, attr#, 
                   decode(attr#,1,attr1,2,attr2,3,attr3,4,attr4,attr5) attr
            from test_attribs
                 cross join (select rownum attr# from dual connect by rownum<=5))
select 'attr'||t1.attr# as different, t1.id as id_1, t2.id as id_2, t1.name, 
       t2.name as name_dup, t1.attr as val1, t2.attr as val2
from t t1 join test_attribs_unpivot t2 
               on( t1.id<>t2.id and 
                   t1.name = replace(t2.name, 'DUP_', '') and 
                   t1.attr#=t2.attr# )
where t1.attr<>t2.attr or (t1.attr is null and t2.attr is not null)
      or (t1.attr is not null and t2.attr is null);

1
+1、一部はUNION ALL。多くの場合UNIONALL通常は、必要な並べ替え操作用の一時ストレージへのスプールになります(「UNION」のUNION ALL後にDISTINCTは並べ替えが暗黙的に続くため)、場合によってはパフォーマンスの違いが大きくなることがあります。
デビッドスピレット16年

7

以下は、JackPDouglas (+1)が提供するtest_attribs_unpivotビューに代わるもので、11gより前のバージョンで動作し、全表スキャンの回数を減らします。

CREATE OR REPLACE VIEW test_attribs_unpivot AS
   SELECT ID, Name, MyRow Attr#, CAST(
      DECODE(MyRow,1,attr1,2,attr2,3,attr3,4,attr4,attr5) AS VARCHAR2(2000)) attr
   FROM TEST_ATTRIBS 
   CROSS JOIN (SELECT level MyRow FROM dual connect by level<=5);

このビューでは、彼の最終クエリを変更せずに使用できます。


ずっといい!キャストをドロップすることもできますか?
ジャックダグラス

SELECT rownum MyRow FROM test_attribs where rownum<=5使用する代わりにselect level MyRow from dual connect by level <= 5。5つの行を作成するためだけに、これらすべての論理取得が必要なわけではありません。
シュテファンオラベック

@ŠtefanOravec-私はそのようにしていましたが、階層クエリがどのバージョンで利用できるのかわからなかったので変更しました。少なくともバージョン8から使用可能になったため、変更します。
リーリッフェル

4

多くの場合、新しい行、削除された行、または変更された行について、テーブルの2つのバージョンを比較する同様の問題が発生します。数か月前、PowerShellを使用したSQL Serverのソリューションをここに公開しました

問題に合わせて、最初に2つのビューを作成して、元の行を重複行から分離します

CREATE OR REPLACE VIEW V1_TEST_ATTRIBS AS 
select * from TEST_ATTRIBS where SUBSTR(name, 1, 4) <> 'DUP_'; 

CREATE OR REPLACE VIEW V2_TEST_ATTRIBS AS 
select id, REPLACE(name, 'DUP_', '') name, attr1, attr2, attr3, attr4, attr5 from TEST_ATTRIBS where SUBSTR(name, 1, 4) = 'DUP_'; 

そして、私はで変更を確認します

SELECT 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
MINUS
Select 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V2_TEST_ATTRIBS
UNION
SELECT 2 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V2_TEST_ATTRIBS
MINUS
SELECT 2 SRC ,NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
ORDER BY NAME, SRC;

ここから元のIDを見つけることができます

Select NVL(v1.id, v2.id) id,  t.name, t.attr1, t.attr2, t.attr3, t.attr4, t.attr5 from
(
SELECT 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V1_TEST_ATTRIBS
MINUS
Select 1 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V2_TEST_ATTRIBS
UNION
SELECT 2 SRC, NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 FROM V2_TEST_ATTRIBS
MINUS
Select 2 SRC ,NAME, ATTR1, ATTR2, ATTR3, ATTR4, ATTR5 from V1_TEST_ATTRIBS
) t
LEFT JOIN V1_TEST_ATTRIBS V1 ON T.NAME = V1.NAME AND T.SRC = 1
LEFT JOIN V2_TEST_ATTRIBS V2 ON T.NAME = V2.NAME AND T.SRC = 2
ORDER by NAME, SRC;

ところで:マイナスとユニオンとGROUP BYは、異なるNULLを等しいものとして扱います。これらの操作を使用すると、クエリがよりエレガントになります。

SQL Serverユーザーへのヒント:MINUSはそこを除いて名前が付けられていますが、同様に機能します。

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