适用于SQL Server 2012的JSON_VALUE?

时间:2018-10-10 04:26:30

标签: json sql-server-2012

我想从SQL的JSON字符串中提取值。开始编写一个函数来执行此操作,但是后来我想到“肯定有人已经这样做了吗?”。当我看到SQL Server中现在有一个JSON_VALUE函数时,我感到非常兴奋……但是当我意识到直到2016年才添加它时,我感到非常失望。:(

所以...我正在写自己的函数版本。我确定它一开始似乎会起作用,然后偶尔会出错,直到我逐步完善为止。

但是我希望有人已经在这方面取得了先机,并弄清了我在初稿中肯定会忽略的一些问题……希望这里有人可以指出吗?

2 个答案:

答案 0 :(得分:2)

首先,谢谢@BVernon-您的回答使我步入正轨。 我已经对此进行了改进,它可以:

  • 返回字符串,数字或null和句柄
  • 处理JSON引号转义
  • 发现更好/更可靠-如果键是其他字符串值的一部分,它会根据"key":进行搜索,而不仅是根据key进行搜索。
CREATE FUNCTION [dbo].[GetJsonValue](@key varchar(100), @data nvarchar(max))
RETURNS nvarchar(max)
AS
BEGIN
  DECLARE @keyJson varchar(105) = '"' + @key+ '":'
  DECLARE @keyIdx int = CHARINDEX(@keyJson, @data)
  IF @keyIdx = 0 RETURN null

  DECLARE @valueIdx int = @keyIdx + LEN(@keyJson)
  DECLARE @termIdx int = CHARINDEX('"', @data, @valueIdx)

  IF @termIdx <> 0 BEGIN
    SET @valueIdx = @valueIdx + 1
    SET @termIdx = CHARINDEX('"', @data, @valueIdx)

    -- Overcome JSON qoute escape
    WHILE SUBSTRING(@data, @termIdx-1, 1) = '\'
    BEGIN
      SET @termIdx = CHARINDEX('"', @data, @termIdx + 1)
    END
  END ELSE BEGIN
    SET @termIdx = CHARINDEX(',', @data, @valueIdx)
    IF @termIdx = 0 SET @termIdx = CHARINDEX('}', @data, @valueIdx)
  END

  IF @termIdx = 0 RETURN null

  -- Replace escapte quote before return value
  RETURN REPLACE(SUBSTRING(@data, @valueIdx, @termIdx - @valueIdx), '\"', '"')
END

它仍然有局限性:

  • 不支持处理嵌套对象,而且,如果键位于对象深处,则可能会给出错误的结果
  • 除了通过键返回标量值之外,什么都不支持

这些是一些测试:

-- These work just fine
print [dbo].[GetJsonValue]('foo', '{"foo":"bar"}')
print [dbo].[GetJsonValue]('foo', '{"foo":"Quoted \"bar\""}')
print [dbo].[GetJsonValue]('foo', '{"foo":55}')
print [dbo].[GetJsonValue]('foo', '{"foo":null}')
print [dbo].[GetJsonValue]('foo', '{"a":"foo","foo":"baz"}')   -- no false positive

-- CANNOT HANDLE SPACES
print [dbo].[GetJsonValue]('foo', '{"foo":   "bar"}')

-- FALSE POSITIVE!!!
print [dbo].[GetJsonValue]('foo', '{"nested:{"foo":123}}')
print [dbo].[GetJsonValue]('foo', '[{"foo":123}]')

输出为:

bar
Quoted "bar"
55
null
baz

123
123

答案 1 :(得分:0)

好吧,因为似乎没有人提供任何东西,所以这是我到目前为止编写的代码。也许会帮我穿鞋的下一个人。我决定根据要检索的值的类型使用单独的函数。特别需要注意的是,date函数用于检索自1970年以来的毫秒数的值,而十进制函数具有用于指定该值是否带引号的参数。

create function [dbo].[GetJsonDateValue](@Key varchar(100), @data nvarchar(max))
returns datetime
as
begin
    declare @keyIdx int = charindex(@Key, @data)
    declare @valueIdx int = @keyIdx + len(@Key) + 2 -- +2 to account for characters between key and value
    declare @termIdx int = charindex(',', @data, @keyIdx)

    -- In case it's last item in an object
    if @termIdx = 0
    set @termIdx = charindex('}', @data, @keyIdx)

    declare @valueLength int = @termIdx - @valueIdx
    declare @secondsSince1970 bigint = cast(substring(@data, @valueIdx, @valueLength) as bigint) / 1000

    declare @retValue datetime = dateadd(s, @secondsSince1970, '19700101')
    return @retValue
end
GO

CREATE function [dbo].[GetJsonDecimalValue](@Key varchar(100), @data nvarchar(max), @quoted bit)
returns decimal(9,2)
as
begin
    declare @keyIdx int = charindex(@Key, @data)
    declare @valueIdx int = @keyIdx + len(@Key) + 2 -- +2 to account for characters between key and value
            + case when @quoted = 1 then 1 else 0 end -- +1 more for quote around value if present
    declare @termIdx int = charindex(case @quoted when 1 then '"' else ',' end, @data, @valueIdx)

    -- In case it's last item in an object and not quoted
    if @quoted = 0 and @termIdx = 0
    set @termIdx = charindex('}', @data, @keyIdx)

    declare @valueLength int = @termIdx - @valueIdx

    if @valueLength = 0
    return null

    declare @retValue decimal(9,2) = cast(substring(@data, @valueIdx, @valueLength) as decimal(9,2))
    return @retValue
end
GO

CREATE function [dbo].[GetJsonStringValue](@Key varchar(100), @data nvarchar(max))
returns varchar(max)
as
begin
    declare @keyIdx int = charindex(@Key, @data)
    declare @valueIdx int = @keyIdx + len(@Key) + 3 -- +3 to account for characters between key and value
    declare @termIdx int = charindex('"', @data, @valueIdx)

    declare @valueLength int = @termIdx - @valueIdx
    declare @retValue varchar(max) = substring(@data, @valueIdx, @valueLength)
    return @retValue
end
GO