T-SQL 支持插值字符串吗?
让我们举个例子:
SET @query = 'SELECT ' + @somevariable + ' FROM SOME_TABLE'
我希望能够做这样的事情:
SET @query = 'SELECT {@somevariable} FROM SOME_TABLE'
感谢您的回答!
感谢@j.f.sebastian 指出了这些解决方案。遗憾的是 xp_sprintf 仅限于 254 个字符,因此在使用长查询时这并不理想。相反,FORMATMESSAGE 限制为 2047 个字符,因此足以运行长查询。
我将在一篇文章中总结解决方案的所有内容,以便使事情井井有条。
使用 FORMATMESSAGE 重要的是要知道,使用插值字符串作为第一个参数,它仅支持 SQL 版本 2012 及更高版本,所以我将用 FORMATMESSAGE 发布 2 个答案:
SQL 版本 >= 2012:
SET @query = FORMATMESSAGE('SELECT %s FROM SOME_TABLE', @somevariable);
SQL 版本 < 2012:
EXEC sp_addmessage 50001, 16, 'SELECT %s FROM SOME_TABLE', NULL, NULL, 'replace'
SET @query = FORMATMESSAGE(50001, @somevariable)
使用 xp_sprintf 存储过程需要注意的是,它仅限于 254 个字符,因此对于长查询来说这不是一个好主意。
DECLARE @query AS VARCHAR(100)
,@somevariable as VARCHAR(10) = '[id]'
EXEC xp_sprintf @query OUTPUT, 'SELECT %s FROM SOME_TABLE', @somevariable
为了提高可读性,当你的字符串很长时,我喜欢这样做:
SET @query = replace( replace( replace(
'SELECT {@variable1} FROM {@variable2} WHERE {@variable3}'
, '{@variable1}', @variable1 )
, '{@variable2}', @variable2 )
, '{@variable3}', @variable3 )
它的优点是 SQL 字符串的可读性,而且您可以多次使用同一个变量。显然这不是正确的插值,但我比
FORMATMESSAGE()
选项更喜欢它,因为
这不是 T-SQL 中常见的处理方式,但可以使用 xp_sprintf
DECLARE @query AS VARCHAR(100)
,@somevariable as VARCHAR(10) = '[id]'
EXEC xp_sprintf @query OUTPUT, 'SELECT %s FROM SOME_TABLE', @somevariable
PRINT @query
我喜欢为动态 SQL 执行此操作,因此我为 SQL Server 2017+ 编写了一个函数(使用 STRING_AGG 和 JSON_VALUE)。它可以被重写以使其与旧版本兼容。
以下是链接失效时的文本:
CREATE OR ALTER FUNCTION [Tools].[StringInterpolation]
(@Template VARCHAR(MAX)
, @JSON_Row NVARCHAR(MAX))
/*
This function replaces a string template with actual values from a JSON-formatted row
The table returns a single column: FormattedString
** Requires SQL Server 2017+ for STRING_AGG (could be rewritten using XML PATH)
** Requires SQL Server 2016+ for JSON_VALUE (maybe you could use XML)
EXAMPLE:
SELECT *
FROM (SELECT [Name] = 'Steven', Adjective = 'internet person', Verb = 'writes helpful(?) SQL functions') [d]
CROSS APPLY Tools.StringInterpolation ('{Name} is a {Adjective} who {Verb}.', (SELECT [d].* FOR JSON PATH))
Name | Adjective | Verb | FormattedString
-------+------------------------+---------------------------------+-----------------------------------------------------------------
Steven | internet person | writes helpful(?) SQL functions | Steven is a internet person who writes helpful(?) SQL functions.
*/
RETURNS TABLE
RETURN
WITH [CTE_10]
AS (SELECT [Number]
FROM(VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) ) [v]([Number])),
-------------------
/* 100 rows (all 1s) */
[CTE_100]
AS (SELECT [Number] = 1
FROM [CTE_10] [a]
CROSS JOIN [CTE_10] [b]),
-------------------
/* 1,000,000 rows max (all 1s) */
[CTE_1000000]
AS (SELECT [Number] = 1
FROM [CTE_100] [a]
CROSS JOIN [CTE_100] [b]
CROSS JOIN [CTE_100] [c]),
-------------------
/* Numbers "Table" CTE: 1) TOP has variable parameter = DATALENGTH(@Template), 2) Use ROW_NUMBER */
[CTE_Numbers]
AS (SELECT TOP (ISNULL(DATALENGTH(@Template), 0))
[Number] = ROW_NUMBER() OVER(ORDER BY (SELECT NULL) )
FROM [CTE_1000000]),
-------------------
/* This is tricky. Get each start of each variable or non-variable
Variables look like {...}
Non-variables look like }...{ (i.e. the bits between the variables) */
[CTE_Start]
AS (SELECT [Type] = 'Text'
, [Start] = 1
UNION ALL
SELECT [Type] = IIF([Char] = '{', 'Variable', 'Text')
, [Start] = [Number] + 1 -- start *after* the { or }
FROM [CTE_Numbers]
CROSS APPLY (SELECT [Char] = SUBSTRING(@Template, [Number], 1)) [c]
WHERE [Char] IN ( '{', '}' ) ),
-------------------
/* Pair each "start" with the next to find indicies of each substring */
[CTE_StringIndicies]
AS (SELECT [Type]
, [Start]
, [End] = ISNULL(LEAD([Start]) OVER(
ORDER BY [Start]) - 1, DATALENGTH(@Template) + 1)
FROM [CTE_Start]),
-------------------
/* Get each substring */
[CTE_Variables]
AS (SELECT [Start]
, [Type]
, [SubString] = SUBSTRING(@Template, [Start], [End] - [Start])
FROM [CTE_StringIndicies]),
-------------------
/* If it's a variable, replace it with the actual value from @JSON_Row
Otherwise, just return the original substring */
[CTE_Replacements]
AS (SELECT [Start]
, [Substring] = IIF([Type] = 'Variable', JSON_VALUE(@JSON_Row, '$[0].' + [Substring]), [Substring])
FROM [CTE_Variables])
-------------------
/* Glue it all back together */
SELECT [FormattedString] = STRING_AGG([Substring], '') WITHIN GROUP (ORDER BY [Start])
FROM [CTE_Replacements];
编写了 2 个方便的基于 Json 的插值函数。一个使用字典样式的 Json(键、值对),另一个使用包含每个替换的属性的 Json 对象。
1)数据作为Json对象
例如,给定模板:
'Hey, {name} is {age}'
,运行:
SELECT [res4] = [ccgen].[fn_string_interpolation_object]('Hi, {name} is {age}' ,'{"name":"Alice", "age":24}')
...返回
Hey, Alice is 24
CREATE OR ALTER FUNCTION [CcGen].[fn_string_interpolation_object](
@template NVARCHAR(4000), @data_json NVARCHAR(4000)) RETURNS NVARCHAR(4000) AS
/*
=============================================
C# or Java like string interpolation brought to TSQL.
example - copy to run proc
-----------------------
--property names must match those in template. Same as a dynamic object in C#
SELECT [res4] = [ccgen].[fn_string_interpolation_object]('Hi, {name} is {age}' ,'{"name":"Alice", "age":24}')
-- returns
Hi, Alic is 24
=============================================
*/
BEGIN
SELECT @template = REPLACE(@template ,'{' + [key] + '}' ,[value]) FROM OPENJSON(@data_json);
RETURN @template;
END;
2)数据为Json字典 例如,给定模板:
'Hey, {name} is {age}'
,运行:
SELECT [res2] = [ccgen].[fn_string_interpolation]('Hey, {name} is {age}','{"items":[{"key":"name", "value":"Alice"},{"key":"age", "value":"24"}]}')
...返回
Hey, Alice is 24
CREATE OR ALTER FUNCTION [CcGen].[fn_string_interpolation](
@template NVARCHAR(4000),
@key_value_json NVARCHAR(4000)) RETURNS NVARCHAR(4000) AS
/*
=============================================
C# or Java like string interpolation brought to TSQL.
example - copy to run proc
-----------------------
DECLARE @json NVARCHAR(2048) = N'{
"items": [
{
"key": "A",
"value": "a1"
},
{
"key": "B",
"value": "b2"
},
{
"key": "C",
"value": "c3"
}
]
}';
DECLARE @template NVARCHAR(4000) = 'message:= A:{A}, B:{B}, C:{C}'
select res = ccgen.fn_string_interpolation(@template, @json)
-- returns
formatted 3 = A:a1, B:b2, C:c3
=============================================
*/
BEGIN
SELECT @template = REPLACE(@template ,'{' + [key] + '}' ,[value]) FROM OPENJSON(@key_value_json ,'$.items') WITH ( [key] VARCHAR(200) '$.key', [value] VARCHAR(4000) '$.value' );
RETURN @template;
END;
提示:Json 需要多输入一些内容。修改代码并将属性名称缩短为“k”(键)、“v”(值)和“d”(项)以使其变小。然后调用就更简洁了:
SELECT [res2] = [ccgen].[fn_string_interpolation]('Hey, {name} is {age}','{"d":[{"k":"name", "v":"Alice"},{"k":"age", "v":"24"}]}')
备注:
当然,这仅适用于支持 Json 的 MSSQL 版本。而且也不支持转义。例如,不可能将 {a} 同时作为文字参数和替换参数。这可以使用转义来添加,但不能保证这一努力,因为我没有使用该功能。
要添加另一个选项,我更喜欢伪插值字符串方法。 T-SQL 实际上并不支持字符串插值,但我发现这种方式更容易跟踪事情。它基本上是一个奇特的
REPLACE()
,不需要一些不同数量的嵌套 REPLACE(REPLACE(REPLACE(...)
调用。
DECLARE @message VARCHAR(MAX) = 'Some {item} with {count} {collection}.';
SELECT @message = REPLACE(@message, SearchText, ReplaceText)
FROM ( VALUES
('{item}', 'text string'),
('{count}', '3'),
('{collection}', 'variables')
) _ (SearchText, ReplaceText);
您可以用字符串文字、其他变量等替换占位符,并且处理许多替换确实很容易,而无需跟踪所有嵌套。您可以使用任何您想要的占位符,但我只是借用了其他语言的样式,因为它从文本的其余部分中脱颖而出。