2009-09-02 11 views
27

Ok, ho bisogno di creare una query basata su qualche input dell'utente per filtrare i risultati.Quanto pessima è la mia domanda?

La query va in fondo qualcosa di simile:

SELECT * FROM my_table ORDER BY ordering_fld; 

Ci sono quattro caselle di testo in cui gli utenti possono scegliere di filtrare i dati, il che significa che avrei dovuto costruire dinamicamente una clausola "WHERE" in esso per il primo filtro utilizzato e quindi le clausole "AND" per ogni filtro successivo inserito.

Poiché sono troppo pigro per farlo, ho appena definito ogni filtro una clausola "AND" e ho inserito una clausola "WHERE 1" nella query per impostazione predefinita.

Così ora ho:

SELECT * FROM my_table WHERE 1 {AND filters} ORDER BY ordering_fld; 

Quindi la mia domanda è, ho fatto qualcosa che possano influire negativamente sul rendimento della mia query o inculata qualsiasi altra cosa in qualsiasi modo dovrei essere lontanamente preoccupati?

+0

Questamente interessante domanda –

+10

La mia query sembra grande in questo? –

+6

Sono solo io o Evernoob estremamente coraggioso? Non vorrei mai chiedere a un sito pieno di altri sviluppatori (che sono notoriamente supponenti) quanto sia stato cattivo il mio codice? –

risposta

37

MySQL ottimizzeranno il tuo 1 di distanza.

Ho appena eseguito questa ricerca il mio database di test:

EXPLAIN EXTENDED 
SELECT * 
FROM t_source 
WHERE 1 AND id < 100 

e mi ha dato il seguente description:

select `test`.`t_source`.`id` AS `id`,`test`.`t_source`.`value` AS `value`,`test`.`t_source`.`val` AS `val`,`test`.`t_source`.`nid` AS `nid` from `test`.`t_source` where (`test`.`t_source`.`id` < 100) 

Come si può vedere, non 1 a tutti.

La documentazione WHERE clause optimization in MySQL cita questo:

  • piegatura costante:

    (a<b AND b=c) AND a=5 
    -> b>5 AND b=c AND a=5 
    
  • costante rimozione condizione (necessaria a causa del costante piegatura):

    (B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6) 
    -> B=5 OR B=6 
    

Nota 5 = 5 e 5 = 6 parti nell'esempio precedente.

+0

+1 per provarlo, usando EXPLAIN e guardando la documentazione – knittl

+8

'@ knittl': Se ogni sviluppatore usava' EXPLAIN' e cercava la documentazione, il mondo sarebbe stato un posto molto più bello! – Quassnoi

+0

+1 per aver cercato di rendere il mondo un posto più bello :) – waqasahmed

2

per migliorare le prestazioni, utilizzare indici di colonna sui campi ascoltare in "WHERE"

2

standard Avvertenze SQL Injection qui ...

Una cosa che si potrebbe fare, per evitare SQL injection in quanto si sa che è solo quattro i parametri utilizzano una stored procedure in cui si passano i valori per i campi o NULL. Non sono sicuro della sintassi proc mySQL memorizzato, ma la query ridursi a

SELECT * 
    FROM my_table 
WHERE Field1 = ISNULL(@Field1, Field1) 
    AND Field2 = ISNULL(@Field2, Field2) 
    ... 
ORDRE BY ordering_fld 
+0

Ciò tende a impedire l'uso di indici su campo1, ecc. Dovrebbe anche usare COALESCE anziché ISNULL –

+0

Il formato corretto sarebbe @Field IS NULL O t.col = @Field. In caso contrario, si tratta di una ricerca indice sprecata o di una scansione di tabelle. –

8

si può spiegare la query:
http://dev.mysql.com/doc/refman/5.0/en/explain.html

e vedere se lo fa nulla di diverso, di cui dubito. Vorrei usare 1 = 1, solo così è più chiaro.

È possibile aggiungere LIMIT 1000 o qualcosa del genere, quando non vengono utilizzati parametri e la tabella diventa grande, si desidera veramente restituire tutto?

+0

'@ KM': secondo i tag, dovrebbe essere' LIMIT 1000' :) – Quassnoi

+0

@Quassnoi, grazie, stavo pensando a mysql quando ho risposto per la prima volta, ma quando ho editato e aggiunto la parte _TOP 1000_ pensavo a SQL Server . –

4

Se esiste un buon modo nella lingua scelta per evitare di creare SQL da soli, utilizzare quello. Mi piacciono Python e Django, e Django ORM rende molto facile filtrare i risultati in base all'input dell'utente.

Se si è impegnati a creare da soli l'SQL, accertarsi di disinfettare gli input dell'utente in SQL injection e provare a racchiudere l'SQL building in un modulo separato dalla logica del filtro.

Inoltre, la prestazione delle query non dovrebbe essere motivo di preoccupazione finché non diventa un problema, che probabilmente non lo sarà fino a quando non avrai migliaia o milioni di righe. E quando arriva il momento di ottimizzare, l'aggiunta di alcuni indici sulle colonne utilizzate per WHERE e JOIN è molto lunga.

5

WHERE 1 è un'espressione costante e deterministica che verrà "ottimizzata" da qualsiasi motore DB decente.

2

Abbiamo fatto qualcosa di simile non troppo tempo fa e ci siamo un paio di cose che abbiamo osservato:

  • Impostare gli indici sulle colonne siamo stati (forse) di filtraggio, il miglioramento delle prestazioni
  • La parte WHERE 1 può essere lasciata completamente se i filtri non vengono utilizzati. (non sono sicuro che si applichi al tuo caso) Non fa la differenza, ma 'sente' giusto.
  • SQL injection non va dimenticato

Inoltre, se si 'solo' avere 4 filtri, si potrebbe costruire una stored procedure e passare valori nulli e verificare la presenza di loro. (Proprio come n8wrl suggerito nel frattempo)

+0

Non sono troppo preoccupato per le iniezioni SQL, lo sto gestendo.Era più che ero solo curioso riguardo all'accettabilità di ciò che avevo fatto. – Evernoob

2

Che funzionerà - alcune considerazioni:

SQL Chi costruita in modo dinamico, in generale, alcuni database (Oracle almeno) memorizza nella cache i piani di esecuzione per le query, quindi se si finisce eseguendo la stessa query molte volte non sarà necessario ricominciare da capo completamente. Se si utilizza SQL generato dinamicamente, si crea ogni volta una query diversa, quindi al database sembrerà 100 query diverse anziché 100 esecuzioni della stessa query.

Probabilmente dovresti solo misurare le prestazioni per scoprire se funziona abbastanza bene per te.

Avete bisogno di tutte le colonne? Specificare esplicitamente loro è probabilmente meglio che usare * in ogni modo perché:

  • È visivamente può vedere ciò che le colonne vengono restituiti
  • Se si aggiunge o rimuove colonne alla tabella successiva, non cambierà l'interfaccia
+0

Oracle verifica la cache in base al contenuto della query inviata, non al fatto che un utente abbia eseguito una determinata funzione o procedura. –

2

Non male, non conoscevo questo snippet per eliminare la domanda "è il primo filtro 3".

Tho dovresti vergognarti del tuo codice (^^), non fa nulla per le prestazioni dato che qualsiasi DB Engine lo ottimizzerà.

+0

haha, perché dovrei vergognarmi del mio codice? se il motore DB lo ottimizza, non c'è nulla di male sul lato DB delle cose e la mia logica di business è ora più chiara e più leggibile per il prossimo. Quindi dov'è la vergogna? – Evernoob

+0

Perché aggiungi "1 = 1", che è un codice inutile, che è cattivo ... innocuo ma cattivo. Ma tutti lo fanno ^^ –

2

L'unico motivo per cui ho utilizzato WHERE 1 = 1 è per SQL dinamico; è un trucco per rendere più semplici le clausole WHERE utilizzando AND .... Non è qualcosa che vorrei includere nel mio SQL altrimenti - non fa nulla per influenzare la query in generale perché viene sempre valutata come vera e non colpisce la/le tabella/i coinvolta, quindi non ci sono ricerche di indici o scansioni di tabelle basate su esso.

Non posso parlare di come MySQL gestisce criteri facoltativi, ma so che utilizzando la seguente:

WHERE (@param IS NULL OR t.column = @param) 

... è il tipico modo di gestire parametri opzionali. COALESCE e ISNULL non sono l'ideale perché la query sta ancora utilizzando indici (o peggio, scansioni di tabelle) basati su un valore sentinella. L'esempio che ho fornito non colpirà la tabella a meno che non sia stato fornito un valore.

Detto questo, la mia esperienza con Oracle (9i, 10g) ha dimostrato che non gestisce [WHERE (@param IS NULL OR t.column = @param)] molto bene. Ho visto un enorme guadagno in termini di prestazioni convertendo l'SQL in dinamico e ho utilizzato le variabili CONTEXT per determinare cosa aggiungere. La mia impressione di SQL Server 2005 è che questi sono gestiti meglio.

2

solito ho fatto qualcosa di simile:

for(int i=0; i<numConditions; i++) { 
    sql += (i == 0 ? "WHERE " : "AND "); 
    sql += dbFieldNames[i] + " = " + safeVariableValues[i]; 
} 

Rende la query generato un po 'più pulito.

2

Un'alternativa volte uso è quello di costruire la clausola in cui un un array e poi unirle:

my @wherefields; 
foreach $c (@conditionfields) { 
    push @wherefields, "$c = ?", 
} 

my $sql = "select * from table"; 
if(@wherefields) { $sql.=" WHERE " . join (" AND ", @wherefields); } 

Quanto sopra è scritto in Perl, ma la maggior parte lingue hanno un qualche tipo di unirsi funciton.

Problemi correlati