Questo è un problema che ho passato ore a ricercare in passato. Mi sembra essere qualcosa che avrebbe dovuto essere affrontato dai moderni RDBMS soluzioni , ma finora non ho trovato nulla che risolva realmente ciò che vedo essere un'esigenza incredibilmente comune in qualsiasi applicazione Web o Windows con un back-end di database.
Parlo di ordinamento dinamico. Nel mio mondo fantasy, dovrebbe essere semplice come qualcosa come:
ORDER BY @sortCol1, @sortCol2
Questo è l'esempio canonico fornito dagli sviluppatori principianti di SQL e Stored Procedure in tutti i forum su Internet. "Perché non è possibile?" loro chiedono. Invariabilmente, qualcuno alla fine arriva per tenere lezioni sulla natura compilata delle procedure archiviate, dei piani di esecuzione in generale e di ogni sorta di altri motivi per cui non è possibile inserire un parametro direttamente in una ORDER BY
clausola.
So cosa stanno già pensando alcuni di voi: "Lasciate che il cliente faccia l'ordinamento, allora." Naturalmente, questo scarica il lavoro dal tuo database. Nel nostro caso, tuttavia, i nostri server di database non hanno nemmeno sudato il 99% delle volte e non sono nemmeno multi-core o nessuna delle altre miriadi di miglioramenti all'architettura di sistema che si verificano ogni 6 mesi. Solo per questo motivo, avere i nostri database in grado di gestire l'ordinamento non sarebbe un problema. Inoltre, i database sono moltobravo nell'ordinamento. Sono ottimizzati per questo e hanno avuto anni per farlo bene, il linguaggio per farlo è incredibilmente flessibile, intuitivo e semplice e soprattutto qualsiasi scrittore SQL principiante sa come farlo e ancora più importante sa come modificarlo, apportare modifiche, eseguire interventi di manutenzione, ecc. Quando i database non sono tassati e si desidera solo semplificare (e abbreviare!) i tempi di sviluppo, questa sembra una scelta ovvia.
Quindi c'è il problema web. Ho giocato con JavaScript che eseguirà l'ordinamento sul lato client di tabelle HTML, ma inevitabilmente non sono abbastanza flessibili per le mie esigenze e, ancora una volta, poiché i miei database non sono eccessivamente tassati e possono fare l'ordinamento molto facilmente, io fare fatica a giustificare il tempo necessario per riscrivere o eseguire il rullaggio del mio selezionatore JavaScript. Lo stesso vale in genere per l'ordinamento sul lato server, anche se probabilmente è già molto preferito su JavaScript. Non sono uno a cui piace particolarmente il sovraccarico di DataSet, quindi fammi causa.
Ma questo riporta il punto che non è possibile - o meglio, non facilmente. Ho fatto, con i sistemi precedenti, un modo incredibilmente hack per ottenere l'ordinamento dinamico. Non era carino, né intuitivo, semplice o flessibile e uno scrittore SQL per principianti si sarebbe perso in pochi secondi. Già questo non sembra essere tanto una "soluzione" ma una "complicazione".
I seguenti esempi non intendono esporre alcun tipo di best practice o buon stile di codifica o altro, né sono indicativi delle mie capacità di programmatore T-SQL. Sono quello che sono e ammetto pienamente che sono confusi, cattivi in forma e semplicemente hack.
Passiamo un valore intero come parametro a una procedura memorizzata (chiamiamo il parametro solo "ordina") e da ciò determiniamo un gruppo di altre variabili. Ad esempio ... diciamo che l'ordinamento è 1 (o il valore predefinito):
DECLARE @sortCol1 AS varchar(20)
DECLARE @sortCol2 AS varchar(20)
DECLARE @dir1 AS varchar(20)
DECLARE @dir2 AS varchar(20)
DECLARE @col1 AS varchar(20)
DECLARE @col2 AS varchar(20)
SET @col1 = 'storagedatetime';
SET @col2 = 'vehicleid';
IF @sort = 1 -- Default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'asc';
SET @sortCol2 = @col2;
SET @dir2 = 'asc';
END
ELSE IF @sort = 2 -- Reversed order default sort.
BEGIN
SET @sortCol1 = @col1;
SET @dir1 = 'desc';
SET @sortCol2 = @col2;
SET @dir2 = 'desc';
END
Puoi già vedere come se dichiarassi più variabili @colX per definire altre colonne, potrei davvero essere creativo con le colonne su cui ordinare in base al valore di "sort" ... per usarlo, di solito finisce come il seguente clausola incredibilmente disordinata:
ORDER BY
CASE @dir1
WHEN 'desc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir1
WHEN 'asc' THEN
CASE @sortCol1
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END,
CASE @dir2
WHEN 'desc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END DESC,
CASE @dir2
WHEN 'asc' THEN
CASE @sortCol2
WHEN @col1 THEN [storagedatetime]
WHEN @col2 THEN [vehicleid]
END
END
Ovviamente questo è un esempio molto ridotto. Il vero materiale, dal momento che di solito abbiamo quattro o cinque colonne per supportare l'ordinamento, ognuna con possibili secondarie o anche una terza colonna su cui ordinare oltre a quella (ad esempio data decrescente quindi ordinata secondariamente per nome crescente) e ogni bi-supporto ordinamento direzionale che raddoppia efficacemente il numero di casi. Sì ... diventa peloso molto velocemente.
L'idea è che si potrebbe "facilmente" cambiare i casi di ordinamento in modo tale che il veicoloid venga ordinato prima del tempo memorizzato ... ma la pseudo-flessibilità, almeno in questo semplice esempio, finisce davvero lì. In sostanza, ogni caso che fallisce un test (perché il nostro metodo di ordinamento non si applica ad esso questa volta) rende un valore NULL. E così finisci con una clausola che funziona come la seguente:
ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah
Ti viene l'idea. Funziona perché SQL Server ignora efficacemente i valori null in ordine di clausole. Questo è incredibilmente difficile da mantenere, come probabilmente può vedere chiunque abbia una conoscenza base di SQL. Se ho perso qualcuno di voi, non stare male. Ci è voluto molto tempo per farlo funzionare e ci confondiamo ancora cercando di modificarlo o crearne di nuovi simili. Per fortuna non ha bisogno di cambiare spesso, altrimenti diventerebbe rapidamente "non vale la pena".
Eppure ha fatto il lavoro.
La mia domanda è quindi: esiste un modo migliore?
Sto bene con soluzioni diverse da quelle memorizzate, poiché mi rendo conto che potrebbe non essere la strada da percorrere. Preferibilmente, vorrei sapere se qualcuno può farlo meglio all'interno della Stored Procedure, ma in caso contrario, come gestite tutti voi consentendo all'utente di ordinare dinamicamente tabelle di dati (anche bidirezionali) con ASP.NET?
E grazie per aver letto (o almeno scremato) una domanda così lunga!
PS: Sii contento di non aver mostrato il mio esempio di una procedura memorizzata che supporta l'ordinamento dinamico, il filtro dinamico / la ricerca di testo delle colonne, l'impaginazione tramite ROWNUMBER () OVER, E prova ... a intercettare il rollback delle transazioni sugli errori ... "behemoth-size" non inizia nemmeno a descriverli.
Aggiornare:
- Vorrei evitare SQL dinamico . L'analisi di una stringa insieme e l'esecuzione di un EXEC su di essa vanifica molto lo scopo di avere una procedura memorizzata in primo luogo. A volte mi chiedo se i contro del fare una cosa del genere non varrebbero la pena, almeno in questi casi di smistamento dinamico speciali. Tuttavia, mi sento sempre sporco ogni volta che eseguo stringhe SQL dinamiche del genere, come se vivessi ancora nel mondo ASP classico.
- Molte delle ragioni per cui vogliamo che le procedure archiviate siano innanzitutto legate alla sicurezza . Non riesco a fare la chiamata per motivi di sicurezza, suggerisco solo soluzioni. Con SQL Server 2005 possiamo impostare le autorizzazioni (su base per utente se necessario) a livello di schema su singole procedure memorizzate e quindi negare direttamente qualsiasi query sulle tabelle. Criticare i pro ei contro di questo approccio è forse per un'altra domanda, ma ancora una volta non è una mia decisione. Sono solo la scimmia del codice principale. :)