it-swarm.com.de

Dynamische Sortierung in gespeicherten SQL-Prozeduren

Dies ist ein Thema, über das ich in der Vergangenheit stundenlang recherchiert habe. Es scheint mir etwas zu sein, das von modernen RDBMS -Lösungen hätte angesprochen werden sollen, aber bisher habe ich nichts gefunden, das wirklich das anspricht, was ich für ein unglaublich verbreitetes Bedürfnis in jedem Web oder Windows halte Anwendung mit einem Datenbank-Backend.

Ich spreche von dynamischer Sortierung. In meiner Fantasiewelt sollte es so einfach sein wie:

ORDER BY @sortCol1, @sortCol2

Dies ist das kanonische Beispiel, das von SQL-Neulingen und Stored Procedure Entwicklern in allen Foren im Internet gegeben wurde. "Warum ist das nicht möglich?" Sie Fragen. Ausnahmslos kommt irgendwann jemand, um ihnen die Kompilierung gespeicherter Prozeduren, Ausführungspläne im Allgemeinen und viele andere Gründe zu erläutern, warum es nicht möglich ist, einen Parameter direkt in eine ORDER BY-Klausel einzufügen.


Ich weiß, was einige von Ihnen bereits denken: "Lassen Sie den Kunden dann die Sortierung durchführen." Dies entlastet natürlich die Arbeit von Ihrer Datenbank. In unserem Fall sind unsere Datenbankserver in 99% der Fälle noch nicht einmal ins Schwitzen gekommen und sie sind noch nicht einmal Multi-Core oder eine der anderen unzähligen Verbesserungen der Systemarchitektur, die alle 6 Monate vorgenommen werden. Alleine aus diesem Grund wäre es kein Problem, wenn unsere Datenbanken die Sortierung übernehmen würden. Darüber hinaus sind Datenbanken sehr gut zu sortieren. Sie sind dafür optimiert und hatten Jahre Zeit, um es richtig zu machen. Die Sprache dafür ist unglaublich flexibel, intuitiv und einfach, und vor allem jeder SQL-Anfänger weiß, wie es geht und was noch wichtiger ist, sie wissen, wie man es bearbeitet. Nehmen Sie Änderungen vor, führen Sie Wartungsarbeiten durch usw. Wenn Ihre Datenbanken weit davon entfernt sind, besteuert zu werden, und Sie nur die Entwicklungszeit vereinfachen (und verkürzen möchten!), ist dies eine naheliegende Wahl.

Dann gibt es das Web-Problem. Ich habe mit JavaScript herumgespielt, das clientseitige Sortierung von HTML-Tabellen vornimmt, aber sie sind unvermeidlich nicht flexibel genug für meine Anforderungen, und auch hier sind meine Datenbanken nicht überfordert und können wirklich sortieren wirklich leicht, ich habe es schwer zu rechtfertigen, wie lange es dauern würde, meinen eigenen JavaScript-Sortierer neu zu schreiben oder zu rollen. Das Gleiche gilt im Allgemeinen für die serverseitige Sortierung, obwohl sie JavaScript wahrscheinlich bereits weit vorgezogen wird. Ich bin keiner, der den Overhead von DataSets besonders mag, also verklagen Sie mich.

Aber das bringt den Punkt zurück, dass es nicht möglich ist - oder besser gesagt, nicht einfach. Ich habe mit früheren Systemen einen unglaublich Hack-Weg gefunden, um eine dynamische Sortierung zu erhalten. Es war weder hübsch noch intuitiv, einfach oder flexibel, und ein Anfänger-SQL-Writer würde innerhalb von Sekunden verloren gehen. Schon jetzt scheint dies nicht so sehr eine "Lösung", sondern eine "Komplikation" zu sein.


Die folgenden Beispiele sollen weder Best Practices, einen guten Codierungsstil oder ähnliches aufzeigen, noch weisen sie auf meine Fähigkeiten als T-SQL-Programmierer hin. Sie sind das, was sie sind, und ich gebe zu, dass sie verwirrend sind, eine schlechte Form haben und einfach nur hacken.

Wir übergeben einer Stored Procedure einen Integer-Wert als Parameter (nennen wir den Parameter einfach "sort") und bestimmen daraus eine Reihe weiterer Variablen. Zum Beispiel ... Angenommen, sort ist 1 (oder die Standardeinstellung):

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

Sie können bereits sehen, dass, wenn ich mehr @ colX-Variablen deklariere, um andere Spalten zu definieren, ich wirklich kreativ werden könnte, indem ich die Spalten nach dem Wert von "sort" sortiere ... um sie zu verwenden, sie normalerweise wie folgt aussehen unglaublich unordentliche Klausel:

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

Offensichtlich ist dies ein sehr reduziertes Beispiel. Das eigentliche Zeug, da wir normalerweise vier oder fünf Spalten zum Sortieren haben, jede mit einer möglichen sekundären oder sogar einer dritten Spalte zum Sortieren zusätzlich dazu (zum Beispiel Datum absteigend, dann sekundär nach Namen aufsteigend sortiert) und jede unterstützende Doppelspalte. Richtungssortierung, die die Anzahl der Fälle effektiv verdoppelt. Ja ... es wird sehr schnell haarig.

Die Idee ist, dass man die Sortierfälle "leicht" so ändern könnte, dass die Fahrzeug-ID vor der Speicherzeit sortiert wird ... aber die Pseudoflexibilität, zumindest in diesem einfachen Beispiel, endet wirklich dort. Grundsätzlich wird für jeden Fall, bei dem ein Test fehlschlägt (da unsere Sortiermethode diesmal nicht angewendet wird), ein NULL-Wert ausgegeben. Und so erhalten Sie eine Klausel, die wie folgt funktioniert:

ORDER BY NULL DESC, NULL, [storagedatetime] DESC, blah blah

Du hast die Idee. Dies funktioniert, weil SQL Server Nullwerte in der Reihenfolge der Klauseln effektiv ignoriert. Dies ist unglaublich schwer zu pflegen, wie jeder mit grundlegenden SQL-Kenntnissen wahrscheinlich sehen kann. Wenn ich jemanden von Ihnen verloren habe, fühlen Sie sich nicht schlecht. Wir haben lange gebraucht, um es zum Laufen zu bringen, und wir sind immer noch verwirrt, wenn wir versuchen, es zu bearbeiten oder neue wie diese zu erstellen. Zum Glück muss es nicht oft gewechselt werden, sonst würde es schnell "die Mühe nicht wert" werden.

Doch es hat ​​funktioniert.


Meine Frage ist dann: gibt es einen besseren Weg?

Ich bin mit anderen Lösungen als den mit gespeicherten Prozeduren einverstanden, da mir klar ist, dass dies möglicherweise nicht der richtige Weg ist. Am liebsten würde ich gerne wissen, ob jemand es innerhalb der gespeicherten Prozedur besser kann, aber wenn nicht, wie gehen Sie alle damit um, dass der Benutzer Datentabellen (auch bidirektional) mit ASP.NET dynamisch sortiert?

Und danke, dass Sie so eine lange Frage gelesen (oder zumindest überflogen) haben!

PS: Seien Sie froh, dass ich kein Beispiel für eine gespeicherte Prozedur gezeigt habe, die dynamisches Sortieren, dynamisches Filtern/Textsuchen von Spalten, Paginieren mit ROWNUMBER () OVER, UND try ... unterstützt. Fangen Sie mit Transaktions-Rollbacks Fehler auf ... "Behemoth-Size" beginnt nicht einmal, sie zu beschreiben.


Update:

  • Ich möchte dynamisches SQL vermeiden . Das Parsen eines Strings zusammen und das Ausführen einer EXEC-Datei haben einen großen Nachteil darin, überhaupt eine gespeicherte Prozedur zu haben. Manchmal frage ich mich jedoch, ob sich die Nachteile eines solchen Vorgangs nicht lohnen würden, zumindest in diesen speziellen Fällen der dynamischen Sortierung. Trotzdem fühle ich mich immer dreckig, wenn ich dynamische SQL-Zeichenfolgen wie diese mache - als würde ich immer noch in der klassischen ASP Welt leben.
  • Viele der Gründe, warum wir zuerst gespeicherte Prozeduren wollen, sind aus Sicherheitsgründen . Ich kann keine Sicherheitsbedenken äußern, sondern nur Lösungen vorschlagen. Mit SQL Server 2005 können wir Berechtigungen (bei Bedarf auf Benutzerbasis) auf Schemaebene für einzelne gespeicherte Prozeduren festlegen und dann alle Abfragen für die Tabellen direkt ablehnen. Das Für und Wider dieses Ansatzes zu kritisieren ist vielleicht eine andere Frage, aber es ist auch nicht meine Entscheidung. Ich bin nur der Leitcode-Affe. :)
126
Sean Hanley

Ja, es ist ein Schmerz, und so, wie du es tust, sieht es ähnlich aus wie ich:

order by
case when @SortExpr = 'CustomerName' and @SortDir = 'ASC' 
    then CustomerName end asc, 
case when @SortExpr = 'CustomerName' and @SortDir = 'DESC' 
    then CustomerName end desc,
...

Für mich ist dies immer noch viel besser als der Aufbau dynamischer SQL-Codes aus Code, der sich in einen Skalierbarkeits- und Wartungsalarm für Datenbankadministratoren verwandelt.

Was ich aus dem Code mache, ist das Paging und die Sortierung umgestalten, so dass ich mich dort nicht immer mit den Werten für @SortExpr und @SortDir auffülle.

Behalten Sie im Hinblick auf SQL das Design und die Formatierung der verschiedenen gespeicherten Prozeduren bei, sodass es zumindest ordentlich und erkennbar ist, wenn Sie Änderungen vornehmen.

92
Eric Z Beard

Dieser Ansatz verhindert, dass die sortierbaren Spalten zweimal in der Reihenfolge by dupliziert werden, und ist IMO etwas lesbarer:

SELECT
  s.*
FROM
  (SELECT
    CASE @SortCol1
      WHEN 'Foo' THEN t.Foo
      WHEN 'Bar' THEN t.Bar
      ELSE null
    END as SortCol1,
    CASE @SortCol2
      WHEN 'Foo' THEN t.Foo
      WHEN 'Bar' THEN t.Bar
      ELSE null
    END as SortCol2,
    t.*
  FROM
    MyTable t) as s
ORDER BY
  CASE WHEN @dir1 = 'ASC'  THEN SortCol1 END ASC,
  CASE WHEN @dir1 = 'DESC' THEN SortCol1 END DESC,
  CASE WHEN @dir2 = 'ASC'  THEN SortCol2 END ASC,
  CASE WHEN @dir2 = 'DESC' THEN SortCol2 END DESC
23
Jason DeFontes

Meine Anwendungen machen das oft, aber sie bauen alle SQL dynamisch auf. Wenn ich jedoch mit gespeicherten Prozeduren arbeite, mache ich Folgendes:

  1. Machen Sie die gespeicherte Prozedur zu einer Funktion, die eine Tabelle Ihrer Werte zurückgibt - keine Sortierung.
  2. Führen Sie dann in Ihrem Anwendungscode eine select * from dbo.fn_myData() where ... order by ... aus, damit Sie die Sortierreihenfolge dort dynamisch angeben können.

Dann ist zumindest der dynamische Teil in Ihrer Anwendung, aber die Datenbank übernimmt immer noch das Heben.

6
Ron Savage

Dynamisches SQL ist immer noch eine Option. Sie müssen nur entscheiden, ob diese Option schmackhafter ist als die, die Sie derzeit haben.

Hier ist ein Artikel, der Folgendes zeigt: http://www.4guysfromrolla.com/webtech/010704-1.shtml

6
jop

Möglicherweise gibt es eine dritte Option, da es auf Ihrem Server viele Ersatzzyklen gibt. Verwenden Sie ein Hilfsverfahren, um die Sortierung über eine temporäre Tabelle durchzuführen. So etwas wie

create procedure uspCallAndSort
(
    @sql varchar(2048),        --exec dbo.uspSomeProcedure arg1,'arg2',etc.
    @sortClause varchar(512)    --comma-delimited field list
)
AS
insert into #tmp EXEC(@sql)
declare @msql varchar(3000)
set @msql = 'select * from #tmp order by ' + @sortClause
EXEC(@msql)
drop table #tmp
GO

Vorbehalt: Ich habe dies nicht getestet, aber "sollte" in SQL Server 2005 funktionieren (wodurch eine temporäre Tabelle aus einer Ergebnismenge erstellt wird, ohne dass zuvor die Spalten angegeben werden.)

4
Steven A. Lowe

Eine Technik für gespeicherte Prozeduren (Hack?), Die ich verwendet habe, um dynamisches SQL für bestimmte Jobs zu vermeiden, ist eine eindeutige Sortierspalte. Das heißt,

SELECT
   name_last,
   name_first,
   CASE @sortCol WHEN 'name_last' THEN [name_last] ELSE 0 END as mySort
FROM
   table
ORDER BY 
    mySort

Dieses ist leicht in die Vorlage einzuschlagen - Sie können Felder in Ihrer mySort-Spalte zusammenfassen, die Reihenfolge mit Mathematik- oder Datumsfunktionen umkehren, usw.

Vorzugsweise verwende ich jedoch meine asp.net-Rasteransichten oder andere Objekte mit integrierter Sortierung, um die Sortierung für mich NACH dem Abrufen der Daten für den SQL-Server durchzuführen. Oder auch, wenn es nicht eingebaut ist - z. B. Datentypen usw. in asp.net.

4
dave

Es gibt verschiedene Möglichkeiten, dies zu hacken. 

Voraussetzungen:

  1. Nur eine SELECT-Anweisung in der Sp 
  2. Keine Sortierung auslassen (oder Als Standard festlegen)

Dann in eine temporäre Tabelle einfügen:

create table #temp ( your columns )

insert #temp
exec foobar

select * from #temp order by whatever

Methode 2: Richten Sie einen Verbindungsserver wieder auf sich selbst ein und wählen Sie dann mit openquery aus: http://www.sommarskog.se/share_data.html#OPENQUERY

4
Matt Rogish

Ein Argument gegen die Sortierung auf der Clientseite ist das große Datenvolumen und die Paginierung. Sobald Ihre Zeilenanzahl über das hinausgeht, was Sie leicht anzeigen können, werden Sie häufig als Teil eines Überspring-/Take-Vorgangs sortiert, den Sie wahrscheinlich in SQL ausführen möchten.

Für Entity Framework können Sie eine gespeicherte Prozedur verwenden, um Ihre Textsuche auszuführen. Wenn das gleiche Sortierungsproblem auftritt, besteht die Lösung darin, eine gespeicherte Prozedur für die Suche zu verwenden, wobei nur ein ID-Schlüsselsatz für die Übereinstimmung zurückgegeben wird. Als nächstes frage (mit der Sortierung) gegen die Datenbank mit den IDs in einer Liste ab (enthält). EF erledigt dies ziemlich gut, auch wenn die ID-Menge ziemlich groß ist. Ja, dies sind zwei Roundtrips, aber Sie können Ihre Sortierung immer in der Datenbank behalten, was in bestimmten Situationen wichtig sein kann, und verhindert, dass Sie in der gespeicherten Prozedur eine Bootload von Logik schreiben.

2
Paul Schirf

Ich stimme zu, benutze die Client-Seite. Aber es scheint, dass dies nicht die Antwort ist, die Sie hören möchten.

Es ist also perfekt so wie es ist. Ich weiß nicht, warum Sie es ändern möchten oder auch nur fragen, ob es einen besseren Weg gibt. Eigentlich sollte es "The Way" heißen. Außerdem scheint es gut zu funktionieren und den Bedürfnissen des Projekts zu entsprechen und wird wahrscheinlich für die kommenden Jahre erweiterbar genug sein. Da Ihre Datenbanken nicht besteuert werden und das Sortieren sehr, sehr einfach ist , sollte dies auch in den kommenden Jahren so bleiben.

Ich würde es nicht schwitzen.

2
D.S.

Wenn Sie sortierte Ergebnisse pagingen, ist dynamisches SQL eine gute Option. Wenn Sie bezüglich der SQL-Injection paranoid sind, können Sie die Spaltennummern anstelle des Spaltennamens verwenden. Ich habe dies getan, bevor ich negative Werte zum Absteigen verwendet habe. Etwas wie das...

declare @o int;
set @o = -1;

declare @sql nvarchar(2000);
set @sql = N'select * from table order by ' + 
    cast(abs(@o) as varchar) + case when @o < 0 then ' desc' else ' asc' end + ';'

exec sp_executesql @sql

Dann müssen Sie nur sicherstellen, dass die Anzahl zwischen 1 und # von Spalten liegt. Sie können dies sogar um eine Liste von Spaltennummern erweitern und diese mit einer Funktion wie this in eine Tabelle von Ints parsen. Dann würden Sie die order by-Klausel so bauen ...

declare @cols varchar(100);
set @cols = '1 -2 3 6';

declare @order_by varchar(200)

select @order_by = isnull(@order_by + ', ', '') + 
        cast(abs(number) as varchar) + 
        case when number < 0 then ' desc' else '' end
from dbo.iter_intlist_to_tbl(@cols) order by listpos

print @order_by

Ein Nachteil ist, dass Sie sich die Reihenfolge jeder Spalte auf der Clientseite merken müssen. Insbesondere, wenn Sie nicht alle Spalten anzeigen oder in einer anderen Reihenfolge anzeigen. Wenn der Client sortieren möchte, ordnen Sie die Spaltennamen der Spaltenreihenfolge zu und generieren die Liste der Ints. 

2
dotjoe

Lohnt es sich nicht, sich irgendwann von gespeicherten Prozeduren zu entfernen und nur parametrisierte Abfragen zu verwenden, um diese Art von Hacker zu vermeiden?

2
Hank Gay

Tut mir leid, ich bin zu spät zur Party, aber hier ist eine weitere Option für diejenigen, die dynamisches SQL wirklich vermeiden möchten, aber die Flexibilität wünschen, die es bietet:

Schreiben Sie den Code nicht dynamisch dynamisch, sondern schreiben Sie Code, um eine eindeutige Prozedur für jede mögliche Variation zu generieren. Dann können Sie eine Methode in den Code schreiben, um die Suchoptionen anzuzeigen und die entsprechende proc auswählen zu lassen.

Wenn Sie nur wenige Variationen haben, können Sie die Procs einfach von Hand erstellen. Wenn Sie jedoch viele Variationen haben, müssen Sie stattdessen nur den Proc-Generator warten, anstatt sie alle warten zu müssen.

Als zusätzlicher Vorteil erhalten Sie auf diese Weise auch bessere SQL-Pläne für eine bessere Leistung.

0
BVernon

Wie wäre es mit der Sortierung der Daten, die die Ergebnisse anzeigen - Raster, Berichte usw., anstatt in SQL?

BEARBEITEN:

Um die Dinge zu klären, seit diese Antwort zuvor abgelehnt wurde, werde ich ein wenig näher darauf eingehen ...

Sie gaben an, dass Sie über das Sortieren auf Kundenseite Bescheid wussten, aber Sie wollten sich davon fernhalten. Das ist natürlich dein Anruf.

Ich möchte jedoch darauf hinweisen, dass Sie durch das Ausführen auf Client-Seite Daten EINMAL ziehen und dann mit ihnen arbeiten können, wie Sie möchten - im Gegensatz zu mehreren Hin- und Herbewegungen zum Server Die Sorte wird geändert.

Ihr SQL Server wird jetzt nicht besteuert und das ist großartig. Es sollte nicht sein. Aber nur weil es noch nicht überladen ist, heißt das nicht, dass es für immer so bleibt.

Wenn Sie eines der neueren ASP.NET-Elemente für die Anzeige im Internet verwenden, ist bereits einiges davon bereits im System enthalten.

Lohnt es sich, jeder gespeicherten Prozedur so viel Code hinzuzufügen, dass nur die Sortierung erfolgt? Wieder dein Anruf. 

Ich bin nicht derjenige, der letztendlich dafür verantwortlich ist. Denken Sie jedoch darüber nach, was dabei zu berücksichtigen ist, wenn in den verschiedenen von den gespeicherten Prozeduren verwendeten Datasets Spalten hinzugefügt/entfernt werden (was Änderungen an den CASE-Anweisungen erfordert). Sie müssen jetzt jede Ihrer gespeicherten Prozeduren aktualisieren, die diese Methode verwenden.

Für mich lohnt es sich, eine funktionierende clientseitige Lösung zu erhalten, diese auf die Handvoll benutzerorientierter Anzeige von Daten anzuwenden und damit fertig zu werden. Wenn eine neue Spalte hinzugefügt wird, wird diese bereits behandelt. Wenn der Benutzer nach mehreren Spalten sortieren möchte, kann er nach zwei oder zwanzig Spalten sortieren.

0
Kevin Fairchild