ทางเลือกอื่นในการต่อสตริงหรือดำเนินการตามขั้นตอนเพื่อป้องกันการทำซ้ำรหัสแบบสอบถาม SQL?


19

คำเตือน: โปรดอดทนกับฉันในฐานะคนที่ใช้ฐานข้อมูลเพียงเล็กน้อยในเวลาทำงานของเขา (ส่วนใหญ่ฉันใช้การเขียนโปรแกรม C ++ ในงานของฉัน แต่ทุกเดือนที่แปลกฉันต้องค้นหา / แก้ไข / เพิ่มบางสิ่งในฐานข้อมูล Oracle)

ฉันจำเป็นต้องเขียนแบบสอบถาม SQL ที่ซับซ้อนซ้ำ ๆ ทั้งสำหรับคิวรีแบบเฉพาะกิจและแบบสอบถามที่มีอยู่ในแอปพลิเคชัน

การเขียนสิ่งที่น่ารังเกียจในภาษาโปรแกรมแบบดั้งเดิมจะทำให้คุณประสบปัญหาอย่างหนัก แต่ฉัน ( I ) ยังไม่สามารถหาเทคนิคที่เหมาะสมในการป้องกันการทำซ้ำรหัสแบบสอบถาม SQL


แก้ไข: 1 ผมอยากจะขอบคุณ answerers ที่ให้การปรับปรุงที่ดีในการเดิมของฉันตัวอย่างเช่น อย่างไรก็ตามคำถามนี้ไม่เกี่ยวกับตัวอย่างของฉัน มันเกี่ยวกับการทำซ้ำในแบบสอบถาม SQL เป็นเช่นนี้คำตอบ ( JackP , ลีห์ ) เพื่อให้ห่างไกลจะได้งานที่ดีของการแสดงให้เห็นว่าคุณสามารถลด repetitiveness โดยการเขียนคำสั่งที่ดีกว่า อย่างไรก็ตามแม้แล้วคุณเผชิญ repetitiveness บางอย่างที่เห็นได้ชัดว่าไม่สามารถเอาออก: นี่เสมอ nagged ฉันกับ SQL ในภาษาการเขียนโปรแกรม "ดั้งเดิม" ฉันสามารถ refactor ค่อนข้างมากเพื่อลดความซ้ำซ้อนในรหัส แต่ด้วย 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 ครั้งได้อย่างง่ายดาย!) สิ่งนี้ทำให้ฉันรู้สึกว่าสมองตายอย่างแน่นอน (ฉันได้รับอนุญาตให้พูดสิ่งนี้หลังจากทั้งหมดที่ฉันเขียนโค้ด) แต่ฉันไม่สามารถหาวิธีแก้ปัญหาที่ดีได้

  • ถ้านี่จะเป็นแบบสอบถามในรหัสแอปพลิเคชันจริงฉันสามารถเขียนฟังก์ชันที่ cobbles รวมแบบสอบถามนี้เป็นสตริงและจากนั้นฉันจะดำเนินการแบบสอบถามเป็นสตริง

    • -> การสร้างสายอักขระนั้นน่ากลัวและน่ากลัวในการทดสอบและบำรุงรักษา หากเขียน "รหัสแอปพลิเคชัน" ในภาษาเช่น PL / SQL มันรู้สึกผิดอย่างมาก
  • อีกวิธีหนึ่งถ้าใช้จาก PL / SQL หรือคล้ายกันฉันเดาว่ามีวิธีการบางอย่างที่จะทำให้แบบสอบถามนี้สามารถบำรุงรักษาได้มากขึ้น

    • -> การคลายสิ่งที่สามารถแสดงในคิวรีเดียวในขั้นตอนกระบวนการเพียงเพื่อป้องกันการทำซ้ำรหัสก็รู้สึกผิดเช่นกัน
  • หากแบบสอบถามนี้จำเป็นต้องใช้เป็นมุมมองในฐานข้อมูลแล้ว - เท่าที่ฉันเข้าใจ - จะไม่มีวิธีอื่นนอกจากการรักษาคำจำกัดความมุมมองตามจริงที่ฉันโพสต์ไว้ (!!?)

    • -> จริง ๆ แล้วฉันต้องทำการบำรุงรักษาบางอย่างในการกำหนดมุมมอง 2 หน้าเมื่อไม่ได้อยู่เหนือคำสั่ง เห็นได้ชัดว่าการเปลี่ยนแปลงทุกอย่างในมุมมองนี้ต้องการการค้นหาข้อความ regexp เหนือคำจำกัดความมุมมองว่ามีการใช้คำสั่งย่อยเดียวกันในอีกบรรทัดหนึ่งหรือไม่และจำเป็นต้องเปลี่ยนที่นั่นหรือไม่

ดังนั้นเมื่อชื่อเรื่อง - เทคนิคอะไรที่มีเพื่อป้องกันไม่ให้เขียนสิ่งที่น่ารังเกียจเช่นนั้น?

คำตอบ:


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

    มุมมองที่สองเป็นชนิดของunpivotการดำเนินงาน - ถ้าคุณอยู่ในอย่างน้อย 11g คุณสามารถทำเช่นนี้รัดกุมมากกับunpivotข้อ - ดูที่นี่เช่น

  • ฉันว่าอย่าลงเส้นทางกระบวนการถ้าคุณสามารถทำใน SQL แต่ ...
  • Dynamic SQL น่าจะคุ้มค่าเมื่อพิจารณาถึงปัญหาที่คุณพูดถึงด้วยการทดสอบและการบำรุงรักษา

--EDIT--

เพื่อตอบคำถามทั่วไปมากขึ้นมีเทคนิคในการลดการทำซ้ำใน SQL ได้แก่ :

แต่คุณไม่สามารถนำความคิด OO เข้าสู่โลกใน SQL โดยตรง - ในหลายกรณีการทำซ้ำเป็นเรื่องปกติถ้าแบบสอบถามสามารถอ่านและเขียนได้ดีและมันจะไม่ฉลาดที่จะหันไปใช้ SQL แบบไดนามิก (ตัวอย่าง) เพียงเพื่อไม่ให้ซ้ำซ้อน

ข้อความค้นหาสุดท้ายรวมถึงการเปลี่ยนแปลงที่แนะนำของ 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ไม่มีALLผลลัพธ์ในสปูลไปยังหน่วยเก็บข้อมูลชั่วคราวสำหรับการดำเนินการเรียงลำดับที่ต้องการ (เนื่องจาก 'ยูเนี่ยน' มีประสิทธิภาพUNION ALLตามมาDISTINCTซึ่งหมายถึงการเรียงลำดับ) ดังนั้นในบางกรณีความแตกต่างของประสิทธิภาพอาจสูงมาก
David Spillett

7

นี่เป็นอีกทางเลือกหนึ่งสำหรับมุมมอง test_attribs_unpivot ที่จัดทำโดย JackPDouglas (+1)ที่ใช้งานได้ในเวอร์ชันก่อนหน้า 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);

ข้อความค้นหาสุดท้ายของเขาสามารถใช้ไม่เปลี่ยนแปลงกับมุมมองนี้


ดีกว่ามาก! ฉันคิดว่าคุณสามารถโยนนักแสดงได้หรือไม่
Jack Douglas

แทนการใช้งานSELECT rownum MyRow FROM test_attribs where rownum<=5 select level MyRow from dual connect by level <= 5คุณไม่ต้องการให้ตรรกะเหล่านั้นทั้งหมดเพียงแค่สร้าง 5 แถว
Štefan Oravec

@ Štefan Oravec - ฉันมีแบบนั้น แต่ฉันเปลี่ยนเพราะฉันไม่แน่ใจว่าแบบสอบถามแบบลำดับชั้นรุ่นใดที่พร้อมใช้งาน เนื่องจากมีให้ตั้งแต่รุ่น 8 เป็นอย่างน้อยฉันจะเปลี่ยน
Leigh Riffel

4

ฉันมักจะพบปัญหาที่คล้ายกันเพื่อเปรียบเทียบตารางสองเวอร์ชันสำหรับแถวใหม่ลบหรือเปลี่ยนแปลง บางเดือนที่ผ่านมาผมตีพิมพ์โซลูชั่นสำหรับ SQL Server ใช้ PowerShell ที่นี่

หากต้องการปรับให้เข้ากับปัญหาของคุณฉันจะสร้างมุมมองที่สองก่อนเพื่อแยกต้นฉบับออกจากแถวที่ซ้ำกัน

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;

จากที่นี่ฉันสามารถหารหัสประจำตัวของคุณได้

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: ลบชื่อมียกเว้น แต่ทำงานคล้ายกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.