Perché SQL non è più refactorable? [chiuso]


39

Tutti sanno che i nuovi sviluppatori scrivono lunghe funzioni. Man mano che avanzi, riesci a dividere il codice in pezzi più piccoli e l'esperienza ti insegna il valore di farlo.

Inserisci SQL. Sì, il modo SQL di pensare al codice è diverso dal modo procedurale di pensare al codice, ma questo principio sembra altrettanto applicabile.

Diciamo che ho una query che assume la forma:

select * from subQuery1 inner join subQuerry2 left join subquerry3 left join join subQuery4 

Utilizzo di alcuni ID o date ecc.

Tali sottoquery sono complesse e possono contenere sottoquery proprie. In nessun altro contesto di programmazione penserei che la logica per le sottoquery complesse 1-4 sia in linea con la mia query padre che le unisce tutte. Sembra così semplice che quelle sottoquery dovrebbero essere definite come viste, proprio come sarebbero funzioni se stessi scrivendo un codice procedurale.

Allora perché non è quella pratica comune? Perché le persone scrivono così spesso queste lunghe query monolitiche SQL? Perché SQL non incoraggia un ampio utilizzo della vista, proprio come la programmazione procedurale incoraggia un ampio utilizzo delle funzioni. (In molti ambienti aziendali, la creazione di viste non è nemmeno facile da eseguire. Sono necessarie richieste e approvazioni. Immagina se altri tipi di programmatori dovessero inviare una richiesta ogni volta che hanno creato una funzione!)

Ho pensato a tre possibili risposte:

  1. Questo è già comune e sto lavorando con persone inesperte

  2. I programmatori esperti non scrivono SQL complessi perché preferiscono risolvere i problemi di elaborazione dei dati con codice procedurale

  3. Qualcos'altro


12
Esistono organizzazioni che consentono di interrogare un database solo attraverso le visualizzazioni e modificarlo tramite procedure memorizzate.
Pieter B,

3
SQL è diventato molto più divertente per me quando ho finalmente accettato che non sarebbe mai stato così ASCIUTTO come il mio normale codice procedurale.
Graham,

1
4. SQL è davvero vecchio e non è stato materialmente aggiornato da decenni. Per cose super complesse, molti team optano per le procedure memorizzate. È possibile aggiungere diverse clausole per questo. A volte devi solo eseguire lavori per mettere in scena i dati in una tabella temporanea e poi partecipare su quello. Ecco come sono diversi i linguaggi dichiarativi e procedurali.
Berin Loritsch,

8
Un altro motivo è che esiste un orribile problema di prestazioni chiamato "join triangolare" che può accadere quando si utilizzano le viste (ovviamente per caso). Se la query si unisce Vista A e B View, ma Vista A anche nella sua attuazione ri-uso View B, si inizia a vedere il problema. Quindi la gente spesso inizia scrivendo una singola query monolitica per essere in grado di vedere cosa effettivamente funzionerebbe meglio in termini di refactoring alle viste, e quindi i loro risultati di scadenza scendono e il monolite va in produzione. Un po 'come il 98% di tutti gli sviluppatori di software, davvero :) :)
Stephen Byrne,

3
"Immagina se altri tipi di programmatori dovessero inviare una richiesta ogni volta che hanno creato una funzione" ... umm. Non fai recensioni di codice?
svidgen,

Risposte:


25

Penso che il problema principale sia che non tutti i database supportano le espressioni comuni delle tabelle.

Il mio datore di lavoro utilizza DB / 2 per molte cose. Le ultime versioni supportano CTE, in modo tale che sono in grado di fare cose come:

with custs as (
    select acct# as accountNumber, cfname as firstName, clname as lastName,
    from wrdCsts
    where -- various criteria
)
, accounts as (
    select acct# as accountNumber, crBal as currentBalance
    from crzyAcctTbl
)
select firstName, lastName, currentBalance
from custs
inner join accounts on custs.accountNumber = accounts.accountNumber

Il risultato è che possiamo avere nomi di tabelle / campi fortemente abbreviati e sto essenzialmente creando viste temporanee, con nomi più leggibili, che posso quindi usare. Certo, la query si allunga. Ma il risultato è che posso scrivere qualcosa che è abbastanza chiaramente separato (usando CTE nel modo in cui useresti le funzioni per ottenere DRY) e finire con un codice che è abbastanza leggibile. E poiché sono in grado di suddividere le mie subquery e avere un riferimento di subquery a un altro, non è tutto "in linea". Ho, a volte, scritto un CTE, poi ho avuto altri quattro CTE a cui fa riferimento, quindi ho avuto l'unione della query principale con i risultati di questi ultimi quattro.

Questo può essere fatto con:

  • DB / 2
  • PostGreSQL
  • Oracolo
  • MS SQL Server
  • MySQL (ultima versione; ancora un po 'nuovo)
  • probabilmente altri

Ma fa molto per rendere il codice più pulito, più leggibile, più ASCIUTTO.

Ho sviluppato una "libreria standard" di CTE che posso collegare a varie query, iniziandomi in volo con la mia nuova query. Alcuni di loro stanno iniziando ad essere abbracciati anche da altri sviluppatori nella mia organizzazione.

Con il passare del tempo, potrebbe avere senso trasformarne alcune in visualizzazioni, in modo tale che questa "libreria standard" sia disponibile senza la necessità di copiare / incollare. Ma i miei CTE finiscono per essere modificati, anche così leggermente, per varie esigenze che non sono stato in grado di avere un singolo CTE usato così tanto, senza mod, che potrebbe valere la pena creare una vista.

Sembrerebbe che parte della tua lamentela sia "perché non conosco i CTE?" o "perché il mio DB non supporta CTE?"

Per quanto riguarda gli aggiornamenti ... sì, puoi usare i CTE ma, nella mia esperienza, devi usarli all'interno della clausola set AND nella clausola where. Sarebbe bello se potessi definirne una o più prima dell'intera istruzione di aggiornamento e quindi avere solo le parti "query principale" nelle clausole set / where ma non funziona in questo modo. E non è possibile evitare nomi di tabella / campi oscuri sulla tabella che si sta aggiornando.

È possibile utilizzare CTE per le eliminazioni. Potrebbero essere necessari più CTE per determinare i valori PK / FK per i record che si desidera eliminare da quella tabella. Ancora una volta, non puoi evitare nomi di tabella / campo oscuri sulla tabella che stai modificando.

Nella misura in cui è possibile effettuare una selezione in un inserto, è possibile utilizzare CTE per inserti. Come sempre, potresti avere a che fare con nomi di tabella / campi oscuri sulla tabella che stai modificando.

SQL NON consente di creare l'equivalente di un oggetto dominio, avvolgendo una tabella, con getter / setter. Per questo, dovrai usare un ORM di qualche tipo, insieme a un linguaggio di programmazione più procedurale / OO. Ho scritto cose di questo tipo in Java / Hibernate.


4
Il signor Big CTE era l'uomo che scriveva il peggior SQL. Il problema era che i CTE erano scelte di astrazione scadenti e l'ottimizzatore non può annullare tutti gli algoritmi con la testa d'ossa che hai inserito.
Joshua,

3
Anche ORM può fare alcune cose piuttosto atroci dal punto di vista delle prestazioni ... specialmente quando stai usando solo getter e setter per recuperare un sacco di dati. Hibernate è noto per l'utilizzo di centinaia di query singole anziché di una query di grandi dimensioni, il che è un problema quando si verifica un sovraccarico su ogni query.
user3067860,

2
@Joshua Puoi scrivere codice errato in qualsiasi lingua. Incluso SQL. Ma il refactoring ai CTE, eseguito correttamente, può creare progetti dal basso verso l'alto che sono più facili da analizzare per gli umani. Tendo a vederlo come un tratto desiderabile, indipendentemente dalla lingua con cui ho a che fare :-)
Meower68,

2
Le altre risposte sono fantastiche, ma questo è quello che cercavo personalmente. "Perché non conosco i CTE" era la maggior parte del mio problema.
Ebrts,

2
@ Meower68 Non esiste il rischio che un uso estensivo di CTE impedisca alle persone di apprendere correttamente e di apprendere una buona progettazione del database? Sostengo il valore di CTE ma rende anche troppo facile lavorare con le subquery, dove non dovresti.
Pieter B,

36

Il blocco della creazione di visualizzazioni del database viene spesso eseguito da organizzazioni paranoiche di problemi di prestazioni nel database. Questo è un problema di cultura organizzativa, piuttosto che un problema tecnico con SQL.

Oltre a ciò, le grandi query monolitiche SQL vengono scritte più volte, poiché il caso d'uso è così specifico che è possibile riutilizzare veramente poco del codice SQL in altre query. Se è necessaria una query complessa, di solito è per un caso d'uso molto diverso. Copiare l'SQL da un'altra query è spesso un punto di partenza, ma a causa delle altre subquery e JOIN nella nuova query, si finisce per modificare l'SQL copiato quanto basta per interrompere qualsiasi tipo di astrazione che una "funzione" in un'altra lingua sarebbe da usare per. Il che mi porta al motivo più importante per cui è difficile fare il refactoring di SQL.

SQL si occupa solo di strutture dati concrete, non di comportamenti astratti (o di un'astrazione in qualsiasi senso della parola). Dato che SQL è scritto attorno a idee concrete, non c'è nulla da astrarre in un modulo riutilizzabile. Le visualizzazioni del database possono essere d'aiuto in questo, ma non allo stesso livello di una "funzione" in un'altra lingua. Una vista del database non è tanto un'astrazione quanto una query. Bene, in realtà, una vista del database è una query. È essenzialmente usato come una tabella, ma eseguito come una sottoquery, quindi di nuovo hai a che fare con qualcosa di concreto, non astratto.

È con le astrazioni che il codice diventa più facile da refactoring, perché un'astrazione nasconde i dettagli dell'implementazione dal consumatore di quell'astrazione. Straight SQL non fornisce tale separazione, anche se le estensioni procedurali a SQL come PL / SQL per Oracle o Transact-SQL per SQL Server iniziano a confondere un po 'le linee.


"SQL si occupa solo di strutture dati concrete, non di comportamenti astratti (o di un'astrazione in qualsiasi senso della parola)." Questa è una strana affermazione, dal momento che dal mio punto di vista SQL si occupa interamente del comportamento astratto e non di una programmazione concreta in alcun senso della parola! Prendi in considerazione tutti i massicci gradi di complessità che sono astratti nella semplice parola "JOIN": dici che vuoi un risultato unito tratto da due diversi set di dati e lascia che sia il DBMS a determinare le tecniche concrete coinvolte, gestire indicizzazione, gestire la differenza tra tabelle e sottoquery, ecc ...
Mason Wheeler

5
@MasonWheeler: suppongo che stavo pensando a SQL più dal punto di vista dei dati su cui lavora, non dall'implementazione delle funzionalità del linguaggio. Le tabelle in un database non sembrano un'astrazione. Sono concreti, come in una tabella chiamata "numero_di_file" contiene numeri di telefono. Un numero di telefono non è un concetto astratto.
Greg Burghardt

12

La cosa che penso che potresti perdere dalla tua domanda / punto di vista è che SQL esegue le operazioni sui set (usando le operazioni sui set ecc.).

Quando si opera a quel livello, naturalmente, si rinuncia al controllo del motore. Puoi comunque forzare un po 'di codice di stile procedurale usando i cursori, ma come l'esperienza mostra 99/100 volte non dovresti farlo.

Il refactoring SQL è possibile ma non utilizza gli stessi principi di refactoring del codice a cui siamo abituati nel codice a livello di applicazione. Invece ottimizzi il modo in cui usi il motore SQL stesso.

Questo può essere fatto in vari modi. Se stai usando Microsoft SQL Server puoi usare SSMS per fornirti un piano di esecuzione approssimativo e puoi usarlo per vedere quali passaggi puoi fare per ottimizzare il tuo codice.

Nel caso della suddivisione del codice in moduli più piccoli, come menzionato da @ greg-burghardt, SQL è generalmente un pezzo di codice appositamente costruito e di conseguenza. Fa quell'unica cosa che ti serve e niente altro. Aderisce alla S in SOLID, ha solo un motivo per essere modificato / interessato ed è allora che hai bisogno di quella query per fare qualcos'altro. Il resto della sigla (OLID) non si applica qui (AFAIK non c'è iniezione di dipendenze, interfacce o dipendenze come tali in SQL) a seconda del sapore dell'SQL che stai usando potresti essere in grado di estendere determinate query avvolgendole in una stored procedure / tabella o utilizzandoli come sottoquery così, direi che il principio aperto-chiuso si applicherebbe comunque, in un certo senso. Ma sto divagando.

Penso che tu debba spostare il tuo paradigma in termini di come stai visualizzando il codice SQL. A causa della sua natura prestabilita, non è in grado di fornire molte delle funzionalità che i linguaggi di livello applicativo (generici, ecc.) Possono offrire. SQL non è mai stato progettato per essere qualcosa del genere, è un linguaggio per eseguire query su set di dati e ogni set è unico a modo suo.

Detto questo, ci sono modi in cui puoi rendere il tuo codice più bello, se la leggibilità è una priorità all'interno dell'organizzazione. Archiviazione di bit di blocchi SQL utilizzati di frequente (set di dati comuni utilizzati) in stored procedure / funzioni di valore di tabella e quindi interrogazione e archiviazione in tabelle / variabili di tabella temporanee, seguite dall'utilizzo di questi per unire i pezzi in un'unica transazione di massa che altrimenti scriveresti è un'opzione. IMHO non vale la pena fare qualcosa del genere con SQL.

Come lingua è progettato per essere facilmente leggibile e comprensibile da chiunque, anche dai non programmatori. Pertanto, a meno che tu non stia facendo qualcosa di molto intelligente, non è necessario riformattare il codice SQL in pezzi di dimensioni byte inferiori. Personalmente, ho scritto enormi query SQL mentre lavoravo su una soluzione ETL / Reporting di data warehouse e tutto era ancora molto chiaro in termini di ciò che stava succedendo. Qualunque cosa che potesse sembrare un po 'strana a chiunque altro avrebbe ricevuto un breve insieme di commenti per fornire una breve spiegazione.

Spero che aiuti.


6

Mi concentrerò sulle "sottoquery" nel tuo esempio.

Perché sono usati così spesso? Perché usano il modo naturale di pensare una persona: ho questo insieme di dati e voglio fare un'azione su un sottoinsieme di esso e unirli a un sottoinsieme di altri dati. 9 su 10 volte che vedo una sottoquery, è stata utilizzata in modo errato. La mia battuta in corso sulle subquery è: le persone che hanno paura dei join usano le subquery.

Se vedi tali sottoquery, spesso è anche un segno di una progettazione del database non ottimale.

Più il tuo database è normalizzato, più join ottieni, più il tuo database sembra un grande foglio Excel, più sottoselezioni ottieni.

Il refactoring in SQL ha spesso un obiettivo diverso: ottenere più prestazioni, tempi di query migliori, "evitare scansioni di tabelle". Questi potrebbero anche rendere il codice meno leggibile ma sono molto preziosi.

Allora perché vedi così tante enormi query monolitiche non refactored?

  • SQL, in molti modi non è un linguaggio di programmazione.
  • Cattiva progettazione del database.
  • Le persone non parlano molto bene SQL.
  • Nessun potere sul database (ad esempio non è consentito utilizzare le viste)
  • Obiettivi diversi con refactoring.

(per me, più esperienza ottengo con SQL, meno grandi sono le mie query, SQL ha modi per persone di tutti i livelli di abilità di svolgere il proprio lavoro, indipendentemente da cosa.)


6
Le "sottoquery" hanno la stessa probabilità di essere una certa aggregazione di un db correttamente normalizzato rispetto alla normalizzazione ad hoc di un db non normalizzato
Caleth,

@Caleth è così vero.
Pieter B,

5
Anche in database ben normalizzati è ancora spesso necessario unirsi alle sottoquery, piuttosto che unire direttamente con le tabelle. Ad esempio, se devi unirti a dati raggruppati.
Barmar

1
@Barmar sicuramente, quindi il mio commento su 9. Le sottoquery hanno il loro posto ma le vedo abusate da persone inesperte.
Pieter B,

Mi piace la tua metrica di "numero di sottoquery" come indicazione della normalizzazione del database (o della sua mancanza).
Jason

2

Separazione dei compiti

Nello spirito di SQL, il database è un bene condiviso che contiene i dati dell'azienda e proteggerlo è di vitale importanza. Entra nel DBA come guardiano del tempio.

La creazione di una nuova vista nel database è intesa come utile e condivisa da una comunità di utenti. Nella vista DBA, questo è accettabile solo se la vista è giustificata dalla struttura dei dati. Ogni modifica di una vista è quindi associata a rischi per tutti i suoi utenti attuali, anche quelli che non utilizzano l'applicazione ma che hanno scoperto la vista. Infine, la creazione di nuovi oggetti richiede la gestione delle autorizzazioni e, nel caso della vista, coerentemente con le autorizzazioni delle tabelle sottostanti.

Tutto ciò spiega perché agli amministratori di database non piace aggiungere visualizzazioni solo per il codice di una singola applicazione.

Progettazione SQL

Se decomponi una delle tue query complesse, potresti scoprire che le sottoquery necessitano spesso di un parametro che dipende da un'altra sottoquery.

Quindi trasformare le sottoquery in vista non è necessariamente semplice come indicato. È necessario isolare i parametri variabili e progettare la vista in modo che i parametri possano essere aggiunti come criteri di selezione nella vista.

Sfortunatamente, nel farlo, a volte imponi di accedere a più dati e in modo meno efficace rispetto a una query su misura.

Estensioni proprietarie

Potresti sperare in qualche refactoring, trasferendo alcune responsabilità alle estensioni procedurali di SQL, come PL / SQL o T-SQL. Tuttavia, questi dipendono dal fornitore e creano una dipendenza tecnologica. Inoltre, queste estensioni vengono eseguite sul server database, creando un carico di elaborazione maggiore su una risorsa che è molto più difficile da ridimensionare rispetto a un server applicazioni.

Ma qual è il problema alla fine?

Infine, la separazione dei compiti e la progettazione di SQL con i suoi punti di forza e le limitazioni sono un vero problema? Alla fine, questi database hanno dimostrato di gestire con successo e affidabilità dati molto critici, anche in ambienti mission-critical.

Quindi, al fine di ottenere un refactoring di successo:

  • prendere in considerazione una migliore comunicazione . Cerca di capire i vincoli del tuo DBA. Se si dimostra a un DBA che una nuova vista è giustificata dalle strutture di dati, che non si tratta di una soluzione temporanea e che non ha un impatto sulla sicurezza, acconsentirà sicuramente a consentirne la creazione. Perché, allora sarebbe un interesse condiviso.

  • pulisci prima la tua casa : niente ti costringe a generare molto SQL in molti posti. Rifattorizzare il codice dell'applicazione, isolare gli accessi SQL e creare le classi o le funzioni per fornire sottoquery riutilizzabili, se utilizzate frequentemente.

  • migliorare la consapevolezza del team : assicurarsi che l'applicazione non stia eseguendo attività che potrebbero essere eseguite in modo più efficiente dal motore DBMS. Come hai giustamente sottolineato, l'approccio procedurale e l'approccio orientato ai dati non sono ugualmente controllati da diversi membri del team. Dipende dal loro background. Ma per ottimizzare il sistema nel suo insieme, il tuo team deve comprenderlo nel suo insieme. Quindi crea consapevolezza, in modo da essere sicuro che i giocatori meno esperti non reinventino la ruota e condividano i loro pensieri DB con i membri più esperti.


+1 Alcuni ottimi punti qui. Dato quanto è cattivo un po 'di SQL, la reticenza dei DBA per consentire le visualizzazioni è spesso del tutto comprensibile. Inoltre, SQL può sicuramente trarre vantaggio dalla revisione tra pari se ha fame di risorse e / o verrà eseguito frequentemente.
Robbie Dee,

1

Per i punti 1 e 3: le viste non sono l'unico modo. Esistono anche tabelle temporanee, marts, variabili di tabella, colonne aggregate, CTE, funzioni, procedure memorizzate e possibilmente altri costrutti a seconda del RDBMS.

I DBA (e sto parlando come qualcuno che è stato sia DBA che sviluppatore) tendono a vedere il mondo in un modo piuttosto binario, quindi spesso sono contro cose come punti di vista e funzioni a causa della penalità delle prestazioni percepita.

Ultimamente, la necessità di join complessi si è ridotta con il riconoscimento che le tabelle denormalizzate nonostante siano subottimali dal punto di vista NF , sono altamente performanti.

C'è anche la tendenza a fare query sul lato client con tecnologie come LINQ che sollevate al punto 2.

Mentre sono d'accordo sul fatto che SQL possa essere difficile da modulare, sono stati fatti passi da gigante sebbene ci sarà sempre una dicotomia tra codice lato client e SQL, sebbene 4GL abbia in qualche modo confuso le linee.

Immagino che dipenda davvero da quanto i vostri DBA / architetti / responsabili tecnologici siano disposti a cedere in questo senso. Se si rifiutano di consentire qualsiasi cosa tranne SQL vanilla con molti join, potrebbero derivarne enormi query. Se sei bloccato con questo, non sbattere la testa su un muro di mattoni, intensificalo. In genere ci sono modi migliori per fare le cose con un po 'di compromesso, specialmente se puoi dimostrarne i benefici.


1
Non ho mai sentito parlare di un costrutto "mart". Cos'è quello?
vescovo

1
I Mart sono solo un sottoinsieme del repository (database principale). Se sono necessarie query complesse specifiche che devono essere eseguite, è possibile creare un database speciale per soddisfare tali richieste. Un esempio molto comune è un mart di segnalazione.
Robbie Dee

1
Confuso perché questo è stato sottoposto a downgrade. Non risponde direttamente alla domanda, ma fornisce una risposta implicita abbastanza chiara di "opzione 3: ci sono molti modi di gestirla, che sono ampiamente utilizzati".
Dewi Morgan,

TIL sui data mart. Prendi un +1!
vescovo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.