Consultas compartidas por el usuario: SQL dinámico frente a SQLCMD


15

Tengo que refactorizar y documentar una serie de foo.sqlconsultas que serán compartidas por un equipo de soporte técnico de DB (para configuraciones de clientes y cosas por el estilo). Hay tipos de tickets que vienen regularmente donde cada cliente tiene sus propios servidores y bases de datos, pero de lo contrario el esquema es el mismo en todos los ámbitos.

Los procedimientos almacenados no son una opción en este momento. Estoy debatiendo si usar dinámico o SQLCMD, no he usado mucho ya que soy un poco nuevo en SQL Server.

Las secuencias de comandos SQLCMD definitivamente me parecen "más limpias", más fáciles de leer y realizar pequeños cambios en las consultas según sea necesario, pero también obliga al usuario a habilitar el modo SQLCMD. La dinámica es más difícil ya que el resaltado de sintaxis es pérdida debido a que la consulta se escribe utilizando la manipulación de cadenas.

Estos se están editando y ejecutando usando Management Studio 2012, SQL versión 2008R2. ¿Cuáles son algunos de los pros / contras de cualquiera de los métodos, o algunas de las "mejores prácticas" de SQL Server en un método u otro? ¿Es uno de ellos "más seguro" que el otro?

Ejemplo dinámico:

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

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

¿Cuál es el propósito de use ...tu guión? ¿Es importante para la correcta ejecución de la consulta posterior? Lo pregunto porque si cambiar la base de datos actual es uno de los resultados esperados de su consulta, la versión dinámica de SQL solo lo cambiará en el alcance de la consulta dinámica, no en el alcance externo, a diferencia de la variación SQLCMD (que, de Por supuesto, tiene un solo alcance).
Andriy M

La usedeclaración probablemente podría omitirse, ya que el alcance no se cambiará durante este script en particular de todos modos. Tengo una pequeña cantidad de casos de uso en los que habrá búsquedas entre servidores, pero eso podría estar fuera del alcance de esta publicación.
Phrancis

Respuestas:


13

Solo para sacarlos del camino:

  • Técnicamente hablando, ambas opciones son consultas "dinámicas" / ad hoc que no se analizan / validan hasta que se envían. Y ambos son susceptibles a la inyección de SQL, ya que no se parametrizan (aunque con los guiones SQLCMD, si estás de paso en una variable a partir de una secuencia de comandos CMD entonces usted tiene la oportunidad de reemplazar 'con '', que puede o puede no funcionar dependiendo de la ubicación del Se están utilizando variables).

  • Hay ventajas y desventajas de cada enfoque:

    • Las secuencias de comandos SQL en SSMS se pueden editar fácilmente (lo cual es excelente si es un requisito) y trabajar con resultados es más fácil que con la salida de SQLCMD. En el lado negativo, el usuario está en un IDE, por lo que es fácil estropear el SQL, y el IDE hace que sea fácil realizar una gran variedad de cambios sin conocer el SQL para hacerlo.
    • La ejecución de scripts a través de SQLCMD.EXE no permite al usuario realizar cambios fácilmente (sin editar el script en un editor y luego guardarlo primero). Esto es genial si no se supone que los usuarios estén cambiando los scripts. Este método también permite registrar cada ejecución del mismo. En el lado negativo, si es necesario editar los scripts de forma rutinaria, sería bastante engorroso. O, si los usuarios necesitan escanear a través de 100k filas de un conjunto de resultados y / o copiar esos resultados a Excel o algo así, eso también es difícil en este enfoque.

Si su personal de soporte no está haciendo consultas ad hoc y solo está completando esas variables, entonces no necesitan estar en SSMS donde puedan editar esos scripts y realizar cambios no deseados.

Crearía scripts CMD para solicitar al usuario los valores de las variables deseadas y luego llamaría a SQLCMD.EXE con esos valores. La secuencia de comandos CMD podría incluso registrar la ejecución en un archivo, completa con la marca de tiempo y los valores variables enviados.

Cree un script CMD por script SQL y colóquelo en una carpeta compartida en red. Un usuario hace doble clic en el script CMD y simplemente funciona.

Aquí hay un ejemplo que:

  • solicita al usuario el nombre del servidor (todavía no hay ningún error al comprobarlo)
  • solicita al usuario el nombre de la base de datos
    • si se deja en blanco, enumerará las bases de datos en el servidor especificado y se le solicitará nuevamente
    • Si el nombre de la base de datos no es válido, se le solicitará al usuario nuevamente
  • solicita al usuario los OrderIDsSeparatedByCommas
    • si está en blanco, vuelve a preguntar al usuario
  • ejecuta el script SQL, pasando el valor de %OrderIDsSeparatedByCommas%como la variable SQLCMD$(OrderIDsSeparatedByCommas)
  • registra la fecha de ejecución, la hora, ServerName, DatabaseName y OrderIDsSeparatedByCommas en un archivo de registro nombrado para el inicio de sesión de Windows que ejecuta el script (de esta manera, si el directorio de registro es de red y hay varias personas que usan esto, no habrá ninguna escritura contención en el archivo de registro como podría haber si el nombre de usuario se registrara en el archivo por entrada)
    • si el directorio del archivo de registro no existe, se creará

Probar script SQL (llamado: FixProblemX.sql ):

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

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

Asegúrese de editar la ScriptLogPathvariable hacia la parte superior del script.

Además, las secuencias de comandos SQL (especificadas por el modificador de-i línea de comandos para SQLCMD.EXE ) pueden beneficiarse de tener una ruta totalmente calificada, pero no del todo seguro.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.