SQL 쿼리 코드 반복을 방지하기 위해 문자열을 연결하거나 절차를 수행하는 대안?


19

면책 조항 : 업무 시간의 일부만 데이터베이스를 사용하는 사람으로 저와 함께하십시오. (대부분 저는 C ++ 프로그래밍 작업을 수행하지만 매월마다 Oracle 데이터베이스에서 무언가를 검색 / 수정 / 추가해야합니다.)

저는 임시 쿼리와 응용 프로그램에 내장 된 쿼리 모두에 대해 복잡한 SQL 쿼리를 반복해서 작성해야했습니다. 여기서는 쿼리의 대부분이 "코드"를 반복했습니다.

기존의 프로그래밍 언어와 같은 가증를 작성하는 것은 곤경에 당신을 얻을 것이다, 그러나 I ( 나는 ) 아직 방지 SQL 쿼리 코드의 반복에 어떤 점잖은 기술을 찾을 수 없었습니다.


편집 : 1, 나는 원래의 예를 크게 개선 한 답변자에게 감사드립니다 . 그러나이 질문은 내 예에 관한 것이 아닙니다. SQL 쿼리의 반복성에 관한 것입니다. 따라서 지금까지 답변 ( JackP , Leigh )은 더 나은 쿼리를 작성하여 반복성을 줄일 수 있다는 것을 보여줍니다 . 그러나 그때 조차도 분명히 제거 할 수없는 반복성에 직면합니다. "전통적인"프로그래밍 언어에서는 코드의 반복성을 최소화하기 위해 상당히 많은 리팩토링을 수행 할 수 있지만 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.namet1.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);

    두 번째보기는 일종의 unpivot작업입니다-11g 이상인 경우 unpivot절 과 함께 더 간결하게 수행 할 수 있습니다 - 예제는 여기 를 참조 하십시오

  • SQL로 할 수 있다면 절차 적 경로를 따르지 말라고 말하지만 ...
  • 테스트 및 유지 관리에서 언급 한 문제에도 불구하고 동적 SQL은 고려할 가치가 있습니다.

--편집하다--

질문의 더 일반적인 측면에 대답하기 위해 다음을 포함하여 SQL의 반복을 줄이는 기술이 있습니다.

쿼리가 읽고 잘 작성된 경우 반복 괜찮 많은 경우에, (예를 들어) 동적 SQL에 의존하는 것은 현명 할 것입니다 -하지만 당신이 직접 SQL의 세계로 OO 아이디어를 가져올 수 없습니다 단지 피하기 반복합니다.

Leigh의 제안 된 변경 사항과 뷰 대신 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. 일반적 으로 필요한 정렬 작업 ( 'UNION'이 효과적으로 뒤 따르는 정렬을 의미 함) 을 위해 임시 스토리지에 대한 스풀을 생성 UNION하지 않으므로 경우에 따라 성능 차이가 클 수 있습니다. ALLUNION ALLDISTINCT
David Spillett

7

다음은 11g 이전 버전에서 작동하며 전체 테이블 스캔이 적은 JackPDouglas (+1) 가 제공하는 test_attribs_unpivot보기의 대안입니다 .

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 개의 행을 생성하기위한 모든 논리적 인 접근을 원하지 않습니다.
Štefan Oravec

@ Štefan Oravec-나는 그것을 좋아했지만 계층 적 쿼리가 어떤 버전에 사용 가능한지 확실하지 않기 때문에 변경했습니다. 버전 8 이상부터 사용 가능하므로 변경하겠습니다.
레이 리펠

4

새로운 행, 삭제 또는 변경된 행에 대해 두 버전의 테이블을 비교할 때 종종 비슷한 문제가 발생합니다. 몇 달 전에 PowerShell을 사용하여 SQL Server 용 솔루션을 여기에 게시했습니다 .

귀하의 문제에 맞게 조정하기 위해 먼저 원본을 중복 행과 분리하는 두 가지보기를 만듭니다.

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;

BTW : MINUS, UNION 및 GROUP BY는 서로 다른 NULL을 동일하게 취급합니다. 이러한 작업을 사용하면 쿼리가 더욱 우아해집니다.

SQL Server 사용자를위한 힌트 : MINUS는 EXCEPT라는 이름을 갖지만 유사하게 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.