【问题标题】:Using a SQL Parameter where a String Literal is required在需要字符串文字的情况下使用 SQL 参数
【发布时间】:2019-06-08 01:45:39
【问题描述】:

我正在尝试将来自here 的顶级解决方案的最顶层代码转换为 SQL 用户定义函数。但是,当我尝试对数字 4 使用变量时(在 value 函数的第一个参数中),它会出错。

所以这行得通:

SELECT CAST('<x>' + REPLACE(@Input,',','</x><x>') + '</x>' AS XML).value('/x[4]','int')

...但是每当我尝试替换 '/x[4]' 以使用变量代替 4 时,我都会收到类似

的消息

XML 数据类型方法“value”的参数 1 必须是字符串字面量。

到目前为止,这是我完整的用户定义函数......只是学习如何:

USE [DBName]
GO

CREATE FUNCTION fx_SegmentN
    (@Input AS VARCHAR(100),
     @Number AS VARCHAR(1)) 
RETURNS varchar(100)
AS
BEGIN
    DECLARE @ValueStringLiteral varchar(14)
    SET @ValueStringLiteral = '/x[' + @Number + ']'

    RETURN '' + 
        CASE
            WHEN @Number <1
                THEN ('ERROR')
            WHEN @Number = 1
                THEN (LEFT(@Input, CHARINDEX('-', @Input, 1)-1))
            WHEN @Number > 1
                --THEN (SELECT CAST('<x>' + REPLACE(@Input,',','</x><x>') + '</x>' AS XML).value('/x[4]','int')) --THIS LINE WORKS
                THEN (SELECT CAST('<x>' + REPLACE(@Input,',','</x><x>') + '</x>' AS XML).value('/x[' + @Number + ']','int')) --THIS ONE DOES NOT
            ELSE
                (NULL)
        END + ''
END

【问题讨论】:

  • 你为什么要传递一个叫做“数字”的字符串?
  • 您在问题中找到并链接的答案为您提供了解决方案:当然您可以使用变量作为分隔符和位置(使用sql:column直接从查询值中检索位置):DECLARE @dlmt NVARCHAR(10)=N' '; DECLARE @pos INT = 2; SELECT CAST(N'&lt;x&gt;' + REPLACE(@input,@dlmt,N'&lt;/x&gt;&lt;x&gt;') + N'&lt;/x&gt;' AS XML).value('/x[sql:variable("@pos")][1]','nvarchar(max)')

标签: sql sql-server xml tsql csv


【解决方案1】:

试试这个:

CREATE FUNCTION fx_SegmentN
    (
         @Input AS VARCHAR(100)
        ,@Number AS VARCHAR(1)
    ) RETURNS varchar(100)
    AS
BEGIN
    DECLARE @XML XML;
    DECLARE @value VARCHAR(100);

    SET @XML = CAST('<x>' + REPLACE(@Input,',','</x><x>') + '</x>' AS XML);

    WITH DataSource ([rowID], [rowValue]) AS
    (
        SELECT ROW_NUMBER() OVER (ORDER BY T.c ASC) 
              ,T.c.value('.', 'VARCHAR(100)')
        FROM @XML.nodes('./x') T(c)
    )
    SELECT @value = [rowValue]
    FROM DataSource
    WHERE [rowID] = @Number;

    RETURN @value;
END

GO

SELECT dbo.fx_SegmentN ('1a,2b,3c,4d,5e,6f,7g,8h', 3);

你可能对Does the nodes() method keep the document order?感兴趣

【讨论】:

  • 首先创建一组所有可能的元素只是为了将其丢弃并返回我们需要的元素是非常昂贵的......此外,您的函数与@input 中的禁止字符一起破坏。并且 multi-line-scalar-function 是性能杀手...此外,它是无证的,因此不能保证,这个ROW_NUMBER() hack 将在任何情况下工作,并将在未来继续工作版本。真的不是最好的解决方案。 This 清楚地展示了如何使用sql:variable()sql:column()
  • @Shnugo 当然 - 标量函数很糟糕,直到 SQL Server 2019 才有并行性。无论如何,输入的最大长度为 100,更具体地说 - 如果我们要进行这些操作的性能,我们最终可能只是标准化数据。
  • true ?,从 v2016 开始,我们有 openjsonkey。这一切只是因为他们“忘记”将其包含在string_split?
  • 是的,@Shnugo,这真是太可惜了 :( 让我们希望 - feedback.azure.com/forums/908035-sql-server/suggestions/…
  • @Shnugo,您的 cmets 说服我使用比我尝试的方法更有效的方法。所以,我使用了“当然你可以使用变量作为分隔符和位置(使用 sql:column 直接从查询的值中检索位置)”下列出的选项虽然我希望原始答案首先明确了该偏好,谢谢!
【解决方案2】:

这是一个丑陋的黑客,但它有效:)

DECLARE @Query AS NVARCHAR(MAX) = N'SELECT CAST(''<x>'' + REPLACE(''' + @Input + ''' ,'','',''</x><x>'') + ''</x>'' AS XML).value(''/x['+ @Number + ']'',''int'')'
EXEC sp_executesql @Query

【讨论】:

  • 这将与@input 中的禁止字符中断,甚至没有必要。 answer linked by the OP 清楚地显示了如何使用 sql:variable()sql:column() 解决这个问题
【解决方案3】:

其实很简单:

declare @x xml = N'
  <x>2345</x>
  <x>vsaaef</x>
  <x>fxcfs</x>
  <x>Number 4</x>
  <x>vxcv</x>
  <x>111</x>
';
declare @Position int = 4;

select t.c.value('./text()[1]', 'nvarchar(100)')
from @x.nodes('/x[position() = sql:variable("@Position")]') t(c);

【讨论】:

  • 这在原则上是正确的,但不必要地昂贵。 answer linked by the OP 提供了一种更好的方法。对于您的情况,这是SELECT @x.value('(/x[sql:variable("@Position")]/text())[1]','nvarchar(100)')
  • @Shnugo,可能,虽然我没有比较执行计划。但如果答案已经知道,那么问这个问题又有什么意义呢?我错过了一些细节吗?将其标记为重复并完成。
  • 无意冒犯...链接问题中接受的答案并不是最佳答案。这就是为什么我没有将其链接为重复... OP 没有找到现有的解决方案...
猜你喜欢
  • 2021-02-03
  • 2017-01-19
  • 2014-04-23
  • 1970-01-01
  • 2018-02-14
  • 2021-10-05
  • 2012-06-09
  • 2013-02-08
  • 2018-09-25
相关资源
最近更新 更多