Query condivise dall'utente: SQL dinamico vs. SQLCMD


15

Devo refactoring e documentare una serie di foo.sqldomande che saranno condivise da un team di supporto tecnico DB (per configurazioni dei clienti e cose del genere). Esistono tipi di ticket che arrivano regolarmente in cui ogni cliente ha i propri server e database, ma per il resto lo schema è lo stesso su tutta la linea.

Le procedure memorizzate non sono un'opzione al momento attuale. Sto discutendo se usare Dynamic o SQLCMD, non ne ho usato granché perché sono un po 'nuovo su SQL Server.

Scripting SQLCMD Mi sento decisamente "più pulito" per me, più facile da leggere e apportare piccole modifiche alle query secondo necessità, ma costringe anche l'utente ad abilitare la modalità SQLCMD. La dinamica è più difficile poiché l'evidenziazione della sintassi è una perdita dovuta alla scrittura della query mediante la manipolazione di stringhe.

Questi vengono modificati ed eseguiti utilizzando Management Studio 2012, versione SQL 2008R2. Quali sono alcuni dei pro / contro di entrambi i metodi o alcune delle "best practice" di SQL Server su un metodo o sull'altro? Uno di questi è "più sicuro" dell'altro?

Esempio dinamico:

declare @ServerName varchar(50) = 'REDACTED';
declare @DatabaseName varchar(50) = 'REDACTED';
declare @OrderIdsSeparatedByCommas varchar(max) = '597336, 595764, 594594';

declare @sql_OrderCheckQuery varchar(max) = ('
use {@DatabaseName};
select 
    -- stuff
from 
    {@ServerName}.{@DatabaseName}.[dbo].[client_orders]
        as "Order"
    inner join {@ServerName}.{@DatabaseName}.[dbo].[vendor_client_orders]
        as "VendOrder" on "Order".o_id = "VendOrder".vco_oid
where "VendOrder".vco_oid in ({@OrderIdsSeparatedByCommas});
');
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@ServerName}',   quotename(@ServerName)   );
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@DatabaseName}', quotename(@DatabaseName) );
set @sql_OrderCheckQuery = replace( @sql_OrderCheckQuery, '{@OrderIdsSeparatedByCommas}', @OrderIdsSeparatedByCommas );
print   (@sql_OrderCheckQuery); -- For debugging purposes.
execute (@sql_OrderCheckQuery);

Esempio SQLCMD:

:setvar ServerName "[REDACTED]";
:setvar DatabaseName "[REDACTED]";
:setvar OrderIdsSeparatedByCommas "597336, 595764, 594594"

use $(DatabaseName)
select 
    --stuff
from 
    $(ServerName).$(DatabaseName).[dbo].[client_orders]
        as "Order"
    inner join $(ServerName).$(DatabaseName).[dbo].[vendor_client_orders]
        as "VendOrder" on "Order".o_id = "VendOrder".vco_oid
where "VendOrder".vco_oid in ($(OrderIdsSeparatedByCommas));

Qual è lo scopo della use ...tua sceneggiatura? È importante per la corretta esecuzione della query successiva? Sto chiedendo perché se la modifica del database corrente è uno dei risultati previsti della query, la versione SQL dinamica lo modificherà solo nell'ambito della query dinamica, non nell'ambito esterno, a differenza della variazione SQLCMD (che, di ovviamente, ha un solo scopo).
Andriy M,

L' useaffermazione potrebbe probabilmente essere lasciata fuori, poiché l'ambito non verrà comunque modificato durante questo particolare script. Ho un numero limitato di casi d'uso in cui ci saranno ricerche tra server ma che potrebbero andare oltre lo scopo di questo post.
Francis,

Risposte:


13

Giusto per toglierli di mezzo:

  • Tecnicamente parlando, entrambe queste opzioni sono query "dinamiche" / ad hoc che non vengono analizzate / convalidate fino a quando non vengono inviate. Ed entrambi sono suscettibili all'iniezione SQL poiché non sono parametrizzati (anche se con gli script SQLCMD, se si passa una variabile da uno script CMD, si ha l'opportunità di sostituire' con '', che può o non può lavorare a seconda di dove la vengono utilizzate le variabili).

  • Ci sono pro e contro per ogni approccio:

    • Gli script SQL in SSMS possono essere facilmente modificati (il che è fantastico se questo è un requisito) e lavorare con i risultati è più facile che con l'output di SQLCMD. Sul lato negativo, l'utente si trova in un IDE, quindi è facile confondere l'SQL e l'IDE rende semplice apportare una grande varietà di modifiche senza conoscere l'SQL per farlo.
    • L'esecuzione di script tramite SQLCMD.EXE non consente all'utente di apportare facilmente modifiche (senza modificare lo script in un editor e quindi salvarlo prima). Questo è fantastico se gli utenti non dovrebbero cambiare gli script. Questo metodo consente anche di registrare ogni esecuzione di esso. Sul lato negativo, se è necessario modificare sistematicamente gli script, sarebbe piuttosto ingombrante. Oppure, se gli utenti devono eseguire la scansione di 100.000 righe di un set di risultati e / o copiare tali risultati in Excel o qualcosa del genere, anche in questo approccio risulta difficile.

Se il personale di supporto non sta eseguendo query ad hoc e sta semplicemente compilando tali variabili, non è necessario che si trovino in SSMS in cui possono modificare tali script e apportare modifiche indesiderate.

Vorrei creare script CMD per richiedere all'utente i valori delle variabili desiderati e quindi chiamare SQLCMD.EXE con tali valori. Lo script CMD potrebbe persino registrare l'esecuzione in un file, completo di data e ora e valori variabili inviati.

Creare uno script CMD per script SQL e posizionarlo in una cartella condivisa in rete. Un utente fa doppio clic sullo script CMD e funziona.

Ecco un esempio che:

  • richiede all'utente il nome del server (nessun errore verifica ancora su quello)
  • richiede all'utente il nome del database
    • se lasciato vuoto, elencherà i database sul server specificato e verrà nuovamente richiesto
    • se il nome del database non è valido, verrà richiesto nuovamente l'utente
  • richiede all'utente OrderIDsSeparatedByCommas
    • se vuoto, richiede nuovamente all'utente
  • esegue lo script SQL, passando il valore di %OrderIDsSeparatedByCommas%come variabile SQLCMD$(OrderIDsSeparatedByCommas)
  • registra la data, l'ora, ServerName, DatabaseName e OrderIDsSeparatedByCommas di esecuzione in un file di registro denominato per il login di Windows che esegue lo script (in questo modo, se la directory di registro è di rete e ci sono più persone che usano questo, non ci sarà alcuna scrittura contesa sul file di registro come potrebbe esserci se USERNAME dovesse essere registrato nel file per voce)
    • se la directory del file di registro non esiste, verrà creata

Test SQL script (denominato: FixProblemX.sql ):

SELECT  *
FROM    sys.objects
WHERE   [schema_id] IN ($(OrderIdsSeparatedByCommas));

Script CMD (denominato: FixProblemX.cmd ):

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION

SET ScriptLogPath=\\server\share\RunSqlCmdScripts\LogFiles

CLS

SET /P ScriptServerName=Please enter in a Server Name (leave blank to exit): 

IF "%ScriptServerName%" == "" GOTO :ThisIsTheEnd

REM echo %ScriptServerName%

:RequestDatabaseName
ECHO.
SET /P ScriptDatabaseName=Please enter in a Database Name (leave blank to list DBs on %ScriptServerName%): 

IF "%ScriptDatabaseName%" == "" GOTO :GetDatabaseNames

SQLCMD -b -E -W -h-1 -r0 -S %ScriptServerName% -Q "SET NOCOUNT ON; IF (NOT EXISTS(SELECT [name] FROM sys.databases WHERE [name] = N'%ScriptDatabaseName%')) RAISERROR('Invalid DB name!', 16, 1);" 2> nul

IF !ERRORLEVEL! GTR 0 (
    ECHO.
    ECHO That Database Name is invalid. Please try again.

    SET ScriptDatabaseName=
    GOTO :RequestDatabaseName
)

:RequestOrderIDs
ECHO.
SET /P OrderIdsSeparatedByCommas=Please enter in the OrderIDs (separate multiple IDs with commas): 

IF "%OrderIdsSeparatedByCommas%" == "" (

    ECHO.
    ECHO Don't play me like that. You gots ta enter in at least ONE lousy OrderID, right??
    GOTO :RequestOrderIDs
)


REM Finally run SQLCMD!!
SQLCMD -E -W -S %ScriptServerName% -d %ScriptDatabaseName% -i FixProblemX.sql -v OrderIdsSeparatedByCommas=%OrderIdsSeparatedByCommas%

REM Log this execution
SET ScriptLogFile=%ScriptLogPath%\%~n0_%USERNAME%.log
REM echo %ScriptLogFile%

IF NOT EXIST %ScriptLogPath% MKDIR %ScriptLogPath%

ECHO %DATE% %TIME% ServerName=%ScriptServerName%    DatabaseName=[%ScriptDatabaseName%] OrderIdsSeparatedByCommas=%OrderIdsSeparatedByCommas%   >> %ScriptLogFile%

GOTO :ThisIsTheEnd

:GetDatabaseNames
ECHO.
SQLCMD -E -W -h-1 -S %ScriptServerName% -Q "SET NOCOUNT ON; SELECT [name] FROM sys.databases ORDER BY [name];"
ECHO.
GOTO :RequestDatabaseName

:ThisIsTheEnd
PAUSE

Assicurati di modificare la ScriptLogPathvariabile nella parte superiore dello script.

Inoltre, gli script SQL (specificati dall'opzione della -iriga di comando per SQLCMD.EXE ) potrebbero trarre vantaggio dall'avere un percorso completo, ma non sono del tutto sicuri.

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.