【问题标题】:SQL Interpolated StringsSQL 插值字符串
【发布时间】:2018-01-29 06:05:29
【问题描述】:

T-SQL 是否支持内插字符串?

让我们举个例子:

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

我希望能够做这样的事情:

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

感谢您的回答!

【问题讨论】:

    标签: sql string tsql interpolation


    【解决方案1】:

    感谢@j.f.sebastian 指出这些解决方案。遗憾的是 xp_sprintf 被限制为 254 个字符,因此在使用长查询时这并不理想。相反,FORMATMESSAGE 限制为 2047 个字符,因此足以运行长查询。

    为了让事情井井有条,我将在一篇文章中总结所有解决方案。

    答案 1:

    使用FORMATMESSAGE 重要的是要知道,使用插值字符串作为第一个参数,它仅支持 SQL 版本 2012 及更高版本,因此我将使用 FORMATMESSAGE 发布 2 个答案:

    SQL 版本 >= 2012:

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


    SQL 版本

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


    答案 2:

    使用xp_sprintf 存储过程需要注意,它限制为 254 个字符,因此对于长查询来说不是一个好主意。

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

    【讨论】:

    • 感谢您的信任。昨天没有时间回来编辑我的答案 - 您应该将此标记为已接受的答案,因为它提供了详细信息!
    • sp_addmessage 方法有一个重要缺点,即从多个会话同时执行时不安全。这可能会导致一些非常混乱的消息。该技术仍然有用,但应注意不要在需要插值时使用它,因为这会大大增加发生冲突的机会。此外,从 SQL Server 2012 开始,CONCAT 可以简化字符串的拼凑,而无需在任何地方添加 CONVERTs。
    • 谢谢塞巴斯蒂安!这是个好主意,完成了。 @JeroenMostert,请举个例子。谢谢
    • 看来FORMATMESSAGE(string, ...) 仅适用于SQL Server 2016 (13.x)
    【解决方案2】:

    当你的字符串很长时,为了便于阅读,我喜欢这样做:

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

    它具有 SQL 字符串可读性的优点,而且您可以多次使用同一个变量。显然这不是正确的插值,但我比 FORMATMESSAGE() 选项更喜欢它,因为

    • 它没有长度限制。谁会写出少于 2047 个字符的查询?! ;)
    • 它支持命名占位符,而不仅仅是有序的占位符
    • 在任何版本的 SQL Server 中均受支持
    • 您可以多次使用同一个占位符

    【讨论】:

    • 漂亮的格式,正是我需要的,谢谢
    【解决方案3】:

    这不是 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
    

    【讨论】:

    • 感谢您的回答!但不幸的是,我发现这个答案有局限性。值得一提的是 xp_sprintf 限制为 254 个字符 :( 所以长查询将无法使用。
    • 抱歉 - 这很烦人。我刚刚发现了另一个可以提供帮助的功能 - FORMATMESSAGE - docs.microsoft.com/en-us/sql/t-sql/functions/…
    • 根据 FORMATMESSAGE 文档,限制为 2047 个字符 - 希望这足以满足您的要求!
    • FORMATMESSAGE 看起来很有希望,但它只支持 SQL 版本 >= 2016。我使用的是 2008 社区版,所以它只能支持内置消息,这意味着,首先要有我自己的字符串参数不起作用。感谢您花时间发布这些链接!我可以确认,因为我还安装了 SQL 2017 服务器和 2008。
    • 找到了一个解决方法,即使它有点难看。我们需要手动添加自定义消息,然后我们可以使用自定义消息号:EXEC sp_addmessage @msgnum=50001, @severity=16, @msgtext=@query, @replace='replace' 你介意将它作为另一个答案发布吗?我可以标记为已接受吗?非常感谢您的帮助:)
    【解决方案4】:

    我喜欢为我的动态 SQL 这样做,所以我 wrote a function for 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];
    

    【讨论】:

      【解决方案5】:

      编写了 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} 既作为文字参数又作为替换参数。这可以使用转义添加,但不能保证付出努力,因为我没有使用该功能。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-06-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-20
        相关资源
        最近更新 更多