Requêtes partagées par l'utilisateur: SQL dynamique contre SQLCMD


15

Je dois refactoriser et documenter un certain nombre de foo.sqlrequêtes qui seront partagées par une équipe de support technique DB (pour les configurations client et des choses comme ça). Il existe des types de tickets qui viennent régulièrement lorsque chaque client a ses propres serveurs et bases de données, mais sinon le schéma est le même à tous les niveaux.

Les procédures stockées ne sont pas une option à l'heure actuelle. Je suis en train de débattre de l'utilisation de Dynamic ou SQLCMD, je n'en ai pas beaucoup utilisé car je suis un peu nouveau chez SQL Server.

Les scripts SQLCMD Je me sens vraiment "plus" propre pour moi, plus facile à lire et à apporter de petites modifications aux requêtes selon les besoins, mais force également l'utilisateur à activer le mode SQLCMD. La dynamique est plus difficile car la mise en évidence de la syntaxe est une perte due à l'écriture de la requête à l'aide de la manipulation de chaînes.

Ceux-ci sont en cours d'édition et exécutés à l'aide de Management Studio 2012, SQL version 2008R2. Quels sont les avantages / inconvénients de l'une ou l'autre méthode, ou certaines des "meilleures pratiques" de SQL Server sur une méthode ou l'autre? L'un d'eux est-il "plus sûr" que l'autre?

Exemple dynamique:

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);

Exemple 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));

À quoi sert use ...votre script? Est-ce important pour l'exécution correcte de la requête suivante? Je demande parce que si la modification de la base de données actuelle est l'un des résultats attendus de votre requête, la version SQL dynamique ne la modifiera que dans la portée de la requête dynamique, pas dans la portée externe, contrairement à la variation SQLCMD (qui, de n’a qu’une seule portée).
Andriy M

L' useinstruction pourrait probablement être omise, car la portée ne sera pas modifiée de toute façon pendant ce script particulier. J'ai un petit nombre de cas d'utilisation où il y aura des recherches inter-serveurs mais qui pourraient être au-delà de la portée de cet article.
Phrancis

Réponses:


13

Juste pour les éliminer:

  • Techniquement parlant, ces deux options sont des requêtes "dynamiques" / ad hoc qui ne sont pas analysées / validées tant qu'elles n'ont pas été soumises. Et les deux sont sensibles à l'injection SQL car ils ne sont pas paramétrés (bien qu'avec les scripts SQLCMD, si vous transmettez une variable à partir d'un script CMD, vous avez la possibilité de la remplacer 'par '', qui peut ou non fonctionner selon l'endroit où le variables sont utilisées).

  • Il y a des avantages et des inconvénients à chaque approche:

    • Les scripts SQL dans SSMS peuvent être facilement modifiés (ce qui est bien si c'est une exigence) et travailler avec les résultats est plus facile qu'avec la sortie de SQLCMD. En revanche, l'utilisateur est dans un IDE, il est donc facile de gâcher le SQL, et l'IDE permet d'effectuer facilement une grande variété de changements sans connaître le SQL pour le faire.
    • L'exécution de scripts via SQLCMD.EXE ne permet pas à l'utilisateur d'apporter facilement des modifications (sans modifier le script dans un éditeur, puis l'enregistrer d'abord). C'est formidable si les utilisateurs ne sont pas censés modifier les scripts. Cette méthode permet également d'enregistrer chaque exécution de celle-ci. En revanche, s'il est nécessaire de modifier régulièrement les scripts, ce serait assez lourd. Ou, si les utilisateurs ont besoin de parcourir 100 000 lignes d'un jeu de résultats et / ou de copier ces résultats dans Excel ou quelque chose, cela est également difficile dans cette approche.

Si vos assistants ne font pas de requêtes ad hoc et remplissent simplement ces variables, ils n'ont pas besoin d'être dans SSMS où ils peuvent éditer ces scripts et apporter des modifications indésirables.

Je créerais des scripts CMD pour demander à l'utilisateur les valeurs de variable souhaitées, puis j'appellerais SQLCMD.EXE avec ces valeurs. Le script CMD pourrait même consigner l'exécution dans un fichier, avec horodatage et valeurs variables soumises.

Créez un script CMD par script SQL et placez-le dans un dossier partagé en réseau. Un utilisateur double-clique sur le script CMD et cela fonctionne.

Voici un exemple qui:

  • demande à l'utilisateur le nom du serveur (pas encore de vérification d'erreur à ce sujet)
  • invite l'utilisateur à entrer le nom de la base de données
    • s'il est laissé vide, il répertoriera les bases de données sur le serveur spécifié et sera de nouveau invité
    • si le nom de la base de données n'est pas valide, l'utilisateur sera de nouveau invité
  • invite l'utilisateur à indiquer les OrderIDsSeparatedByCommas
    • si vide, invite à nouveau l'utilisateur
  • exécute le script SQL, en transmettant la valeur de %OrderIDsSeparatedByCommas%comme variable SQLCMD$(OrderIDsSeparatedByCommas)
  • enregistre la date, l'heure, ServerName, DatabaseName et OrderIDsSeparatedByCommas dans un fichier journal nommé pour la connexion Windows exécutant le script (de cette façon, si le répertoire du journal est réseau et que plusieurs personnes l'utilisent, il n'y aura pas d'écriture conflit sur le fichier journal comme il pourrait y en avoir si USERNAME devait être enregistré dans le fichier par entrée)
    • si le répertoire du fichier journal n'existe pas, il sera créé

Tester le script SQL (nommé: FixProblemX.sql ):

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

Script CMD (nommé: 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

Assurez-vous de modifier la ScriptLogPathvariable vers le haut du script.

En outre, les scripts SQL (spécifiés par le -icommutateur de ligne de commande pour SQLCMD.EXE ) peuvent bénéficier d'un chemin d'accès complet, mais pas entièrement sûr.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.