Alternativen zur Verkettung von Zeichenfolgen oder zur Vorgehensweise, um die Wiederholung von SQL-Abfragecodes zu verhindern?

19

Haftungsausschluss: Bitte nehmen Sie mich als jemanden mit, der nur einen winzigen Teil seiner Arbeitszeit mit Datenbanken verbringt. (Die meiste Zeit mache ich C ++ - Programmierung in meinem Job, aber jeden ungeraden Monat muss ich etwas in einer Oracle-Datenbank suchen / reparieren / hinzufügen.)

Ich musste wiederholt komplexe SQL-Abfragen schreiben, sowohl für Ad-hoc-Abfragen als auch für in Anwendungen integrierte Abfragen, bei denen große Teile der Abfragen nur "Code" wiederholten.

Schreiben solche Abscheulichkeiten in einer traditionellen Programmiersprache würden Sie in großen Schwierigkeiten bekommen, aber ich ( I ) haben noch nicht in der Lage gewesen , eine anständige Technik zu Verhindern , dass SQL - Abfrage - Code Wiederholung zu finden.


Edit: 1. Ich möchte den Antwortenden danken, die mein ursprüngliches Beispiel hervorragend verbessert haben . Bei dieser Frage geht es jedoch nicht um mein Beispiel. Es geht um die Wiederholbarkeit von SQL-Abfragen. Insofern zeigen die bisherigen Antworten ( JackP , Leigh ) hervorragend, dass Sie die Wiederholungsrate reduzieren können, indem Sie bessere Abfragen schreiben . Aber selbst dann gibt es Wiederholungen, die anscheinend nicht zu beseitigen sind: Das hat mich immer mit SQL geärgert. In "traditionellen" Programmiersprachen kann ich eine ganze Menge umgestalten, um die Wiederholungen im Code zu minimieren, aber mit SQL scheint es keine (?) Werkzeuge zu geben, die dies ermöglichen, außer dass eine weniger sich wiederholende Anweisung geschrieben wird.

Beachten Sie, dass ich das Oracle-Tag wieder entfernt habe, da ich wirklich interessiert wäre, ob es keine Datenbank oder Skriptsprache gibt, die mehr zulässt.


Hier ist ein solches Juwel, das ich heute zusammengeschustert habe. Grundsätzlich wird der Unterschied in einer Reihe von Spalten einer einzelnen Tabelle angegeben. Bitte überfliegen Sie den folgenden Code. die große Abfrage am Ende. Ich werde weiter unten fortfahren.

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

Wie Sie sehen, verwendet die Abfrage zum Generieren eines "Differenzberichts" den gleichen SQL SELECT-Block fünfmal (kann leicht 42-mal sein!). Das kommt mir absolut hirntot vor (das darf ich sagen, nachdem ich den Code geschrieben habe), aber ich konnte keine gute Lösung dafür finden.

  • Wenn dies eine Abfrage in einem tatsächlichen Anwendungscode wäre, könnte ich eine Funktion schreiben, die diese Abfrage als Zeichenfolge zusammenfügt, und dann die Abfrage als Zeichenfolge ausführen.

    • -> Strings bauen ist schrecklich und schrecklich zu testen und zu warten. Wenn der "Anwendungscode" in einer Sprache wie PL / SQL geschrieben ist, fühlt er sich so falsch an, dass es weh tut.
  • Wenn Sie eine PL / SQL-Version oder eine ähnliche Version verwenden, kann diese Abfrage möglicherweise mithilfe bestimmter Verfahren besser gewartet werden.

    • -> Etwas, das in einer einzelnen Abfrage ausgedrückt werden kann, in prozedurale Schritte zu zerlegen, um eine Wiederholung des Codes zu verhindern, fühlt sich ebenfalls falsch an.
  • Wenn diese Abfrage als Ansicht in der Datenbank benötigt würde, gäbe es meines Wissens keine andere Möglichkeit, als die Ansichtsdefinition wie oben angegeben beizubehalten. (!!?)

    • -> Ich musste tatsächlich einige Wartungsarbeiten an einer 2-Seiten-Ansichtsdefinition durchführen, sobald dies nicht mehr weit von der obigen Aussage entfernt war. Offensichtlich erforderte das Ändern von Elementen in dieser Ansicht eine reguläre Textsuche über der Ansichtsdefinition, um festzustellen, ob dieselbe Unteranweisung in einer anderen Zeile verwendet wurde und ob sie dort geändert werden musste.

Also, wie der Titel schon sagt - welche Techniken gibt es, um zu verhindern, dass solche Abscheulichkeiten geschrieben werden müssen?

Martin
quelle

Antworten:

13

Sie sind zu bescheiden - Ihr SQL ist gut und präzise geschrieben, je nachdem, welche Aufgabe Sie übernehmen. Ein paar Hinweise:

  • t1.name <> t2.nameist immer wahr, wenn t1.name = REPLACE(t2.name, 'DUP_', '')- Sie die erstere fallen lassen können
  • normalerweise willst du union all. unionBedeutet union alldann Duplikate ablegen. In diesem Fall macht es vielleicht keinen Unterschied, aber es ist immer union alleine gute Angewohnheit, wenn Sie keine Duplikate explizit löschen möchten.
  • Wenn Sie bereit sind, die numerischen Vergleiche nach dem Casting in varchar durchzuführen, sollten Sie Folgendes berücksichtigen:

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

    Die zweite Ansicht ist eine Art unpivotOperation - wenn Sie mindestens 11 g haben, können Sie dies mit der unpivotKlausel präziser machen - siehe hier für ein Beispiel

  • Ich sage, gehen Sie nicht den prozeduralen Weg, wenn Sie es in SQL tun können, aber ...
  • Dynamisches SQL ist wahrscheinlich eine Überlegung wert, trotz der Probleme, die Sie beim Testen und bei der Wartung angesprochen haben

--BEARBEITEN--

Um die allgemeinere Seite der Frage zu beantworten, gibt es Techniken, um die Wiederholung in SQL zu reduzieren, einschließlich:

Aber man kann OO Ideen in die SQL - Welt nicht direkt bringen - in vielen Fällen Wiederholung ist in Ordnung , wenn die Abfrage lesbar und gut geschrieben, und es wäre unklug, dynamische SQL greifen (zum Beispiel) nur um Wiederholungen zu vermeiden.

Die abschließende Abfrage mit Leighs Änderungsvorschlag und einem CTE anstelle einer Ansicht könnte ungefähr so ​​aussehen:

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);
Jack Douglas
quelle
1
+1, teilweise für UNION ALL. Oft UNIONohne ALLführt in der Regel ein Spool zum temporären Speichern für die erforderliche Sortieroperation (da 'UNION' effektiv UNION ALLgefolgt wird, DISTINCTwas eine Sortierung impliziert), kann der Leistungsunterschied in einigen Fällen sehr groß sein.
David Spillett
7

Hier ist eine Alternative zu der von JackPDouglas (+1) bereitgestellten Ansicht test_attribs_unpivot , die in Versionen vor 11g funktioniert und weniger vollständige Tabellenscans durchführt:

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

Seine letzte Abfrage kann mit dieser Ansicht unverändert verwendet werden.

Leigh Riffel
quelle
Viel besser! Ich denke, Sie können sogar die Besetzung fallen lassen?
Jack Douglas
Anstatt zu SELECT rownum MyRow FROM test_attribs where rownum<=5benutzen select level MyRow from dual connect by level <= 5. Sie möchten nicht, dass alle diese logischen Verknüpfungen nur zum Erstellen von 5 Zeilen verwendet werden.
Štefan Oravec
@ Štefan Oravec - Ich hatte es so, aber ich habe es geändert, weil ich nicht sicher war, für welche Versionen hierarchische Abfragen verfügbar waren. Da es seit mindestens Version 8 verfügbar ist, werde ich es ändern.
Leigh Riffel
4

Ich stoße oft auf das ähnliche Problem, zwei Versionen einer Tabelle auf neue, gelöschte oder geänderte Zeilen zu vergleichen. Vor einigen Monaten veröffentlichte ich eine Lösung für SQL Server Powershell hier .

Um es an Ihr Problem anzupassen, erstelle ich zwei Ansichten, um das Original von den doppelten Zeilen zu trennen

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

und dann überprüfe ich die Änderungen mit

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;

Von hier aus kann ich Ihre Original-IDs finden

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;

Übrigens: MINUS und UNION und GROUP BY behandeln unterschiedliche NULL-Werte als gleich. Durch die Verwendung dieser Operationen werden die Abfragen eleganter.

Hinweis für SQL Server-Benutzer: MINUS heißt dort EXCEPT, funktioniert aber ähnlich.

bernd_k
quelle