SQL sorgu kodu tekrarını önlemek için dizeleri birleştirmeye veya yordama alternatifler?


19

Feragatname: Lütfen veritabanını sadece çalışma süresinin çok küçük bir kısmını kullanan biri olarak yanımda olun. (Çoğu zaman işimde C ++ programlama yaparım, ancak her garip ayda bir Oracle veritabanında arama / düzeltme / bir şey eklemem gerekir.)

Tekrar tekrar hem karmaşık sorguları hem de uygulamaların içinde yerleşik sorguları için karmaşık SQL sorguları yazmanız gerekiyordu, nerede sorguların büyük bölümleri nerede sadece tekrarlanan "kod".

Bu tür iğrençlikleri geleneksel bir programlama dilinde yazmak sizi derinden rahatsız eder, ancak ben ( I ) SQL sorgu kodu tekrarını önlemek için iyi bir teknik bulamadım.


Düzenleme: 1, benim orijinal mükemmel iyileştirmeler sağlanan Cevap Gönderen teşekkür etmek istiyorum örneğin . Ancak bu soru benim örneğim ile ilgili değil. SQL sorgularında tekrarlama ile ilgilidir. Bu nedenle, cevaplar ( JackP , Leigh ) şimdiye kadar daha iyi sorgular yazarak tekrarlanabilirliği azaltabileceğinizi göstermek için harika bir iş çıkarıyor . Ancak o zaman bile zaman , görünüşe göre kaldırılamayan bazı tekrarlılıklarla karşılaşıyorsunuz: Bu, beni her zaman SQL ile karıştırdı. "Geleneksel" programlama dillerinde, koddaki tekrarlanabilirliği en aza indirgemek için epeyce refactor yapabilirim, ancak SQL ile, daha az tekrarlayan bir ifade yazmak dışında, buna izin veren (?) Araçların olmadığı anlaşılıyor.

Oracle etiketini tekrar kaldırdığımı, başka bir şeye izin veren veritabanı veya komut dosyası dili olup olmadığına gerçekten ilgi duyacağımı unutmayın.


İşte bugün birlikte topladığım bir mücevher. Temel olarak tek bir tablonun sütun kümesindeki farkı bildirir. Lütfen aşağıdaki kodu gözden geçirin, esp. sonunda büyük sorgu. Aşağıda devam edeceğim.

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

Gördüğünüz gibi, bir "fark raporu" oluşturmak için sorgu aynı SQL SELECT bloğunu 5 kez kullanır (kolayca 42 kez olabilir!). Bu kesinlikle beyin ölü gibi beni vurur (sonuçta ben kodu yazdım bunu söyleyebilirim), ama bu iyi bir çözüm bulamadım.

  • Bu bazı gerçek uygulama kodunda bir sorgu olacağını, ben olabilir birlikte bu sorguyu cobbles fonksiyon yazmak bir dize olarak ve sonra bir dize olarak sorguyu yürütmek istiyorum.

    • -> Bina dizeleri test etmek ve korumak için korkunç ve korkunç. Eğer "uygulama kodu" PL / SQL gibi bir dilde yazılmışsa, o kadar yanlıştır ki acıtıyor.
  • Alternatif olarak, PL / SQL veya benzerlerinden kullanılırsa, bu sorguyu daha sürdürülebilir hale getirmek için bazı prosedürel araçlar olacağını tahmin ediyorum.

    • -> Sadece kod tekrarını önlemek için tek bir sorguda ifade edilebilecek bir şeyi prosedürel adımlara açmak da yanlış geliyor.
  • Bu sorgu veritabanında bir görünüm olarak gerekliyse, o zaman - anladığım kadarıyla - yukarıda gönderdiğim gibi görünüm tanımını korumaktan başka bir yol yoktur. (!!?)

    • -> Aslında yukarıdaki açıklama çok uzakta değildi bir kez 2 sayfalık görünüm tanımı üzerinde bazı bakım yapmak zorunda kaldı. Açıkçası, bu görünümdeki herhangi bir şeyin değiştirilmesi, görünüm tanımı üzerinde aynı alt ifadenin başka bir satırda kullanılıp kullanılmadığı ve orada değiştirilmesinin gerekip gerekmediğine ilişkin bir regexp metin araması gerektirdi.

Peki, başlık ilerledikçe - bu tür iğrençlikleri yazmak zorunda kalmamak için hangi teknikler var?

Yanıtlar:


13

Çok mütevazısınız - üstlendiğiniz görev göz önüne alındığında SQL'iniz iyi ve kısaca yazılmış. Birkaç işaretçi:

  • t1.name <> t2.nameher zaman doğruysa t1.name = REPLACE(t2.name, 'DUP_', '')- birincisini bırakabilirsiniz
  • genellikle istersiniz union all. daha sonra kopyaları bırakın unionanlamına gelir union all. Bu durumda bir fark yaratmayabilir, ancak union allkopyaları açıkça bırakmak istemiyorsanız, her zaman kullanmak iyi bir alışkanlıktır.
  • Varchar'a döküm yaptıktan sonra sayısal karşılaştırmaların yapılmasını istiyorsanız , aşağıdakileri dikkate almaya değer olabilir:

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

    ikinci görünüm bir tür unpivotişlemdir - en az 11 g üzerinde iseniz, bu unpivotmaddeyi daha kısaca yapabilirsiniz - bir örnek için buraya bakın

  • SQL'de yapabilirseniz, prosedürel rotadan aşağı gitmeyin diyorum ama ...
  • Dinamik SQL, test ve bakımla ilgili belirttiğiniz sorunlara rağmen muhtemelen dikkate değer

--DÜZENLE--

Sorunun daha genel yönüne cevap vermek için, SQL'deki tekrarlamayı azaltmak için aşağıdakiler de dahil olmak üzere teknikler vardır:

Ancak OO fikirlerini doğrudan SQL dünyasına getiremezsiniz - çoğu durumda sorgu okunabilir ve iyi yazılmışsa tekrarlama iyidir ve sadece tekrardan kaçınmak için dinamik SQL'e (örneğin) başvurmak akıllıca olmaz .

Leigh'in önerdiği değişikliği ve bir görünüm yerine bir CTE'yi içeren son sorgu şöyle görünebilir:

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, kısmen için UNION ALL. Genellikle UNIONolmadan ALLgenellikle ( 'BİRLİĞİ' etkili bir şekilde olduğu gibi gerekli sıralama işlemi için geçici depolama için bir makara ile sonuçlanır UNION ALLardından DISTINCTbir tür anlamına gelir) bu nedenle bazı durumlarda performans farkı çok büyük olabilir.
David Spillett

7

JackPDouglas (+1) tarafından sağlanan 11g'den önceki sürümlerde çalışan ve daha az tam tablo taraması yapan test_attribs_unpivot görünümüne bir alternatif :

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

Son sorgusu bu görünümle değişmeden kullanılabilir.


Çok daha iyi! Sanırım oyuncuları bile düşürebilir misin?
Jack Douglas

Yerine SELECT rownum MyRow FROM test_attribs where rownum<=5kullanımı select level MyRow from dual connect by level <= 5. Tüm bu mantıksal işlemlerin sadece 5 satır oluşturmak istemezsiniz.
Štefan Oravec

@ Štefan Oravec - Ben böyle vardı, ama ben değiştirdim çünkü hiyerarşik sorgu hangi sürümleri için kullanılabilir olduğundan emin değildi. En azından sürüm 8'den beri mevcut olduğu için değiştireceğim.
Leigh Riffel

4

Sıklıkla yeni, silinmiş veya değiştirilmiş satırlar için bir tablonun iki sürümünü karşılaştırmak için benzer bir sorunla karşılaşıyorum. Bir ay önce burada PowerShell kullanarak SQL Server için bir çözüm yayınladım .

Sorununuza uyarlamak için önce orijinali yinelenen satırlardan ayırmak için iki görünüm oluşturuyorum

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

ve sonra değişiklikleri

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;

Buradan orijinal kimliklerinizi bulabilirim

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: EKSİ ve BİRLİK ve GRUP TARAFINDAN farklı NULL'lara eşit muamele edin. Bu işlemleri kullanmak sorguları daha zarif hale getirir.

SQL Server kullanıcıları için ipucu: MINUS orada EXCEPT olarak adlandırılır, ancak benzer şekilde çalışır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.