Alternativen zu verketten von Zeichenfolgen oder gehen prozedurale um zu verhindern, dass SQL-Abfrage-code Wiederholung?

dba.stackexchange https://dba.stackexchange.com/questions/2710

  •  16-10-2019
  •  | 
  •  

Frage

Haftungsausschluss:Bitte Geduld mit mir, als jemand, der nur Datenbanken verwendet einen winzigen Bruchteil seiner Arbeitszeit.(Die meisten der Zeit, die ich mit C++ - Programmierung in meinem job, aber in jedem ungeraden Monat muss ich suchen/beheben/etwas etwas hinzufügen, in eine Oracle-Datenbank.)

Ich habe immer wieder benötigt zum schreiben komplexer SQL-Abfragen, sowohl für ad-hoc-Abfragen und Abfragen erstellt werden, die in Anwendungen, wo große Teile der Abfragen, in denen nur wiederholt "code".

Das schreiben solcher Greuel in einer traditionellen Programmiersprache würden Sie in großen Schwierigkeiten sind, aber ich (Ich) wurden bisher nicht in der Lage zu finden, jede anständige Technik, um zu verhindern, dass SQL-Abfrage-code-Wiederholung.


Edit: 1., möchte ich danken, die Antworten besitzen die hervorragende Verbesserungen an meinen ursprünglichen Beispiel.Jedoch, diese Frage ist nicht über mein Beispiel.Es geht um die Wiederholung in SQL-Abfragen.Als solche, die Antworten (JackP, Leigh) so weit machen einen tollen job, die zeigen, dass Sie kann reduzieren die Wiederholung durch das schreiben besser Abfragen.Aber auch dann Sie Gesicht einige wiederholungshäufigkeit, die scheinbar nicht entfernt werden können:Dies ist immer beschwor mich mit SQL.In "klassischen" Programmiersprachen kann ich umgestalten einiges zu minimieren, Wiederholung in den code, aber mit SQL es scheint, dass es keine(?) tools, die dies zulassen, außer für das schreiben einer weniger repetitive Aussage zu beginnen.

Beachten Sie, dass ich Sie entfernt habe die Oracle-tag wieder, als wäre ich wirklich interessiert, ob es gibt keine Datenbank-oder Skriptsprache, die ermöglicht für etwas mehr.


Hier ist so ein Juwel, das ich zusammengebastelt heute.Es wird hauptsächlich die Berichte der Unterschied in der eine Reihe von Spalten mit einer einzelnen Tabelle.Bitte Blättern Sie durch den folgenden code, esp.die große Abfrage am Ende.Ich werde weiter unten.

--
-- 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 können, ist die Abfrage zu generieren, die eine "difference report" verwendet die gleiche SQL-SELECT-block 5-mal (könnte leicht sein 42-mal!).Das scheint mir absolut hirntot (ich darf das so sagen, nach allem, was ich schrieb den code), aber ich habe nicht in der Lage zu finden, eine gute Lösung.

  • Wäre dies eine Abfrage in der eigentlichen Anwendungscode, ich könnte schreiben Sie eine Funktion, die Pflastersteine zusammen diese Abfrage als string und dann würde ich ausführen Abfrage als Zeichenfolge.

    • -> Building strings ist schrecklich, und schrecklich, zu testen und zu pflegen.Wenn die "Anwendungs-code" ist geschrieben in einer Sprache wie PL/SQL es fühlt sich so falsch, dass es weh tut.
  • Alternativ, wenn verwendet von PL/SQL oder wie Ihre, ich denke, es werden einige prozessuale Mittel, um diese Abfrage mehr wartbar.

    • -> Ausrollen etwas, das ausgedrückt werden kann in eine einzelne Abfrage in prozessuale Schritte, nur um zu verhindern, dass code-Wiederholung fühlt sich geirrt.
  • Wenn Sie diese Abfrage benötigt würden, wie ein Blick in die Datenbank, dann - soweit ich das verstehe - würde es keine andere Weise, als tatsächlich pflegen die view definition-als ich oben gepostet.(!!?)

    • -> Ich eigentlich zu tun hatte, einige Wartungsarbeiten auf ein 2-Seiten-Ansicht-definition einmal, das war nicht weit Weg obige Aussage.Offensichtlich ändert sich nichts in dieser Sicht erforderlich, ein regexp-text Suche über die view definition-ob in der gleichen sub-Anweisung verwendet worden waren, in einer anderen Linie, und ob es geändert werden musste es.

So, wie der Titel geht - welche Techniken gibt es, um zu verhindern, dass zu schreiben, solche Greuel?

War es hilfreich?

Lösung

Sie sind zu bescheiden - Ihr SQL ist gut und übersichtlich geschrieben angesichts der Aufgabe, die Sie Unternehmen.Ein paar Hinweise:

  • t1.name <> t2.name ist immer wahr, wenn t1.name = REPLACE(t2.name, 'DUP_', '') - Sie können die drop-ehemalige
  • in der Regel will man union all. union Mittel union all dann legen Sie Duplikate.Es könnte keinen Unterschied machen, in diesem Fall aber immer mit union all ist eine gute Angewohnheit, es sei denn, Sie explizit löschen Sie keine Duplikate.
  • wenn Sie bereit sind, für den numerischen Vergleiche zu passieren nach casting varchar, folgende überlegung Wert:

    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 unpivot Bedienung - wenn Sie auf mindestens 11g Sie können dies tun, mehr bündig mit der unpivotKlausel - siehe hier für ein Beispiel

  • Ich sage nicht gehen, der prozessuale Weg, wenn Sie können, tun Sie es in SQL, aber...
  • Dynamische SQL-ist wohl eine überlegung Wert, trotz der Probleme, die Sie erwähnen, mit die Prüfung und Wartung

--BEARBEITEN--

Die Antwort auf die mehr Allgemeine Seite der Frage, gibt es Techniken zur Verringerung der Wiederholung in SQL, einschließlich:

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

Die Letzte Abfrage mit Leigh vorgeschlagene änderung und CTE anstelle einer Ansicht könnte in etwa 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);

Andere Tipps

Hier finden Sie eine Alternative zur von Jackpdouglas bereitgestellten test_attribs_unpivot -Ansicht (+1) Das funktioniert in Versionen vor 11G und macht weniger vollständige Tisch -Scans:

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 endgültige Abfrage kann mit dieser Ansicht unverändert verwendet werden.

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

Um es an Ihr Problem anzupassen, erstelle ich zunächst 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 finde ich Ihre ursprünglichen IDs

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 Gruppe, indem sie verschiedene Null als gleich behandeln. Die Verwendung dieser Operationen macht die Anfragen eleganter.

Hinweis für SQL Server -Benutzer: Minus ist außer dort benannt, funktioniert aber ähnlich.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit dba.stackexchange
scroll top