# SQL Server: `sp_executesql` khác gì `EXECUTE`? Khi nào nên dùng và rủi ro cần biết

Khi làm việc với SQL Server, đặc biệt là dynamic SQL, chúng ta thường gặp hai cách thực thi câu SQL động:

“`sql
EXEC(…)
“`

hoặc:

“`sql
EXECUTE(…)
“`

và:

“`sql
sp_executesql
“`

Nhìn bên ngoài, cả hai đều có thể chạy một câu SQL được tạo dưới dạng string.

Ví dụ:

“`sql
EXEC(‘SELECT * FROM Users’);
“`

và:

“`sql
EXEC sp_executesql N’SELECT * FROM Users’;
“`

đều có thể trả về cùng một kết quả.

Tuy nhiên, khi bắt đầu truyền parameter, xử lý performance và bảo mật, hai cách này có sự khác biệt rất lớn.

Điểm quan trọng nhất cần nhớ:

> `EXEC` thường thực thi trực tiếp một chuỗi SQL, trong khi `sp_executesql` hỗ trợ parameterized dynamic SQL tốt hơn.

—

# 1. EXEC và EXECUTE có khác nhau không?

Trong SQL Server:

“`sql
EXEC
“`

chỉ là dạng viết ngắn của:

“`sql
EXECUTE
“`

Ví dụ:

“`sql
EXEC GetUsers;
“`

và:

“`sql
EXECUTE GetUsers;
“`

về cơ bản giống nhau.

Do đó, khi nói:

“`text
EXEC vs sp_executesql
“`

thực chất chúng ta đang so sánh:

“`text
EXECUTE dynamic SQL
“`

với:

“`text
sp_executesql
“`

—

# 2. EXECUTE có thể dùng để gọi Stored Procedure

Ví dụ:

“`sql
EXEC GetUsers;
“`

hoặc:

“`sql
EXECUTE GetUsers;
“`

Nếu stored procedure có parameter:

“`sql
EXEC GetUserById @Id = 10;
“`

Đây là use case rất bình thường của `EXEC`.

—

# 3. EXECUTE cũng có thể chạy Dynamic SQL

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE IsActive = 1
‘;

EXEC(@sql);
“`

SQL Server sẽ lấy nội dung của:

“`sql
@sql
“`

và execute nó như một câu SQL.

Có thể hình dung:

“`text
String SQL
↓
EXEC
↓
SQL Server parses
↓
Compile
↓
Execute
“`

—

# 4. Dynamic SQL với EXEC

Giả sử chúng ta muốn tìm user theo email:

“`sql
DECLARE @Email NVARCHAR(100);

SET @Email = N’test@example.com’;

DECLARE @sql NVARCHAR(MAX);

SET @sql =
N’SELECT *
FROM Users
WHERE Email = ”’ + @Email + ””;

EXEC(@sql);
“`

Sau khi nối string, SQL thực tế có thể trở thành:

“`sql
SELECT *
FROM Users
WHERE Email = ‘test@example.com’;
“`

Cách này chạy được.

Nhưng nó bắt đầu có vấn đề khi input đến từ user.

—

# 5. Risk lớn nhất của EXEC với string concatenation: SQL Injection

Giả sử:

“`sql
SET @Email = N”’ OR 1 = 1 –‘;
“`

Sau khi concatenate:

“`sql
SELECT *
FROM Users
WHERE Email = ” OR 1 = 1 –‘
“`

Condition:

“`sql
1 = 1
“`

luôn đúng.

Kết quả có thể trả về toàn bộ user.

Đây là SQL Injection.

Một ví dụ nguy hiểm hơn có thể là input cố tình thêm các SQL statement khác.

Vì vậy:

> Không nên concatenate dữ liệu từ client trực tiếp vào dynamic SQL.

—

# 6. sp_executesql là gì?

`sp_executesql` là một system stored procedure của SQL Server được thiết kế để execute dynamic SQL, đặc biệt có hỗ trợ parameter.

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE Email = @Email
‘;

EXEC sp_executesql
@sql,
N’@Email NVARCHAR(100)’,
@Email = N’test@example.com’;
“`

Ở đây:

“`sql
@Email
“`

không được nối trực tiếp vào SQL string.

SQL statement giữ nguyên:

“`sql
SELECT *
FROM Users
WHERE Email = @Email
“`

và value được truyền riêng.

Đây là điểm cực kỳ quan trọng.

—

# 7. EXEC và sp_executesql khác nhau như thế nào?

Có thể tóm tắt:

| Đặc điểm | EXEC / EXECUTE | sp_executesql |
|—|—|—|
| Execute dynamic SQL | Có | Có |
| Parameterization | Không tốt khi dùng string trực tiếp | Hỗ trợ tốt |
| SQL Injection protection | Dễ lỗi nếu concatenate | Tốt hơn khi dùng parameter đúng cách |
| Execution plan reuse | Kém hơn trong nhiều trường hợp | Tốt hơn |
| Output parameter | Khó hơn | Hỗ trợ |
| Code readability | Đơn giản | Rõ ràng hơn với dynamic SQL phức tạp |
| Recommended dynamic SQL | Hạn chế | Thường nên dùng |

—

# 8. Ví dụ EXEC với dynamic value

Ví dụ:

“`sql
DECLARE @Id INT = 100;

DECLARE @sql NVARCHAR(MAX);

SET @sql =
N’SELECT *
FROM Users
WHERE Id = ‘ + CAST(@Id AS NVARCHAR(20));

EXEC(@sql);
“`

SQL cuối cùng:

“`sql
SELECT *
FROM Users
WHERE Id = 100;
“`

Nếu lần sau:

“`sql
@Id = 200
“`

string trở thành:

“`sql
SELECT *
FROM Users
WHERE Id = 200;
“`

Hai câu SQL khác nhau về mặt text.

SQL Server có thể phải compile các execution plan riêng tùy trường hợp.

—

# 9. sp_executesql dùng parameter

Cùng bài toán:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE Id = @Id
‘;

EXEC sp_executesql
@sql,
N’@Id INT’,
@Id = 100;
“`

Lần sau:

“`sql
EXEC sp_executesql
@sql,
N’@Id INT’,
@Id = 200;
“`

SQL statement vẫn là:

“`sql
SELECT *
FROM Users
WHERE Id = @Id
“`

chỉ có parameter value thay đổi.

Điều này tạo điều kiện tốt hơn cho SQL Server reuse execution plan.

—

# 10. Execution Plan Reuse

Đây là một trong những khác biệt quan trọng nhất.

Với EXEC + concatenation:

“`sql
SELECT *
FROM Users
WHERE Id = 1;
“`

lần khác:

“`sql
SELECT *
FROM Users
WHERE Id = 2;
“`

rồi:

“`sql
SELECT *
FROM Users
WHERE Id = 3;
“`

SQL text thay đổi liên tục.

Về mặt concept:

“`text
SQL 1
↓
compile

SQL 2
↓
compile

SQL 3
↓
compile
“`

Có thể tạo nhiều execution plans khác nhau.

Trong khi `sp_executesql`:

“`sql
SELECT *
FROM Users
WHERE Id = @Id;
“`

statement giữ nguyên.

Chỉ có:

“`text
@Id = 1
@Id = 2
@Id = 3
“`

thay đổi.

SQL Server có nhiều cơ hội reuse execution plan hơn.

—

# 11. Tại sao plan reuse quan trọng?

Compile query cũng tốn resource.

SQL Server phải thực hiện:

“`text
Parse
↓
Bind
↓
Optimize
↓
Generate execution plan
↓
Execute
“`

Nếu mỗi request đều tạo SQL text khác nhau:

“`text
CPU cost ↑
Plan cache usage ↑
Compilation ↑
“`

Trong hệ thống nhiều request, điều này có thể ảnh hưởng performance.

—

# 12. sp_executesql không tự động chống SQL Injection

Đây là điểm rất quan trọng.

Nhiều developer nghĩ:

> Chỉ cần dùng sp_executesql là an toàn.

Không đúng.

Ví dụ:

“`sql
DECLARE @Name NVARCHAR(100);

SET @Name = N”’ OR 1=1 –‘;

DECLARE @sql NVARCHAR(MAX);

SET @sql =
N’SELECT *
FROM Users
WHERE Name = ”’ + @Name + ””;

EXEC sp_executesql @sql;
“`

Vẫn bị SQL Injection.

Vì chúng ta vẫn concatenate input vào SQL string.

Điều bảo vệ chúng ta là:

> Parameterization.

Không phải tên `sp_executesql`.

—

# 13. Cách đúng

Nên:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE Name = @Name
‘;

EXEC sp_executesql
@sql,
N’@Name NVARCHAR(100)’,
@Name = @Name;
“`

Ở đây value không trở thành SQL code.

Nó chỉ là data.

—

# 14. Parameter không dùng được cho table name hoặc column name

Đây là một điểm cực kỳ hay gặp.

Bạn không thể làm:

“`sql
DECLARE @TableName NVARCHAR(100) = N’Users’;

EXEC sp_executesql
N’SELECT * FROM @TableName’,
N’@TableName NVARCHAR(100)’,
@TableName;
“`

SQL Server không hiểu `@TableName` là table identifier.

Parameter chỉ dùng cho values.

Ví dụ:

“`sql
WHERE Id = @Id
“`

thì được.

Nhưng:

“`sql
FROM @TableName
“`

thì không.

—

# 15. Dynamic table name phải làm sao?

Nếu thực sự phải dynamic table:

“`sql
DECLARE @TableName SYSNAME = N’Users’;

DECLARE @sql NVARCHAR(MAX);

SET @sql =
N’SELECT *
FROM ‘ + QUOTENAME(@TableName);

EXEC sp_executesql @sql;
“`

`QUOTENAME()` giúp xử lý identifier an toàn hơn.

Ví dụ:

“`sql
QUOTENAME(‘Users’)
“`

trả:

“`text
[Users]
“`

Tuy nhiên, tốt nhất vẫn nên whitelist table name.

Ví dụ:

“`sql
IF @TableName NOT IN (‘Users’, ‘Orders’, ‘Products’)
BEGIN
THROW 50000, ‘Invalid table’, 1;
END;
“`

—

# 16. Dynamic ORDER BY cũng cần cẩn thận

Ví dụ user gửi:

“`text
sortBy = CreatedAt
“`

Không thể parameterize:

“`sql
ORDER BY @SortColumn
“`

theo cách mong muốn.

Bạn thường phải build identifier động.

Ví dụ:

“`sql
DECLARE @SortColumn SYSNAME = N’CreatedAt’;

IF @SortColumn NOT IN
(
N’CreatedAt’,
N’Name’,
N’Email’
)
BEGIN
SET @SortColumn = N’CreatedAt’;
END;

DECLARE @sql NVARCHAR(MAX);

SET @sql =
N’
SELECT *
FROM Users
ORDER BY ‘ + QUOTENAME(@SortColumn);

EXEC sp_executesql @sql;
“`

Rule:

> Values dùng parameter. Identifiers dùng whitelist + QUOTENAME.

—

# 17. sp_executesql với nhiều parameters

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE Status = @Status
AND CreatedAt >= @FromDate
‘;

EXEC sp_executesql
@sql,
N’
@Status NVARCHAR(20),
@FromDate DATETIME2
‘,
@Status = N’active’,
@FromDate = ‘2026-01-01′;
“`

Câu SQL vẫn rõ ràng và dễ maintain.

—

# 18. Output parameter với sp_executesql

`sp_executesql` cũng hỗ trợ output parameter.

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

DECLARE @Count INT;

SET @sql = N’
SELECT @Total = COUNT(*)
FROM Users
WHERE IsActive = 1
‘;

EXEC sp_executesql
@sql,
N’@Total INT OUTPUT’,
@Total = @Count OUTPUT;

SELECT @Count;
“`

Đây là điểm rất hữu ích khi dynamic SQL cần trả scalar value ra ngoài.

—

# 19. Scope của dynamic SQL

Một vấn đề hay gặp là variable scope.

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
DECLARE @Value INT = 10;

SELECT @Value;
‘;

EXEC sp_executesql @sql;
“`

`@Value` chỉ tồn tại bên trong dynamic SQL.

Sau khi execute:

“`sql
SELECT @Value;
“`

ở ngoài sẽ không chạy vì variable không tồn tại trong outer scope.

Nếu cần trả value ra ngoài, dùng output parameter.

—

# 20. Temporary table thì sao?

Temporary table có một nuance khác.

Ví dụ outer scope tạo:

“`sql
CREATE TABLE #TempUsers
(
Id INT
);
“`

Sau đó dynamic SQL:

“`sql
EXEC sp_executesql N’
INSERT INTO #TempUsers(Id)
VALUES (1);
‘;
“`

Sau đó:

“`sql
SELECT *
FROM #TempUsers;
“`

thường có thể truy cập được vì local temp table được tạo ở outer scope.

Nhưng nếu tạo temp table bên trong dynamic SQL:

“`sql
EXEC sp_executesql N’
CREATE TABLE #TempUsers
(
Id INT
);
‘;
“`

sau khi dynamic batch kết thúc, outer scope thường không thể dùng table đó theo cách bạn mong muốn.

Do đó cần hiểu rõ scope.

—

# 21. EXEC stored procedure khác EXEC(dynamic SQL)

Hai syntax này nhìn giống nhưng ý nghĩa khác nhau.

Stored procedure:

“`sql
EXEC GetUsers;
“`

Dynamic SQL:

“`sql
EXEC(‘SELECT * FROM Users’);
“`

Một cái execute object đã tồn tại trong database.

Một cái execute một SQL string.

Không nên nhầm hai use case này.

—

# 22. Khi nào nên dùng EXEC?

`EXEC` hoàn toàn phù hợp khi gọi stored procedure.

Ví dụ:

“`sql
EXEC dbo.GetUserById @Id = 10;
“`

Không có vấn đề gì.

Hoặc stored procedure output:

“`sql
DECLARE @Result INT;

EXEC dbo.CalculateSomething
@Id = 10,
@Result = @Result OUTPUT;
“`

`EXEC` không phải là thứ xấu.

Vấn đề chủ yếu xuất hiện khi dùng:

“`sql
EXEC(@sql)
“`

với string concatenation không kiểm soát.

—

# 23. Khi nào nên dùng sp_executesql?

`sp_executesql` thường phù hợp khi:

– Cần dynamic WHERE.
– Có parameter value.
– Dynamic filtering.
– Dynamic search.
– Dynamic report.
– Dynamic pagination.
– Dynamic SQL nhưng muốn giữ parameterization.
– Muốn reuse execution plan tốt hơn.
– Muốn giảm SQL Injection risk.
– Cần output parameter.

—

# 24. Ví dụ dynamic filter thực tế

Giả sử API có filter:

“`text
Status
FromDate
ToDate
“`

Có thể build query:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT
Id,
Name,
Status,
CreatedAt
FROM Users
WHERE 1 = 1
‘;

IF @Status IS NOT NULL
BEGIN
SET @sql += N’
AND Status = @Status
‘;
END;

IF @FromDate IS NOT NULL
BEGIN
SET @sql += N’
AND CreatedAt >= @FromDate
‘;
END;

EXEC sp_executesql
@sql,
N’
@Status NVARCHAR(20),
@FromDate DATETIME2
‘,
@Status = @Status,
@FromDate = @FromDate;
“`

Dynamic ở đây là structure của query.

Nhưng values vẫn parameterized.

Đây là cách sử dụng tốt.

—

# 25. EXEC với string concatenate dễ debug nhưng có risk

Một lý do developer thích:

“`sql
PRINT @sql;
EXEC(@sql);
“`

là dễ nhìn SQL cuối cùng.

Ví dụ:

“`sql
SELECT *
FROM Users
WHERE Id = 123;
“`

Nhưng điều này không có nghĩa concatenate là cách tốt.

Với `sp_executesql`, vẫn có thể log:

“`text
SQL statement

+

parameters
“`

thay vì build một câu SQL chứa toàn bộ value.

—

# 26. Parameter Sniffing

Khi nói về `sp_executesql`, một vấn đề nâng cao cần biết là:

> Parameter Sniffing.

Ví dụ:

“`sql
SELECT *
FROM Orders
WHERE CustomerId = @CustomerId;
“`

Giả sử:

“`text
Customer A = 5 orders

Customer B = 5,000,000 orders
“`

Execution plan tối ưu cho Customer A có thể không phù hợp với Customer B.

SQL Server có thể compile plan dựa trên parameter value đầu tiên rồi reuse plan đó.

Đây là một trade-off của plan reuse.

Do đó:

> Plan reuse thường tốt, nhưng không phải lúc nào reuse cũng tạo ra execution plan tối ưu cho mọi parameter.

Các solution có thể bao gồm:

“`text
OPTION (RECOMPILE)

OPTIMIZE FOR

Query Store / plan analysis

Rewrite query

Statistics/index optimization
“`

Nhưng không nên dùng `RECOMPILE` một cách máy móc.

—

# 27. Một misconception phổ biến

Có người nói:

> sp_executesql luôn nhanh hơn EXEC.

Không hoàn toàn chính xác.

Nếu query chỉ execute một lần:

“`sql
EXEC(N’SELECT COUNT(*) FROM Users’);
“`

và:

“`sql
EXEC sp_executesql
N’SELECT COUNT(*) FROM Users’;
“`

performance gần như có thể không khác đáng kể.

Lợi thế lớn của `sp_executesql` xuất hiện khi:

“`text
Repeated queries
+
Different parameter values
+
Parameterized statements
“`

—

# 28. Không nên dynamic SQL nếu không cần

Ví dụ:

“`sql
DECLARE @sql NVARCHAR(MAX);

SET @sql = N’
SELECT *
FROM Users
WHERE Id = @Id
‘;

EXEC sp_executesql
@sql,
N’@Id INT’,
@Id = @Id;
“`

Nếu query hoàn toàn cố định thì không cần dynamic SQL.

Có thể đơn giản:

“`sql
SELECT *
FROM Users
WHERE Id = @Id;
“`

Rule:

> Chỉ dùng dynamic SQL khi structure của query thực sự cần dynamic.

—

# 29. Dynamic SQL thường xuất hiện ở đâu?

Các trường hợp phổ biến:

### Dynamic filtering

“`text
Search screen
“`

có 10 filter optional.

### Dynamic sorting

“`text
sortBy=name
sortBy=createdAt
“`

### Reporting

Column hoặc grouping thay đổi.

### Multi-tenant

Table/schema thay đổi tùy tenant trong một số architecture.

### Database administration

Generate DDL hoặc maintenance command.

—

# 30. EXEC trong C# tương đương gì?

Khi dùng raw SQL trong C#:

“`csharp
var sql =
“SELECT * FROM Users WHERE Id = ” + id;
“`

đây có cùng vấn đề với concatenate dynamic SQL trong database.

Không nên.

Nên parameterize.

Ví dụ với ADO.NET:

“`csharp
using var command = connection.CreateCommand();

command.CommandText =
“SELECT * FROM Users WHERE Id = @Id”;

command.Parameters.Add(
new SqlParameter(“@Id”, id));
“`

Ý tưởng giống với:

“`sql
sp_executesql
“`

—

# 31. EF Core cũng parameterize query

Ví dụ LINQ:

“`csharp
var user = await dbContext.Users
.Where(x => x.Id == id)
.FirstOrDefaultAsync();
“`

EF Core thường generate SQL dạng:

“`sql
SELECT …
FROM Users
WHERE Id = @__id_0;
“`

Tức là parameterized query.

Đây là một trong các lý do không nên chuyển sang raw SQL chỉ vì nghĩ raw SQL luôn nhanh hơn.

—

# 32. Raw SQL trong EF Core cũng cần parameter

Không nên:

“`csharp
var sql =
$”SELECT * FROM Users WHERE Email = ‘{email}'”;
“`

rồi execute raw SQL trực tiếp.

Nên dùng API hỗ trợ parameterization.

Rule vẫn giống SQL Server:

“`text
Code
+
User input
+
String concatenation
=
Danger
“`

—

# 33. So sánh bằng ví dụ trực quan

## EXEC

“`sql
DECLARE @Id INT = 100;

EXEC(
‘SELECT *
FROM Users
WHERE Id = ‘ + @Id
);
“`

Có thể hình dung:

“`text
@Id
↓
convert to text
↓
concatenate
↓
new SQL text
↓
execute
“`

## sp_executesql

“`sql
EXEC sp_executesql
N’
SELECT *
FROM Users
WHERE Id = @Id
‘,
N’@Id INT’,
@Id = 100;
“`

Flow:

“`text
SQL template
+
Parameter metadata
+
Parameter value
↓
SQL Server
“`

Query structure và data được tách riêng.

Đây là cách tiếp cận tốt hơn.

—

# 34. Best Practice

Nếu cần dynamic SQL:

## Không nên

“`sql
SET @sql =
‘SELECT *
FROM Users
WHERE Email = ”’
+ @Email +
””;

EXEC(@sql);
“`

## Nên

“`sql
SET @sql =
N’
SELECT *
FROM Users
WHERE Email = @Email
‘;

EXEC sp_executesql
@sql,
N’@Email NVARCHAR(100)’,
@Email = @Email;
“`

—

# 35. Checklist khi viết Dynamic SQL

Trước khi đưa dynamic SQL lên production, hãy kiểm tra:

“`text
[ ] Dynamic SQL có thực sự cần thiết không?

[ ] User input có bị concatenate vào SQL không?

[ ] Values có parameterized không?

[ ] Table/column names có whitelist không?

[ ] Identifier có dùng QUOTENAME không?

[ ] Parameter type có đúng với column type không?

[ ] Query có reuse execution plan được không?

[ ] Có vấn đề parameter sniffing không?

[ ] Index phù hợp chưa?

[ ] Execution Plan đã được kiểm tra chưa?

[ ] Có log query + parameter phù hợp để debug không?
“`

—

# 36. Câu hỏi phỏng vấn: EXEC khác sp_executesql như thế nào?

Interviewer hỏi:

> What is the difference between EXEC and sp_executesql?

Có thể trả lời:

> Both can execute dynamic SQL, but sp_executesql supports parameterized statements. This makes it safer against SQL injection when used correctly and also allows SQL Server to reuse execution plans more effectively when the same query runs with different parameter values. EXEC with string concatenation usually generates different SQL text for different values and is easier to misuse.

—

# 37. Câu hỏi phỏng vấn: sp_executesql có chống SQL Injection không?

Một câu trả lời tốt:

> Not by itself. sp_executesql is safe when user values are passed as parameters. If I still concatenate user input into the SQL statement, it can still be vulnerable to SQL injection.

Đây là một điểm interviewer rất thích hỏi.

—

# 38. Câu hỏi phỏng vấn: Tại sao không parameterize table name?

Có thể trả lời:

> SQL parameters represent values, not SQL identifiers. Table names, column names and keywords are part of the SQL statement structure, so they cannot normally be passed as regular parameters. For dynamic identifiers, I whitelist allowed values and use QUOTENAME.

—

# 39. Câu hỏi phỏng vấn: sp_executesql có luôn nhanh hơn EXEC không?

Không.

Câu trả lời tốt:

> Not necessarily. For a one-time query, performance may be similar. The main performance benefit of sp_executesql comes from parameterization and better execution plan reuse when the same statement runs repeatedly with different values.

—

# 40. Cách nhớ nhanh

Có thể nhớ như sau:

“`text
EXEC
↓
execute command / SQL string
“`

Còn:

“`text
sp_executesql
↓
execute dynamic SQL
+
parameters
+
better plan reuse
“`

Nếu thấy:

“`sql
‘…’ + @UserInput + ‘…’
“`

hãy nghĩ ngay:

“`text
SQL Injection risk
“`

Nếu thấy:

“`sql
WHERE Id = @Id
“`

và `@Id` được truyền bằng parameter:

“`text
Good
“`

—

# Kết luận

`EXEC` và `sp_executesql` đều có thể execute SQL trong SQL Server, nhưng chúng phù hợp với các use case khác nhau.

`EXEC` rất phù hợp để gọi stored procedure:

“`sql
EXEC dbo.GetUserById @Id = 10;
“`

Với dynamic SQL đơn giản, `EXEC(@sql)` cũng chạy được.

Tuy nhiên, nếu dynamic SQL có dữ liệu đầu vào, đặc biệt là input từ client, nên ưu tiên:

“`sql
sp_executesql
“`

với parameterization.

Điểm quan trọng nhất cần nhớ:

“`text
EXEC + String Concatenation
↓
SQL Injection Risk
+
Poorer Plan Reuse
“`

Trong khi:

“`text
sp_executesql
+
Parameters
↓
Safer Dynamic SQL
+
Better Plan Reuse
“`

Nhưng:

> `sp_executesql` không tự động làm query an toàn. Chính việc parameterize dữ liệu mới giúp bảo vệ query khỏi SQL Injection.

Một rule thực tế có thể ghi nhớ:

> Với value, hãy parameterize. Với table hoặc column name, hãy whitelist và dùng `QUOTENAME`. Và nếu query không cần dynamic thì đừng dùng dynamic SQL.