Kueri yang dibagikan pengguna: SQL dinamis vs. SQLCMD

15

Saya harus memperbaiki dan mendokumentasikan sejumlah foo.sqlpertanyaan yang akan dibagikan oleh tim dukungan teknis DB (untuk konfigurasi pelanggan dan hal-hal seperti itu). Ada beberapa jenis tiket yang datang secara teratur di mana setiap pelanggan memiliki server dan database mereka sendiri, tetapi jika tidak, skemanya sama di seluruh papan.

Prosedur tersimpan bukan merupakan pilihan saat ini. Saya berdebat apakah akan menggunakan dinamis atau SQLCMD, saya belum banyak menggunakan karena saya agak baru di SQL Server.

Skrip SQLCMD Saya merasa "terlihat" lebih bersih bagi saya, dan lebih mudah membaca dan membuat perubahan kecil pada permintaan yang diperlukan, tetapi juga memaksa pengguna untuk mengaktifkan mode SQLCMD. Dinamis lebih sulit karena penyorotan sintaksnya hilang karena kueri ditulis menggunakan manipulasi string.

Ini sedang diedit dan dijalankan menggunakan Management Studio 2012, SQL versi 2008R2. Apa saja pro / kontra dari salah satu metode, atau beberapa "praktik terbaik" SQL Server pada satu metode atau yang lain? Apakah salah satu dari mereka "lebih aman" dari yang lain?

Contoh dinamis:

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

Contoh 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));
Phrancis
sumber
Apa tujuan dari use ...naskah Anda? Apakah penting untuk eksekusi yang benar dari query selanjutnya? Saya bertanya karena jika mengubah basis data saat ini adalah salah satu hasil yang diharapkan dari permintaan Anda, versi SQL dinamis hanya akan mengubahnya dalam lingkup permintaan dinamis, bukan dalam lingkup luar, tidak seperti variasi SQLCMD (yang, dari tentu saja, hanya memiliki satu ruang lingkup).
Andriy M
The usePernyataan mungkin bisa ditinggalkan, karena ruang lingkup tidak akan berubah selama skrip tertentu lagian. Saya memiliki sejumlah kecil kasus penggunaan di mana akan ada pencarian lintas-server tapi itu mungkin berada di luar cakupan tulisan ini.
Phrancis

Jawaban:

13

Hanya untuk mendapatkan ini:

  • Secara teknis, kedua opsi ini adalah "dinamis" / kueri ad hoc yang tidak diuraikan / divalidasi hingga diajukan. Dan keduanya rentan terhadap SQL Injection karena mereka tidak diparameterisasi (meskipun dengan skrip SQLCMD, jika Anda mengirimkan variabel dari skrip CMD maka Anda memiliki kesempatan untuk mengganti 'dengan '', yang mungkin atau mungkin tidak bekerja tergantung di mana variabel sedang digunakan).

  • Ada pro dan kontra untuk setiap pendekatan:

    • Skrip SQL dalam SSMS dapat dengan mudah diedit (yang bagus jika itu merupakan persyaratan) dan bekerja dengan hasil lebih mudah daripada dengan output dari SQLCMD. Sisi buruknya, pengguna berada dalam IDE sehingga mudah untuk mengacaukan SQL, dan IDE membuatnya mudah untuk membuat berbagai perubahan tanpa mengetahui SQL untuk melakukannya.
    • Menjalankan skrip melalui SQLCMD.EXE tidak memungkinkan pengguna untuk dengan mudah melakukan perubahan (tanpa mengedit skrip dalam editor dan kemudian menyimpannya terlebih dahulu). Ini bagus jika pengguna tidak seharusnya mengubah skrip. Metode ini juga memungkinkan untuk mencatat setiap eksekusi itu. Sisi buruknya, jika ada kebutuhan untuk secara rutin mengedit skrip maka itu akan cukup rumit. Atau, jika pengguna perlu memindai 100 ribu baris set hasil dan / atau menyalin hasil itu ke Excel atau sesuatu, maka itu juga sulit dalam pendekatan ini.

Jika orang-orang pendukung Anda tidak melakukan kueri ad hoc dan hanya mengisi variabel-variabel itu, maka mereka tidak perlu berada di SSMS tempat mereka dapat mengedit skrip tersebut dan membuat perubahan yang tidak diinginkan.

Saya akan membuat skrip CMD untuk meminta pengguna untuk nilai variabel yang diinginkan dan kemudian memanggil SQLCMD.EXE dengan nilai-nilai itu. Skrip CMD bahkan bisa mencatat eksekusi ke file, lengkap dengan cap waktu dan nilai variabel yang dikirimkan.

Buat satu skrip CMD per skrip SQL dan letakkan di folder bersama jaringan. Pengguna mengklik dua kali pada skrip CMD dan hanya berfungsi.

Berikut ini contohnya:

  • meminta pengguna untuk nama server (belum ada kesalahan memeriksa itu)
  • meminta pengguna untuk nama database
    • jika dibiarkan kosong, itu akan mencantumkan basis data di server yang ditentukan dan diminta lagi
    • jika nama database tidak valid, pengguna akan diminta lagi
  • meminta pengguna untuk OrderIDsSeparatedByCommas
    • jika kosong, meminta pengguna lagi
  • menjalankan skrip SQL, meneruskan nilai %OrderIDsSeparatedByCommas%sebagai variabel SQLCMD$(OrderIDsSeparatedByCommas)
  • mencatat tanggal eksekusi, waktu, ServerName, DatabaseName, dan OrderIDsSeparatedByCommas ke file log bernama untuk Windows Login yang menjalankan skrip (dengan cara ini, jika direktori log adalah jaringan dan ada beberapa orang yang menggunakan ini, tidak akan ada tulisan apa pun) pertentangan pada file log seperti mungkin ada jika USERNAME akan dicatat dalam file per entri)
    • jika direktori file log tidak ada, itu akan dibuat

Uji skrip SQL (bernama: FixProblemX.sql ):

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

Skrip CMD (bernama: 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

Pastikan untuk mengedit ScriptLogPathvariabel di bagian atas skrip.

Juga, skrip SQL (ditentukan oleh -isaklar baris perintah untuk SQLCMD.EXE ) mungkin mendapat manfaat dari memiliki jalur yang sepenuhnya memenuhi syarat, tetapi tidak sepenuhnya yakin.

Solomon Rutzky
sumber