Запити, що користуються спільним користувачем: Dynamic SQL vs. SQLCMD


15

Мені доведеться перефактурувати і задокументувати ряд foo.sqlзапитів, якими поділятиметься команда технічної підтримки БД (для конфігурацій клієнтів і подібних речей). Існують типи квитків, які регулярно надходять, коли кожен клієнт має власні сервери та бази даних, але в іншому випадку схеми є однаковими для всіх.

Збережені процедури не є варіантом на даний момент. Я обговорюю, чи використовувати динамічний або SQLCMD, я не використовував багато з них, оскільки я трохи новий у SQL Server.

Сценарій SQLCMD Я відчуваю, що напевно "виглядає" чистішим для мене, і простіше читати та вносити невеликі зміни в запити за потребою, але також змушує користувача ввімкнути режим SQLCMD. Динамічне складніше, оскільки виділення синтаксису - це втрата через запит, записаний за допомогою маніпуляції з рядком.

Вони редагуються та розповсюджуються за допомогою Management Studio 2012, версії SQL 2008R2. Які є плюси чи мінуси того чи іншого методу чи деякі "найкращі практики" SQL Server у тому чи іншому методі? Чи одна з них "безпечніша", ніж інша?

Динамічний приклад:

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

Приклад 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));

Яка мета use ...вашого сценарію? Чи важливо правильне виконання наступного запиту? Я прошу, бо якщо зміна поточної бази даних є одним із очікуваних результатів вашого запиту, динамічна версія SQL змінить її лише в області динамічного запиту, а не у зовнішній області, на відміну від варіації SQLCMD (що, з Звичайно, має лише одну сферу застосування).
Андрій М

useЗаява, ймовірно , може бути опущено, так як обсяг не буде змінений в протягом цього конкретного сценарію в будь-якому випадку. У мене є невелика кількість випадків використання, коли буде пошук між серверами, але це може вийти за рамки цієї публікації.
Франкіс

Відповіді:


13

Просто, щоб усунути це:

  • Технічно кажучи, обидва ці варіанти є "динамічними" / спеціальними запитами, які не аналізуються / перевіряються до моменту подання. І обидва сприйнятливі до SQL - ін'єкції , оскільки вони не визначені установки (хоча зі сценаріями Sqlcmd, якщо ви передаєте в змінної зі сценарію CMD , то у вас є можливість замінити 'з '', що може або не може працювати в залежності від того, де використовуються змінні).

  • У кожного підходу є плюси і мінуси:

    • Сценарії SQL в SSMS можна легко редагувати (що чудово, якщо це вимога), а працювати з результатами простіше, ніж з результатами SQLCMD. З нижньої сторони, користувач перебуває в IDE, тому його легко зіпсувати SQL, а IDE дозволяє легко вносити великі зміни, не знаючи SQL, щоб це зробити.
    • Запуск сценаріїв через SQLCMD.EXE не дозволяє користувачеві легко вносити зміни (не редагуючи скрипт у редакторі, а потім зберігаючи його спочатку). Це чудово, якщо користувачі не повинні змінювати сценарії. Цей метод також дозволяє вести журнал кожного його виконання. З іншого боку, якщо є необхідність регулярно редагувати сценарії, то це було б досить громіздко. Або, якщо користувачам потрібно сканувати через 100k рядків набору результатів та / або скопіювати ці результати в Excel або щось подібне, це теж складно в такому підході.

Якщо ваші спонсори не займаються спеціальними запитами і просто заповнюють ці змінні, їм не потрібно знаходитись у SSMS, де вони можуть редагувати ці сценарії та вносити небажані зміни.

Я б створив сценарії CMD, щоб підказати користувачеві потрібні значення змінної, а потім викликати SQLCMD.EXE з цими значеннями. Сценарій CMD навіть може зафіксувати виконання у файлі, доповненому часовою маркою та поданими змінними значеннями.

Створіть один скрипт CMD на сценарій SQL і помістіть у загальномережеву спільну папку. Користувач двічі клацає на скрипті CMD, і він просто працює.

Ось приклад, який:

  • запрошує користувача на ім’я сервера (ще не було перевірки помилок на цьому)
  • запрошує користувача на ім’я бази даних
    • якщо залишити порожнім, він відобразить список баз даних на вказаному сервері та запросить знову
    • якщо ім'я бази даних недійсне, користувачеві буде запропоновано знову
  • запрошує користувача для OrderIDsSeparatedByCommas
    • якщо порожнє, запрошує користувача знову
  • запускає скрипт SQL, передаючи значення %OrderIDsSeparatedByCommas%як змінну SQLCMD$(OrderIDsSeparatedByCommas)
  • записує дату, час виконання, ім’я ServerName, DatabaseName та OrderIDsSeparatedByCommas у файл журналу, іменований для Windows Login для запуску скрипту (таким чином, якщо каталог журналів є мережевим і багато людей використовують це, запису не буде суперечка у файлі журналу, як може бути, якщо USERNAME повинен був увійти до файлу за кожну запис)
    • якщо каталог файлів журналу не існує, він буде створений

Тестовий сценарій SQL (названий: FixProblemX.sql ):

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

Сценарій CMD (названий: 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

Обов’язково відредагуйте ScriptLogPathзмінну у верхній частині сценарію.

Також сценарії SQL (визначені -iперемикачем командного рядка для SQLCMD.EXE ) можуть отримати користь від повноцінного шляху, але не зовсім впевненого.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.