ทำไมฉันไม่สามารถใช้ตัวแปรใน T-SQL เหมือนที่ฉันจินตนาการได้


18

ยกโทษให้ฉันฉันเป็นนักพัฒนาที่ได้ย้ายไปยังโลกของ SQL ฉันคิดว่าฉันสามารถปรับปรุง SQL ด้วยการเพิ่มตัวแปร แต่มันไม่ทำงานเหมือนที่ฉันคาดไว้ มีใครบอกฉันได้ไหมว่าทำไมสิ่งนี้ถึงใช้งานไม่ได้ ฉันไม่ต้องการทำงานฉันต้องการทราบเหตุผลว่าทำไมมันถึงใช้งานไม่ได้ฉันคิดว่ามันควรจะเป็นเพราะฉันแน่ใจว่ามีเหตุผลที่ดี

DECLARE @DatabaseName varchar(150)
SET @DatabaseName = 'MyAmazingDatabaseName'

CREATE DATABASE @DatabaseName
GO

USE @DatabaseName
GO

คุณต้องใช้ไดนามิก sql สำหรับสิ่งนั้น mssqltips.com/sqlservertip/1160/…
Stijn Wynants

3
เฮ้ขอบคุณสำหรับการแสดงความคิดเห็น แต่ตามคำถามของฉันนี่ไม่ใช่คำตอบที่ฉันกำลังมองหา ฉันต้องการที่จะรู้ว่าถ้าใครรู้ว่าทำไมฉันไม่สามารถทำมันได้เหมือนที่ฉันได้แสดง
แกเร็ ธ

2
เพราะมันเป็นไปไม่ได้ที่จะใช้USEคำสั่งกับพารามิเตอร์
TT

2
นอกเหนือจากคำตอบที่ให้ไปแล้ว แต่ไม่เพียงพอสำหรับคำตอบของตัวเองตัวแปรจะถูกกำหนดขอบเขตให้สูงสุดของแบตช์ปัจจุบัน (ฉันไม่รู้ว่ามือนั้นมีความเป็นไปได้ที่จะกำหนดขอบเขตตัวแปรให้แคบกว่าใน SQL Server) ดังนั้นเมื่อคุณมีGOตัวแปรที่ประกาศก่อนหน้านี้จะหายไป คุณอาจต้องการค้นหาตัวแปร SQLCMD ซึ่งอาจจะใช่หรือไม่ใช่กับสถานการณ์ของคุณ
CVN

1
เรามักจะนึกถึงชื่อฐานข้อมูลและชื่อคอลัมน์เป็นค่าสตริง แต่ในบริบทของ SQL จะเป็นตัวระบุ สิ่งที่คุณพยายามจะเป็นเช่นเดียวกับที่คาดหวัง x = 7; ให้เหมือนกับ 'x' = 7; ในภาษาอื่น ๆ เช่นเดียวกับภาษาคอมพิวเตอร์ที่สามารถสร้างขึ้นซึ่งเกี่ยวข้องกับ 'x' = 7 เหมือนกับ x = 7 สามารถสร้าง RDBMS ที่ใช้สร้าง Table X เหมือนกับ Create Table 'X' แต่นั่นไม่ใช่ SQL
user1008646

คำตอบ:


20

สำหรับหน้าหนังสือออนไลน์สำหรับตัวแปร

ตัวแปรสามารถใช้ได้เฉพาะในนิพจน์เท่านั้นไม่ใช่ตำแหน่งของชื่อวัตถุหรือคำหลัก ในการสร้างคำสั่ง SQL แบบไดนามิกให้ใช้ EXECUTE

มันจะทำงานในแบบที่คุณคาดหวังเช่นถ้าคุณใช้ตัวแปรของคุณในส่วนคำสั่ง สำหรับสาเหตุที่ฉันคิดว่ามันมีบางอย่างที่เกี่ยวข้องกับ parser ที่ไม่สามารถประเมินตัวแปรและตรวจสอบการมีอยู่ เมื่อดำเนินการเคียวรีจะถูกวิเคราะห์คำแรกสำหรับไวยากรณ์และวัตถุจากนั้นหากการแยกวิเคราะห์สำเร็จแบบสอบถามจะดำเนินการตามจุดที่ตัวแปรนั้นจะถูกตั้งค่า

DECLARE @name varchar(20);
SET @name = 'test';

CREATE TABLE [#tmp]([val] varchar(10));

insert into #tmp
values('test')

SELECT *
FROM [#tmp]
WHERE [val] = @name;

3
โปรดทราบว่า SQL แบบไดนามิกควรหลีกเลี่ยงเมื่อเป็นไปได้ มันเป็น SQL แบบอะนาล็อกที่ใช้evalฟังก์ชั่นในภาษาเชิงโพรซีเดอร์เช่น JavaScript และ Python มันเป็นวิธีที่รวดเร็วในการสร้างช่องโหว่ด้านความปลอดภัย
jpmc26

1
@ jpmc26: วิธีที่ปลอดภัยกว่าที่จะทำคือไม่เกี่ยวข้องกับ SQL แบบไดนามิกคืออะไร?
Robert Harvey

1
@RobertHarvey เพียงเพราะการหลีกเลี่ยงที่ดีที่สุดไม่ได้หมายความว่าจะมีทางเลือกอื่น ๆ ;) บ่อยครั้งส่วนหนึ่งของคำตอบคือ "ใช้วิธีแก้ไขปัญหาที่แตกต่างไปจากเดิมอย่างสิ้นเชิง" บางครั้งมันเป็นสิ่งที่ดีที่สุดที่จะทำ แต่ไม่ใช่โดยไม่มีการไตร่ตรองอย่างรอบคอบและทำให้แน่ใจว่าคุณไม่ได้ละเลยทางเลือกอื่นและถึงอย่างนั้นมันก็ควรมาพร้อมกับความระมัดระวัง
jpmc26

2
@ jpmc26: ตัวอย่างของ OP ดูเหมือนว่า ORM "Code-First" อาจทำเพื่อตั้งค่าตารางในฐานข้อมูล ในขณะที่ไดนามิก SQL นั้นไม่ปลอดภัยตามหลักการผู้ใช้ปลายทางจะไม่แตะต้องรหัสนั้น
Robert Harvey

@RobertHarvey ขึ้นอยู่กับบุคคลที่คุณพิจารณาว่า "ผู้ใช้ปลายทาง" สำหรับสคริปต์ที่ปรับใช้ฐานข้อมูลฉันจะพิจารณาผู้พัฒนาและผู้ดูแลระบบบางรายอาจเป็น "ผู้ใช้ปลายทาง" ฉันยังคงออกแบบให้ระบบปฏิเสธการป้อนข้อมูลที่ไม่ปลอดภัยในกรณีนั้นหากไม่มีเหตุผลอื่นนอกจากเพื่อหลีกเลี่ยงอุบัติเหตุ นอกจากนี้สำหรับ "ไม่เคยสัมผัส" OP กำลังแตะต้องรหัสนี้ดังนั้น ...
jpmc26

17

ข้อ จำกัด ในการใช้ตัวแปรในคำสั่ง SQL เกิดขึ้นจากสถาปัตยกรรมของ SQL

มีสามขั้นตอนในการประมวลผลคำสั่ง SQL:

  1. การเตรียมการ - มีการแยกวิเคราะห์คำสั่งและรวบรวมแผนการดำเนินการโดยระบุว่าวัตถุฐานข้อมูลใดที่เข้าถึงวิธีเข้าถึงและวิธีการเชื่อมโยง แผนการดำเนินการถูกบันทึกไว้ในแผนแคชแคชแผน
  2. Binding - ตัวแปรใด ๆ ในคำสั่งจะถูกแทนที่ด้วยค่าจริง
  3. การดำเนินการ - แผนแคชจะดำเนินการด้วยค่าที่ถูกผูกไว้

เซิร์ฟเวอร์ SQL ซ่อนขั้นตอนการเตรียมการจากโปรแกรมเมอร์และดำเนินการได้เร็วกว่าฐานข้อมูลแบบดั้งเดิมเช่น Oracle และ DB2 เพื่อเหตุผลด้านประสิทธิภาพที่ SQL ใช้เวลาเป็นจำนวนมากในการพิจารณาแผนการดำเนินการที่ดีที่สุด แต่จะทำในครั้งแรกที่พบคำสั่งหลังจากรีสตาร์ทเท่านั้น

ดังนั้นในSQL แบบคงที่ตัวแปรอาจถูกใช้ในสถานที่ที่พวกเขาจะไม่ทำให้แผนการดำเนินการเป็นโมฆะดังนั้นไม่ควรใช้กับชื่อตารางชื่อคอลัมน์ (รวมถึงชื่อคอลัมน์ในเงื่อนไข WHERE) เป็นต้น

Dynamic SQLมีอยู่สำหรับกรณีที่ไม่มีข้อ จำกัด และโปรแกรมเมอร์รู้ดีว่าจะต้องใช้เวลาดำเนินการนานกว่าเล็กน้อย Dynamic SQL อาจมีความเสี่ยงต่อการฉีดโค้ดที่เป็นอันตรายดังนั้นระวัง!


7

อย่างที่คุณเห็นคำถาม "ทำไม" ต้องใช้คำตอบที่แตกต่างกันรวมถึงเหตุผลทางประวัติศาสตร์และสมมติฐานพื้นฐานสำหรับภาษาฉันไม่แน่ใจว่าฉันจะสามารถทำสิ่งนั้นได้จริง

บทความที่ครอบคลุมโดย SQL MVP Erland Sommarskog พยายามให้เหตุผลบางอย่างพร้อมกับกลไก:

คำสาปและพรของ Dynamic SQL :

แผนการแคชแบบสอบถาม

แบบสอบถามทั้งหมดที่คุณเรียกใช้ใน SQL Server ต้องมีแผนแบบสอบถาม เมื่อคุณเรียกใช้คิวรีในครั้งแรก SQL Server จะสร้างแผนคิวรีสำหรับแบบสอบถามหรือเป็นคำศัพท์ที่ใช้ไปซึ่งจะรวบรวมคิวรี SQL Server บันทึกแผนในแคชและครั้งถัดไปที่คุณเรียกใช้แบบสอบถามแผนจะถูกนำมาใช้ใหม่

นี่ (และความปลอดภัยดูด้านล่าง) น่าจะเป็นเหตุผลที่ดีที่สุด

SQL ทำงานภายใต้สมมติฐานที่ว่าการสืบค้นไม่ใช่การดำเนินการเพียงครั้งเดียว แต่จะมีการใช้แบบสอบถามซ้ำไปซ้ำมา หากไม่มีการระบุตาราง (หรือฐานข้อมูล!) ในแบบสอบถามจริง ๆ ไม่มีวิธีการสร้างและบันทึกแผนการดำเนินการสำหรับการใช้งานในอนาคต

ใช่ไม่ใช่ทุกคำถามที่เราเรียกใช้จะถูกนำมาใช้ใหม่ แต่นี่คือหลักฐานการดำเนินงานเริ่มต้นของ SQLดังนั้น "ข้อยกเว้น" จึงมีความพิเศษ

เหตุผลอื่น ๆ อีกสองสามรายการ Erland (โปรดทราบว่าเขาแสดงรายการข้อดีของการใช้กระบวนงานที่เก็บไว้อย่างชัดเจนแต่สิ่งเหล่านี้ส่วนใหญ่เป็นข้อดีของแบบสอบถามแบบใช้พารามิเตอร์ (ไม่ใช่แบบไดนามิก):

  • ระบบอนุญาต : เครื่องมือ SQL ไม่สามารถคาดการณ์ได้ว่าคุณมีสิทธิ์ในการเรียกใช้แบบสอบถามหรือไม่หากไม่ทราบว่ามีตาราง (หรือฐานข้อมูล) ที่คุณจะใช้งาน "โซ่สิทธิ์" โดยใช้ไดนามิก SQL เป็นความเจ็บปวดในก้น
  • การลดปริมาณการใช้เครือข่าย : การส่งชื่อของ proc ที่เก็บไว้และค่าพารามิเตอร์สองสามตัวบนเครือข่ายนั้นสั้นกว่าคำสั่งการสืบค้นแบบยาว
  • Encapsulating Logic : คุณควรคุ้นเคยกับข้อดีของการห่อหุ้มตรรกะจากสภาพแวดล้อมการเขียนโปรแกรมอื่น ๆ
  • การติดตามสิ่งที่ใช้ : หากฉันต้องการเปลี่ยนคำจำกัดความของคอลัมน์ฉันจะค้นหารหัสทั้งหมดที่เรียกได้อย่างไร กระบวนการของระบบมีอยู่เพื่อค้นหาการอ้างอิงภายในฐานข้อมูล SQL แต่ถ้ารหัสอยู่ในกระบวนงานที่เก็บไว้
  • ความง่ายในการเขียนรหัส SQL : การตรวจสอบไวยากรณ์เกิดขึ้นเมื่อคุณสร้างหรือแก้ไขขั้นตอนการจัดเก็บดังนั้นหวังว่าจะเกิดข้อผิดพลาดน้อยลง
  • การจัดการข้อบกพร่องและปัญหา : DBA สามารถติดตามและวัดประสิทธิภาพของขั้นตอนการจัดเก็บแต่ละรายการได้ง่ายกว่า SQL แบบไดนามิกที่เปลี่ยนแปลงตลอดเวลา

อีกครั้งสิ่งเหล่านี้มีความแตกต่างเป็นร้อยที่ฉันจะไม่เข้าไปที่นี่


2

คุณต้องใช้ไดนามิก sql

DECLARE @DatabaseName varchar(150) = 'dbamaint'
declare @sqltext nvarchar(max) = N''

set @sqltext = N'CREATE DATABASE '+quotename(@DatabaseName)+ ';'

print @sqltext 

-- once you are happy .. uncomment below
--exec sp_executesql @sqltext
set @sqltext = ''
set @sqltext = N'use '+quotename(@DatabaseName)+ ';'
print @sqltext 
-- once you are happy .. uncomment below
--exec sp_executesql @sqltext

ด้านล่างนี้เป็นผลลัพธ์ของการพิมพ์ .. เมื่อคุณยกเลิกการคอมเม้นท์exec sp_executesql @sqltextข้อความจะถูกดำเนินการจริง ...

CREATE DATABASE [dbamaint];
use [dbamaint];

1
ใช่ขอบคุณฉันรู้ว่า แต่ฉันต้องการที่จะรู้ว่าถ้าใครรู้ว่าทำไมคุณไม่สามารถใช้ตัวแปรได้หรือไม่
แกเร็ ธ

ตัวแยกวิเคราะห์ T-SQL จะส่งข้อผิดพลาดทางไวยากรณ์ ไม่ใช่ T-SQL ที่ถูกต้องที่ parser รู้จัก
Kin Shah

ขอบคุณ Kin ฉันแน่ใจว่าต้องมีเหตุผลที่ดี อาจเป็นเพราะชื่อฐานข้อมูลอาจมี '@' และสาเหตุที่ซับซ้อนกว่านี้
แกเร็ ธ

1
ใช่พวกเขาสามารถมี @ และฉันคิดว่ามันเป็นเหตุผลหลัก msdn.microsoft.com/en-us/library/ms175874.aspx
Paweł Tajs

1
@ gazeranco เชื่อฉันทุกคนที่ทำงานกับ SQL Server ไม่ต้องสงสัยเลยว่าคำสั่งเพิ่มเติมจะยอมรับตัวแปรแทนที่ตัวระบุคงที่
db2
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.