Các lựa chọn thay thế để nối chuỗi hoặc đi theo thủ tục để ngăn chặn sự lặp lại mã truy vấn SQL?


19

Tuyên bố miễn trừ trách nhiệm: Hãy đồng ý với tôi với tư cách là người chỉ sử dụng cơ sở dữ liệu một phần rất nhỏ thời gian làm việc của anh ấy. (Hầu hết thời gian tôi làm lập trình C ++ trong công việc của mình, nhưng mỗi tháng tôi cần tìm kiếm / sửa chữa / thêm một cái gì đó vào cơ sở dữ liệu của Oracle.)

Tôi đã nhiều lần cần phải viết các truy vấn SQL phức tạp, cả cho các truy vấn đặc biệt và cho các truy vấn được tích hợp trong các ứng dụng, trong đó phần lớn các truy vấn chỉ lặp lại "mã".

Viết những điều gớm ghiếc như vậy bằng ngôn ngữ lập trình truyền thống sẽ khiến bạn gặp rắc rối lớn, nhưng tôi ( tôi ) vẫn chưa thể tìm thấy bất kỳ kỹ thuật tử tế nào để ngăn chặn sự lặp lại mã truy vấn SQL.


Chỉnh sửa: Thứ nhất, tôi muốn cảm ơn những người trả lời đã cung cấp những cải tiến tuyệt vời cho ví dụ ban đầu của tôi . Tuy nhiên, câu hỏi này không phải là về ví dụ của tôi. Đó là về sự lặp lại trong các truy vấn SQL. Như vậy, các câu trả lời ( JackP , Leigh ) cho đến nay đã làm rất tốt cho thấy rằng bạn có thể giảm sự lặp lại bằng cách viết các truy vấn tốt hơn . Tuy nhiên, ngay cả sau đó bạn phải đối mặt với một số sự lặp đi lặp lại mà dường như không thể xóa được: Điều này luôn cằn nhằn tôi với SQL. Trong các ngôn ngữ lập trình "truyền thống", tôi có thể cấu trúc lại khá nhiều để giảm thiểu tính lặp lại trong mã, nhưng với SQL, dường như không có công cụ (?) Cho phép điều này, ngoại trừ việc viết một câu lệnh ít lặp đi lặp lại.

Lưu ý rằng tôi đã xóa thẻ Oracle một lần nữa, vì tôi thực sự quan tâm liệu không có cơ sở dữ liệu hoặc ngôn ngữ kịch bản nào cho phép thêm thứ gì nữa.


Đây là một viên ngọc quý mà tôi đã cùng nhau sở hữu ngày hôm nay. Về cơ bản, nó báo cáo sự khác biệt trong một tập hợp các cột của một bảng. Vui lòng đọc lướt qua đoạn mã sau, đặc biệt. truy vấn lớn ở cuối Tôi sẽ tiếp tục dưới đây.

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

Như bạn có thể thấy, truy vấn để tạo "báo cáo khác biệt" sử dụng cùng một khối SQL SELECT 5 lần (có thể dễ dàng là 42 lần!). Điều này khiến tôi hoàn toàn chết não (tôi được phép nói điều này, sau tất cả những gì tôi đã viết mã), nhưng tôi không thể tìm thấy bất kỳ giải pháp tốt nào cho việc này.

  • Nếu đây sẽ là một truy vấn trong một số mã ứng dụng thực tế, tôi có thể viết một hàm kết hợp truy vấn này thành một chuỗi và sau đó tôi sẽ thực hiện truy vấn dưới dạng một chuỗi.

    • -> Xây dựng chuỗi là khủng khiếp và khủng khiếp để kiểm tra và bảo trì. Nếu "mã ứng dụng" được viết bằng một ngôn ngữ như PL / SQL thì cảm giác đó rất sai.
  • Ngoài ra, nếu được sử dụng từ PL / SQL hoặc tương tự, tôi đoán có một số phương tiện thủ tục để làm cho truy vấn này dễ duy trì hơn.

    • -> Việc hủy một cái gì đó có thể được thể hiện trong một truy vấn duy nhất thành các bước thủ tục chỉ để ngăn chặn việc lặp lại mã cũng cảm thấy sai.
  • Nếu truy vấn này sẽ là cần thiết như một khung nhìn trong cơ sở dữ liệu, thì - theo như tôi hiểu - sẽ không có cách nào khác ngoài việc thực sự duy trì định nghĩa khung nhìn như tôi đã đăng ở trên. (!!?)

    • -> Tôi thực sự phải thực hiện một số bảo trì cho định nghĩa chế độ xem 2 trang một khi không vượt quá tuyên bố trên. Rõ ràng, việc thay đổi bất cứ điều gì trong chế độ xem này yêu cầu tìm kiếm văn bản regrec qua định nghĩa chế độ xem liệu cùng một câu lệnh phụ đã được sử dụng trong một dòng khác hay chưa và liệu nó có cần thay đổi ở đó không.

Vì vậy, như tiêu đề đi - những kỹ thuật nào có để ngăn chặn việc phải viết những điều ghê tởm như vậy?

Câu trả lời:


13

Bạn quá khiêm tốn - SQL của bạn được viết tốt và chính xác với nhiệm vụ bạn đang đảm nhận. Một vài gợi ý:

  • t1.name <> t2.nameluôn luôn đúng nếu t1.name = REPLACE(t2.name, 'DUP_', '')- bạn có thể bỏ cái trước
  • thông thường bạn muốn union all. unioncó nghĩa là union allsau đó thả trùng lặp. Nó có thể không có sự khác biệt trong trường hợp này nhưng luôn luôn sử dụng union alllà một thói quen tốt trừ khi bạn rõ ràng muốn bỏ bất kỳ bản sao nào.
  • nếu bạn sẵn sàng cho các phép so sánh số xảy ra sau khi truyền tới varchar, thì những điều sau đây có thể đáng để xem xét:

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

    Chế độ xem thứ hai là một loại unpivothoạt động - nếu bạn ở trên ít nhất 11g, bạn có thể thực hiện việc này chính xác hơn với unpivotmệnh đề - xem ví dụ ở đây

  • Tôi nói đừng đi theo con đường thủ tục nếu bạn có thể làm điều đó bằng SQL, nhưng ...
  • SQL động có lẽ đáng để xem xét mặc dù các vấn đề bạn đề cập với kiểm tra và bảo trì

--CHỈNH SỬA--

Để trả lời khía cạnh tổng quát hơn của câu hỏi, có các kỹ thuật để giảm sự lặp lại trong SQL, bao gồm:

Nhưng bạn không thể trực tiếp đưa các ý tưởng OO vào thế giới SQL - trong nhiều trường hợp, việc lặp lại là tốt nếu truy vấn có thể đọc và được viết tốt, và sẽ không khôn ngoan khi sử dụng SQL động (ví dụ) chỉ để tránh lặp lại.

Truy vấn cuối cùng bao gồm thay đổi được đề xuất của Leigh và CTE thay vì chế độ xem có thể trông giống như thế này:

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, một phần cho UNION ALL. Thường thì UNIONkhông có ALLkết quả trong một bộ đệm để lưu trữ tạm thời cho hoạt động sắp xếp cần thiết (vì 'UNION' được thực hiện UNION ALLtheo cách hiệu quả theo DISTINCTđó là một loại), do đó, trong một số trường hợp, sự khác biệt hiệu năng có thể rất lớn.
David Spillett

7

Đây là một thay thế cho chế độ xem test_attribs_unp Pivot do JackPDoureb (+1) cung cấp , hoạt động trong các phiên bản trước 11g và quét toàn bộ bảng ít hơn:

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

Truy vấn cuối cùng của ông có thể được sử dụng không thay đổi với quan điểm này.


Tốt hơn nhiều! Tôi nghĩ bạn thậm chí có thể thả các diễn viên?
Jack Douglas

Thay vì SELECT rownum MyRow FROM test_attribs where rownum<=5sử dụng select level MyRow from dual connect by level <= 5. Bạn không muốn tất cả những thứ hợp lý đó chỉ để tạo 5 hàng.
Štefan Oravec

@ Tefan Oravec - Tôi đã có nó như thế, nhưng tôi đã thay đổi nó vì tôi không chắc các phiên bản phân cấp truy vấn nào có sẵn cho. Vì nó đã có sẵn từ ít nhất là phiên bản 8, tôi sẽ thay đổi nó.
Leigh Riffel

4

Tôi thường gặp phải vấn đề tương tự để so sánh hai phiên bản của một bảng cho các hàng mới, đã xóa hoặc đã thay đổi. Vài tháng trước tôi đã xuất bản một giải pháp cho SQL Server bằng PowerShell tại đây .

Để thích ứng với vấn đề của bạn, trước tiên tôi tạo hai chế độ xem để tách bản gốc khỏi các hàng trùng lặp

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

và sau đó tôi kiểm tra các thay đổi với

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;

Từ đây tôi có thể tìm id gốc của bạn

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 và UNION và GROUP BY coi các NULL khác nhau như nhau. Sử dụng các hoạt động này làm cho các truy vấn thanh lịch hơn.

Gợi ý cho người dùng SQL Server: MINUS được đặt tên là EXCEPT ở đó, nhưng hoạt động tương tự.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.