Warum erzeugt dieser Cursor Ergebnisse in der falschen Reihenfolge?

7

Ich schreibe dynamisches SQL, um meine NONCLUSTEREDIndizes zu identifizieren und, wenn ich mich verrückt genug fühle, automatisch in CLUSTEREDIndizes umzuwandeln .

Die Zeile ORDER BY 1,2,3 DESC;in der folgenden SQL dient dazu, DROP INDEX...Anweisungen vor ALTER TABLE...Anweisungen auszugeben, um zuerst den NONCLUSTERED-Index zu TROPFEN und dann einen CLUSTERED-Index hinzuzufügen. Ich musste DESCdie Nachspalte 3 hinzufügen , um zuerst den DROP zu erhalten, gefolgt vom ALTER. Das ist rückwärts, es sei denn, ich verliere es!

DECLARE @Server nvarchar(max);
DECLARE @Database nvarchar(max);
DECLARE @cmd nvarchar(max);
DECLARE @IndexType int;


SET @IndexType = 2; /*  1 is CLUSTERED, 2 is NONCLUSTERED */
SET @Server = 'MyServer';
SET @Database = 'MyDatabase';

SET @cmd = '
    DECLARE @cmd nvarchar(max);
    SET @cmd = ''
    SET NOCOUNT ON;
    DECLARE @IndexInfo TABLE (TableName nvarchar(255), IndexName nvarchar(255), IndexColumnName nvarchar(255));
    INSERT INTO @IndexInfo (TableName, IndexName, IndexColumnName)
    SELECT t.name AS TableName, i.name AS IndexName, c.name AS IndexColumnName /*, t.create_date, ic.*, c.* */
    FROM sys.tables t
        LEFT JOIN sys.indexes i ON t.object_id = i.object_id
        LEFT JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
        LEFT JOIN sys.columns c ON i.object_id = c.object_id and ic.column_id = c.column_id
    WHERE i.is_primary_key = 1  
        AND i.type = ' + CAST(@IndexType as nvarchar(max)) + '
    ORDER BY t.create_date desc;
    DECLARE @t1 nvarchar(max);
    DECLARE @t2 nvarchar(max);
    DECLARE @t3 nvarchar(max);
    DECLARE @cmd nvarchar(max);
    DECLARE cur CURSOR FOR
    SELECT TableName, IndexName, 1 AS ExecOrder, ''''DROP INDEX '''' + IndexName + '''' ON '''' + TableName + '''';'''' FROM @IndexInfo I
    UNION ALL 
    SELECT TableName, IndexName, 2 AS ExecOrder, ''''ALTER TABLE '''' + TableName + '''' ADD CONSTRAINT PK_'''' + TableName + ''''_'''' + IndexColumnName + '''' PRIMARY KEY CLUSTERED ('''' + IndexColumnName + '''')'''' + '''';'''' FROM @IndexInfo I
    ORDER BY 1,2,3 DESC;
    OPEN cur;
    FETCH NEXT FROM cur INTO @t1, @t2, @t3, @cmd;
    WHILE @@FETCH_STATUS = 0
    BEGIN
        PRINT @cmd;
        FETCH NEXT FROM cur INTO @t1, @t2, @t3, @cmd;
    END
    CLOSE cur;
    DEALLOCATE cur;
    '';
    EXEC ' + @Server + '.' + @Database + '.sys.sp_executesql @cmd;
';
PRINT @cmd;
Max Vernon
quelle
Oh, ich habe vergessen zu erwähnen, dass dieses dynamische SQL für die Ausführung auf einem Remote-Server eingerichtet ist. Bei Bedarf kann ich eine Version ohne diese zusätzliche Komplexität veröffentlichen.
Max Vernon

Antworten:

11

Die PRINTAnweisung in der WHILESchleife wird in der erwarteten Reihenfolge ausgeführt, die Ausgabe wird jedoch vor der sys.sp_executesqlRückgabe gepuffert . Implementierungsdetails bedeuten, dass die gepufferte Ausgabe umgekehrt wird.

Wenn Sie RAISERROR (@cmd, 0, 1) WITH NOWAIT;anstelle von PRINTverwenden, wird der Puffer nach jedem Aufruf geleert, sodass Sie die Ergebnisse in der erwarteten Reihenfolge erhalten. IIRC funktioniert der NOWAITTrick nur pro Zeile für die ersten 500 Zeilen. Auf jeden Fall ist dies alles undokumentiertes Zeug, das sich jederzeit ändern kann. Verlassen Sie sich also bitte nicht darauf - ich erwähne es nur, um zu erklären, was Sie sehen.

Die Umkehrung erfolgt nicht, wenn Sie den sp_executesqlAnruf durch EXEC (@cmd) AT ' + @Server + 'einen USE databaseBefehl ersetzen, dem ein Befehl vorangestellt ist, @cmdund der Verbindungsserver für RPC aktiviert sein muss. Dies ist auch keine Empfehlung, nur die Ausgabeumkehrung zu zeigen, ist eine Eigenart von sp_executesql.

Paul White 9
quelle
wow - ausführliche Antwort; Vielen Dank! Normalerweise benutze ich RAISERROR, werde aber faul und PRINT tippt weniger.
Max Vernon
5

Angesichts der neuen Informationen in den Kommentaren gibt es mindestens drei Lösungen dafür, die nach der tatsächlichen Reaktion kein Durcheinander von dynamischem SQL und Handbuch erfordern:

Verwenden Sie ein Lineal

Im Ernst, er ist ein DBA mit Aufgaben in Ihrem Unternehmen. Sicherlich können Sie eine Richtlinie implementieren, bei der Tabellen außer in bestimmten Szenarien Cluster-Indizes haben (und in diesem Fall müssen sie abgemeldet sein). Wenn dies als Richtlinie angegeben wird, wird dies zu einem potenziell quantifizierbaren Maß für die Arbeitsleistung.

Implementieren Sie einen DDL-Trigger

Es ist sehr einfach, CREATE_TABLEEreignisse in DDL-Triggern zu erfassen und dann die Katalogansichten sys.indexes oder sys.partitions auf das Vorhandensein eines Clustered-Index zu überprüfen. Kein Clustered-Index? Rollback. Dadurch wird die Möglichkeit ausgeschlossen SELECT INTO, da Sie keinen Clustered-Index im Voraus definieren können, aber vielleicht ist das in Ordnung.

Lass ihn sich in Azure entwickeln

Sie können keine Tabelle in der Cloud ohne einen Clustered-Index erstellen. Wenn Sie ihn dort entwickeln lassen, sind keine Lineale oder DDL-Trigger erforderlich.


Warum benutzt du Ordnungszahlen ? Anstatt:

ORDER BY 1,2,3 DESC;

Versuchen Sie, die Aliase direkt zu referenzieren:

ORDER BY TableName, IndexName, ExecOrder;

Außerdem verwenden Sie einen Cursor zum Aufbau @cmdund ich würde erwarten, dass Sie ihn irgendwo anhängen. So wie es aussieht, durchlaufen Sie den Cursor, führen jedoch immer nur @cmddie allerletzte Iteration in der Schleife aus. Was ist der Zweck des Cursors? Und warum verwenden Sie keine besseren Cursoroptionen, wenn der Cursor tatsächlich notwendig ist? (Wenn dies nicht der Fall ist, können Sie es in eine einzelne TOP-Abfrage ändern.)

Aaron Bertrand
quelle