Alternative alla concatenazione di stringhe o andare procedurale per prevenire codice di query SQL ripetizione?

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

  •  16-10-2019
  •  | 
  •  

Domanda

Disclaimer: Si prega di portare con me come qualcuno che utilizza solo i database una piccola frazione del suo tempo di lavoro. (La maggior parte delle volte che faccio C ++ la programmazione in mio lavoro, ma ogni mese dispari ho bisogno di cercare / fix / aggiungere qualcosa in un database Oracle.)

Ho più volte necessarie per scrivere query SQL complesse, sia per le query ad-hoc e per le query integrati in applicazioni in cui gran parte delle query in cui appena ripetuto "codice".

Scrivendo tali abomini in un linguaggio di programmazione tradizionale si otterrebbe nei guai, ma io ( ) sono ancora stati in grado di trovare qualsiasi tecnica decente per evitare SQL codice di query ripetizione.


Modifica prima, voglio ringraziare i answerers che hanno fornito ottimi miglioramenti alla mia originale Esempio . Tuttavia, la questione non riguarda il mio esempio. Si tratta di ripetitività nelle query SQL. Come tale, le risposte ( JackP , Leigh ) finora fare un grande lavoro di mostrare che si possibile ridurre ripetitività scrivendo query migliori . Tuttavia, anche poi che si faccia un po 'di ripetitività che, apparentemente, non può essere rimosso: questo sempre mi tormentava con SQL. In "tradizionali" linguaggi di programmazione posso refactoring un bel po 'per ridurre al minimo ripetitività nel codice, ma con SQL sembra che non ci sono (?) Gli strumenti che permettono di questo, tranne che per la scrittura di una dichiarazione meno ripetitivo per cominciare.

Si noti che ho rimosso di nuovo il tag di Oracle, come vorrei essere realmente interessati se non c'è alcun linguaggio di database o di scripting che permette di qualcosa di più.


Ecco un tale gioiello che ho messo insieme oggi. Riporta fondamentalmente la differenza in una serie di colonne di una singola tabella. Si prega di sfogliare il codice seguente, esp. la grande interrogazione alla fine. Io continuerò qui di seguito.

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

Come si può vedere, la query per generare un "rapporto differenza" utilizza le stesse SQL SELECT blocco 5 volte (potrebbe facilmente essere 42 volte!). Questo mi sembra assolutamente morte cerebrale (mi è permesso di dire questo, dopo tutto quello che ha scritto il codice), ma non sono stato in grado di trovare alcuna soluzione buona per questo.

  • Se questo sarebbe una query in un certo codice di applicazione effettiva, I potrebbe scrivere una funzione che ciottoli insieme questa interrogazione come una stringa e poi mi piacerebbe eseguire la query come una stringa.

    • -> Costruire stringhe è orribile e orribile per testare e mantenere. Se il "codice di applicazione" è scritto in un linguaggio come PL / SQL si sente in modo sbagliato fa male.
  • In alternativa, se utilizzato da PL / SQL o il suo simile, direi vi sono alcuni strumenti procedurali per rendere la query più gestibile.

    • -.> Svolgitore qualcosa che può essere espresso in una singola query in fasi procedurali solo per evitare la ripetizione di codice si sente male troppo
  • Se questa query sarebbe necessaria come vista nel database, quindi - per quanto ho capito - non ci sarebbe altro modo che in realtà mantenere la definizione della vista, come ho postato sopra. (!!?)

    • -> Ho dovuto fare un po 'di manutenzione su una definizione di vista di 2 pagine, una volta che non era lontano dichiarazione di cui sopra. Ovviamente, cambiare nulla in questa ottica ha richiesto un ricerca regexp sopra la definizione della vista per se stesso sub-dichiarazione era stata utilizzata in un'altra linea e se aveva bisogno di cambiare lì.

Quindi, come dice il titolo va? - Quali tecniche è quello di evitare di dover scrivere tali abomini

È stato utile?

Soluzione

sono troppo modesti - SQL è ben scritto e conciso il compito si stanno intraprendendo. Alcune indicazioni:

  • t1.name <> t2.name è sempre vero se t1.name = REPLACE(t2.name, 'DUP_', '') - si può cadere l'ex
  • di solito si desidera union all. mezzi union union all poi cadere duplicati. Si potrebbe fare alcuna differenza in questo caso, ma sempre utilizzando union all è una buona abitudine a meno che non si vuole in modo esplicito a cadere tutti i duplicati.
  • se si è disposti per i confronti numerici per accadere dopo colata a varchar, quanto segue potrebbe valere la pena considerare:

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

    il secondo punto di vista è un tipo di operazione unpivot - se siete su almeno 11g si può fare questo in modo più conciso con il unpivot clausola - vedi qui per un esempio

  • Sono dire non imboccare la strada procedurale se si può farlo in SQL, ma ...
  • SQL dinamico è probabilmente opportuno prendere in considerazione, nonostante i problemi, si fa riferimento con il collaudo e la manutenzione

- EDIT -

Per rispondere alla parte più generale della questione, ci sono tecniche per ridurre la ripetizione in SQL, tra cui:

Ma non si può portare idee OO nel mondo SQL direttamente - in molti casi la ripetizione è bene se la query è leggibile e ben scritto, e non sarebbe saggio a ricorrere a SQL dinamico (per esempio) solo per evitare ripetizioni.

La query finale compreso il cambiamento suggerito di Leigh e un CTE, invece di una vista potrebbe essere simile a questo:

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

Altri suggerimenti

Ecco un alternativa alla vista test_attribs_unpivot fornito da JackPDouglas (+ 1) che funziona nelle versioni prima di 11g e fa un minor numero di scansioni di tabella completa:

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

La sua domanda finale può essere utilizzato invariato con questa visione.

I incontrano spesso il problema simile per confrontare due versioni di un tavolo per le nuove righe, cancellati o modificati. Qualche mese fa ho pubblicato una soluzione per SQL Server utilizzando PowerShell qui .

Per adattarlo al vostro problema, prima creo due viste per separare l'originale dalle righe duplicate

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

e poi verifico le modifiche con

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;

Da qui posso trovare l'originale 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;

A proposito: MENO e UNION e GROUP BY trattare diversi NULL di come uguali. Utilizzando queste operazioni rende le query più elegante.

Suggerimento per gli utenti di SQL Server: MINUS è chiamato TRANNE lì, ma funziona in modo simile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top