简体   繁体   English

SQL 内插字符串

[英]SQL Interpolated Strings

Does T-SQL support Interpolated String? T-SQL 是否支持内插字符串?

Let's put up this example:让我们举这个例子:

SET @query = 'SELECT ' + @somevariable + ' FROM SOME_TABLE'

I want to be able to do something like that:我希望能够做这样的事情:

SET @query = 'SELECT {@somevariable} FROM SOME_TABLE'

Thanks for the answers!感谢您的回答!

Giving the credits to @jfsebastian for pointing out these solutions.感谢@jfsebastian 指出这些解决方案。 Sadly xp_sprintf is limited to 254 characters, so that wouldn't be ideal when using long queries.遗憾的是 xp_sprintf 被限制为 254 个字符,因此在使用长查询时这并不理想。 FORMATMESSAGE instead is limited to 2047 characters, so that's good enough in order to run long queries. FORMATMESSAGE 被限制为 2047 个字符,所以这足以运行长查询。

I'll summarize everything in one post for the solutions in order to keep things organized.我将在一篇文章中总结解决方案的所有内容,以使事情井井有条。

Answer 1:答案 1:

Using FORMATMESSAGE it's important to know, that using interpolated string as first the parameter, Its supported only SQL versions 2012 and above, so I'll post 2 answers with FORMATMESSAGE:使用FORMATMESSAGE重要的是要知道,使用内插字符串作为第一个参数,它仅支持 SQL 版本 2012 及更高版本,因此我将使用 FORMATMESSAGE 发布 2 个答案:

SQL Version >= 2012: SQL 版本 >= 2012:

SET @query = FORMATMESSAGE('SELECT %s FROM SOME_TABLE', @somevariable);


SQL Version < 2012: SQL 版本 < 2012:

EXEC sp_addmessage 50001, 16, 'SELECT %s FROM SOME_TABLE', NULL, NULL, 'replace'
SET @query = FORMATMESSAGE(50001, @somevariable)


Answer 2:答案 2:

Using xp_sprintf stored procedure is important to note that It's limited to 254 characters, so it won't be a good idea for long queries.使用xp_sprintf存储过程很重要,要注意它限制为 254 个字符,因此对于长查询来说不是一个好主意。

DECLARE  @query AS VARCHAR(100)
        ,@somevariable as VARCHAR(10) = '[id]'
EXEC xp_sprintf @query OUTPUT, 'SELECT %s FROM SOME_TABLE', @somevariable

It's not a common way of doing things in T-SQL but it is possible with xp_sprintf这不是 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

For readability when your strings are long I like doing this:当你的字符串很长时,为了可读性,我喜欢这样做:

SET @query = replace( replace( replace( 
               'SELECT {@variable1} FROM {@variable2} WHERE {@variable3}'
               , '{@variable1}', @variable1 )
               , '{@variable2}', @variable2 )
               , '{@variable3}', @variable3 )

It has the advantage of readability of the SQL string plus you can use the same variable multiple times.它具有 SQL 字符串可读性的优点,而且您可以多次使用同一个变量。 Clearly it's not proper interpolation but I like it better than the FORMATMESSAGE() option since显然这不是正确的插值,但我比FORMATMESSAGE()选项更喜欢它,因为

  • It doesn't have a length limitation.它没有长度限制。 Who writes queries shorter than 2047 characters anyway?!谁写的查询短于 2047 个字符呢?! ;) ;)
  • It supports named placeholders instead of just ordered ones它支持命名占位符,而不仅仅是有序的
  • Supported in any version of SQL Server在任何版本的 SQL Server 中都受支持

Another solution I like is inspired by Stefan Hoffman via this article .我喜欢的另一个解决方案是通过这篇文章受到Stefan Hoffman 的启发。

I think this approach is a huge win for readability, and cleanly separates what the query does from how the query is compiled.我认为这种方法在可读性方面是一个巨大的胜利,并且将查询的作用与查询的编译方式完全分开。

The easier it is to read a query, the easier it is to work on it.阅读查询越容易,处理它就越容易。

/* (1/5) Define a query template */
DECLARE @stmt_with_placeholders NVARCHAR(MAX) = '
  USE {database}; 
  SELECT * FROM {schema}.{table};
';

/* (2/5) Copy the query template to a new variable */
DECLARE @stmt_processed NVARCHAR(MAX) = @stmt_with_placeholders;

/* (3/5) Build a "view model" */
DECLARE @placeholders TABLE (placeholder NVARCHAR(20) ,replacement_text NVARCHAR(200))
INSERT INTO @placeholders VALUES
  ('{database}', 'WideWorldImporters'),
  ('{schema}', 'Warehouse'),
  ('{table}', 'PackageTypes');

/* (4/5) Compile the query from the "view model" */
SELECT 
  @stmt_processed = REPLACE(
    @stmt_processed, 
    placeholders.placeholder,
    placeholders.replacement_text
  )
FROM @placeholders as placeholders;

/* (5/5) Execute the compiled query */
EXEC (@stmt_processed);

I like doing this for my dynamic SQL, so I wrote a function for SQL Server 2017+ (uses STRING_AGG and JSON_VALUE).我喜欢为我的动态 SQL 这样做,所以我为 SQL Server 2017+ 编写了一个函数(使用 STRING_AGG 和 JSON_VALUE)。 It could be rewritten to make it compatible with older versions.可以重写它以使其与旧版本兼容。

Here's the text in case that link dies:这是链接失效时的文本:

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

Wrote 2 handy Json based interpolation functions.编写了 2 个方便的基于Json的插值函数。 One makes use of a dictionary style Json (key, value pairs), the other a Json object containing a property for each substitution.一个使用字典样式的 Json(键,值对),另一个使用包含每个替换属性的 Json 对象。

1) Data as Json object 1) 数据作为 Json 对象

For example, given the template: 'Hey, {name} is {age}' , run:例如,给定模板: 'Hey, {name} is {age}' ,运行:

SELECT [res4] = [ccgen].[fn_string_interpolation_object]('Hi, {name} is {age}' ,'{"name":"Alice", "age":24}')

... which returns Hey, Alice is 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) Data as Json dictionary For example, given the template: 'Hey, {name} is {age}' , run: 2) Data as Json dictionary例如,给定模板: 'Hey, {name} is {age}' ,运行:

SELECT [res2] = [ccgen].[fn_string_interpolation]('Hey, {name} is {age}','{"items":[{"key":"name", "value":"Alice"},{"key":"age", "value":"24"}]}')

... which returns Hey, Alice is 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;

Tip: The Json takes a little more typing.提示:Json 需要更多的输入。 Modify the code and shorten property names to "k" (key) ,"v" (value) and "d" (items) to make it tiny.修改代码并将属性名称缩短为 "k" (key) 、"v" (value) 和 "d" (items) 以使其变小。 Then calling is neater:然后调用更整洁:

SELECT [res2] = [ccgen].[fn_string_interpolation]('Hey, {name} is {age}','{"d":[{"k":"name", "v":"Alice"},{"k":"age", "v":"24"}]}')
 

Notes:注意事项:

Naturally this will only work on MSSQL versions that support Json.当然,这仅适用于支持 Json 的 MSSQL 版本。 Also there is no support for escaping.也不支持转义。 For example it's not possible to have {a} as both a literal and a substitution param.例如,不可能将 {a} 同时用作文字和替换参数。 This could be added using escaping but can't warrant the effort since I have no use for the feature.这可以使用转义添加,但不能保证努力,因为我没有使用该功能。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM