解析T-SQL中的JSON

huangapple go评论95阅读模式
英文:

Parse JSON from tsql

问题

I need to parse JSON using TSQL.

我需要使用 TSQL 解析 JSON。

I find a function to create a temporary table contains all values of the json and the mode to find every elements of it.

我找到了一个函数来创建一个临时表,其中包含 JSON 的所有值,以及查找其每个元素的模式。

Not there are an issue. I have a JSON with an array of int value, negative and positive value. But the function convert all number in positive number. So I need to fixed them.

但是存在一个问题。我的 JSON 中有一个整数值的数组,包括负数和正数。但是该函数将所有数字转换为正数。因此,我需要修复它们。

This is my json:

这是我的 JSON:

declare @json as nvarchar(max)
set @json = N'[

{
"timestamp": 1679424324000,
"SENSORNAME": "DEM-Checkme 1607",
"uuid": "4FRA001",
"PULSE": "99",
"SPO2": "0",
"PI": "0.00",
"DIA": "0",
"SYS": "0",
"TEMP": "0.0",
"HR": "99",
"ECGWAVE": [
-52,
-51,
-50,
-48,
-47,
-46,
-46,
-46,
-46,
-45,
-44,
-41,
-38,
-33,
-28,
-22,
-16,
-11,
-7,
-6,
-5,
-7,
-10,
-14,
-19,
-25,
-31,
-35,
-39,
-40,
-42,
-41,
-40,
-37,
-35,
-32,
-30
],
"QRS": "74",
"QTC": "272",
"QT": "224",
"ST": "-2"
}

]'

这是我的 JSON:

EXEC @return_value = [dbo].[AA_P_ParseJsonECG]
@json = @json

This is the function:

这是函数:

create FUNCTION [dbo].[AA_F_ParseJSON]( @JSON NVARCHAR(MAX))
RETURNS @hierarchy TABLE
(
Element_ID INT IDENTITY(1, 1) NOT NULL, /* internal surrogate primary key gives the order of parsing and the list order /
SequenceNo [int] NULL, /
the place in the sequence for the element /
Parent_ID INT null, /
if the element has a parent then it is in this column. The document is the ultimate parent, so you can get the structure from recursing from the document /
Object_ID INT null, /
each list or object has an object id. This ties all elements to a parent. Lists are treated as objects here /
Name NVARCHAR(2000) NULL, /
the Name of the object /
StringValue NVARCHAR(MAX) NOT NULL,/the string representation of the value of the element. /
ValueType VARCHAR(10) NOT null /
the declared type of the value represented as a string in StringValue
/
)
/

*/

AS
BEGIN
DECLARE
@FirstObject INT, --the index of the first open bracket found in the JSON string
@OpenDelimiter INT,--the index of the next open bracket found in the JSON string
@NextOpenDelimiter INT,--the index of subsequent open bracket found in the JSON string
@NextCloseDelimiter INT,--the index of subsequent close bracket found in the JSON string
@Type NVARCHAR(10),--whether it denotes an object or an array
@NextCloseDelimiterChar CHAR(1),--either a '}' or a ']'
@Contents NVARCHAR(MAX), --the unparsed contents of the bracketed expression
@Start INT, --index of the start of the token that you are parsing
@end INT,--index of the end of the token that you are parsing
@param INT,--the parameter at the end of the next Object/Array token
@EndOfName INT,--the index of the start of the parameter at end of Object/Array token
@token NVARCHAR(200),--either a string or object
@value NVARCHAR(MAX), -- the value as a string
@SequenceNo int, -- the sequence number within a list
@Name NVARCHAR(200), --the Name as a string
@Parent_ID INT,--the next parent ID to allocate
@lenJSON INT,--the current length of the JSON String
@characters NCHAR(36),--used to convert hex to decimal
@result BIGINT,--the value of the hex symbol being parsed
@index SMALLINT,--used for parsing the hex value
@Escape INT --the index of the next escape character

DECLARE @Strings TABLE /* in this temporary table we keep all strings, even the Names of the elements, since they are 'escaped' in a different way, and may contain, unescaped, brackets denoting objects or lists. These are replaced in the JSON string by tokens representing the string /
(
String_ID INT IDENTITY(1, 1),
StringValue NVARCHAR(MAX)
)
SELECT--initialise the characters to convert hex to ascii
@characters='0123456789abcdefghijklmnopqrstuvwxyz',
@SequenceNo=0, --set the sequence no. to something sensible.
/
firstly we process all strings. This is done because [{} and ] aren't escaped in strings, which complicates an iterative parse. */
@Parent_ID=0;
WHILE 1=1 --forever until there is nothing more to do
BEGIN
SELECT
@start=PATINDEX('%[^a-zA-Z]["]%', @json collate SQL_Latin1_General_CP850_Bin);--next delimited string
IF @start=0 BREAK --no more so drop through the WHILE loop
IF SUBSTRING(@json, @start+1, 1)='"'
BEGIN --Delimited Name
SET @start=@Start+1;
SET @end=PATINDEX('%[^]["]%', RIGHT(@json, LEN(@json+'"')-@start) collate SQL_Latin1_General_CP850_Bin);
END
IF @end=0 --either the end or no end delimiter to last string
BEGIN-- check if ending with a double slash...
SET @end=PATINDEX('%[][]["]%', RIGHT(@json, LEN(@json+'"')-@start) collate SQL_Latin1_General_CP850_Bin);
IF @end=0 --we really have reached the end
BEGIN
BREAK --assume all tokens found
END
END
SELECT @token=SUBSTRING(@json, @start+1, @end-1)
--now put in the escaped control characters
SELECT @token=REPLACE(@token, FromString, ToString)
FROM
(SELECT '\b', CHAR(08)
UNION ALL SELECT '\f', CHAR(12)
UNION ALL SELECT '\n', CHAR(10)
UNION ALL SELECT '\r', CHAR(13)
UNION ALL SELECT '\t', CHAR(09)
UNION ALL SELECT '"', '"'
UNION ALL SELECT '/', '/'
) substitutions(FromString, ToString)
SELECT @token=Replace(@token, '\', '')
SELECT @result=0, @escape=1
--Begin to take out any hex escape codes
WHILE @escape>

英文:

I need to parse JSON using TSQL.

I find a function to create a temporary table contains all values of the json and the mode to find every elements of it.

Not there are an issue. I have a JSON with an array of int value, negative and positive value. But the function convert all number in positive number. So I need to fixed them.
This is my json:

  1. declare @json as nvarchar(max)
  2. set @json = N'[
  3. {
  4. "timestamp": 1679424324000,
  5. "SENSORNAME": "DEM-Checkme 1607",
  6. "uuid": "4FRA001",
  7. "PULSE": "99",
  8. "SPO2": "0",
  9. "PI": "0.00",
  10. "DIA": "0",
  11. "SYS": "0",
  12. "TEMP": "0.0",
  13. "HR": "99",
  14. "ECGWAVE": [
  15. -52,
  16. -51,
  17. -50,
  18. -48,
  19. -47,
  20. -46,
  21. -46,
  22. -46,
  23. -46,
  24. -45,
  25. -44,
  26. -41,
  27. -38,
  28. -33,
  29. -28,
  30. -22,
  31. -16,
  32. -11,
  33. -7,
  34. -6,
  35. -5,
  36. -7,
  37. -10,
  38. -14,
  39. -19,
  40. -25,
  41. -31,
  42. -35,
  43. -39,
  44. -40,
  45. -42,
  46. -41,
  47. -40,
  48. -37,
  49. -35,
  50. -32,
  51. -30
  52. ],
  53. "QRS": "74",
  54. "QTC": "272",
  55. "QT": "224",
  56. "ST": "-2"
  57. }
  58. ]'
  59. EXEC @return_value = [dbo].[AA_P_ParseJsonECG]
  60. @json = @json

This is the function:

  1. create FUNCTION [dbo].[AA_F_ParseJSON]( @JSON NVARCHAR(MAX))
  2. RETURNS @hierarchy TABLE
  3. (
  4. Element_ID INT IDENTITY(1, 1) NOT NULL, /* internal surrogate primary key gives the order of parsing and the list order */
  5. SequenceNo [int] NULL, /* the place in the sequence for the element */
  6. Parent_ID INT null, /* if the element has a parent then it is in this column. The document is the ultimate parent, so you can get the structure from recursing from the document */
  7. Object_ID INT null, /* each list or object has an object id. This ties all elements to a parent. Lists are treated as objects here */
  8. Name NVARCHAR(2000) NULL, /* the Name of the object */
  9. StringValue NVARCHAR(MAX) NOT NULL,/*the string representation of the value of the element. */
  10. ValueType VARCHAR(10) NOT null /* the declared type of the value represented as a string in StringValue*/
  11. )
  12. /*
  13. */
  14. AS
  15. BEGIN
  16. DECLARE
  17. @FirstObject INT, --the index of the first open bracket found in the JSON string
  18. @OpenDelimiter INT,--the index of the next open bracket found in the JSON string
  19. @NextOpenDelimiter INT,--the index of subsequent open bracket found in the JSON string
  20. @NextCloseDelimiter INT,--the index of subsequent close bracket found in the JSON string
  21. @Type NVARCHAR(10),--whether it denotes an object or an array
  22. @NextCloseDelimiterChar CHAR(1),--either a '}' or a ']'
  23. @Contents NVARCHAR(MAX), --the unparsed contents of the bracketed expression
  24. @Start INT, --index of the start of the token that you are parsing
  25. @end INT,--index of the end of the token that you are parsing
  26. @param INT,--the parameter at the end of the next Object/Array token
  27. @EndOfName INT,--the index of the start of the parameter at end of Object/Array token
  28. @token NVARCHAR(200),--either a string or object
  29. @value NVARCHAR(MAX), -- the value as a string
  30. @SequenceNo int, -- the sequence number within a list
  31. @Name NVARCHAR(200), --the Name as a string
  32. @Parent_ID INT,--the next parent ID to allocate
  33. @lenJSON INT,--the current length of the JSON String
  34. @characters NCHAR(36),--used to convert hex to decimal
  35. @result BIGINT,--the value of the hex symbol being parsed
  36. @index SMALLINT,--used for parsing the hex value
  37. @Escape INT --the index of the next escape character
  38. DECLARE @Strings TABLE /* in this temporary table we keep all strings, even the Names of the elements, since they are 'escaped' in a different way, and may contain, unescaped, brackets denoting objects or lists. These are replaced in the JSON string by tokens representing the string */
  39. (
  40. String_ID INT IDENTITY(1, 1),
  41. StringValue NVARCHAR(MAX)
  42. )
  43. SELECT--initialise the characters to convert hex to ascii
  44. @characters='0123456789abcdefghijklmnopqrstuvwxyz',
  45. @SequenceNo=0, --set the sequence no. to something sensible.
  46. /* firstly we process all strings. This is done because [{} and ] aren't escaped in strings, which complicates an iterative parse. */
  47. @Parent_ID=0;
  48. WHILE 1=1 --forever until there is nothing more to do
  49. BEGIN
  50. SELECT
  51. @start=PATINDEX('%[^a-zA-Z]["]%', @json collate SQL_Latin1_General_CP850_Bin);--next delimited string
  52. IF @start=0 BREAK --no more so drop through the WHILE loop
  53. IF SUBSTRING(@json, @start+1, 1)='"'
  54. BEGIN --Delimited Name
  55. SET @start=@Start+1;
  56. SET @end=PATINDEX('%[^\]["]%', RIGHT(@json, LEN(@json+'|')-@start) collate SQL_Latin1_General_CP850_Bin);
  57. END
  58. IF @end=0 --either the end or no end delimiter to last string
  59. BEGIN-- check if ending with a double slash...
  60. SET @end=PATINDEX('%[\][\]["]%', RIGHT(@json, LEN(@json+'|')-@start) collate SQL_Latin1_General_CP850_Bin);
  61. IF @end=0 --we really have reached the end
  62. BEGIN
  63. BREAK --assume all tokens found
  64. END
  65. END
  66. SELECT @token=SUBSTRING(@json, @start+1, @end-1)
  67. --now put in the escaped control characters
  68. SELECT @token=REPLACE(@token, FromString, ToString)
  69. FROM
  70. (SELECT '\b', CHAR(08)
  71. UNION ALL SELECT '\f', CHAR(12)
  72. UNION ALL SELECT '\n', CHAR(10)
  73. UNION ALL SELECT '\r', CHAR(13)
  74. UNION ALL SELECT '\t', CHAR(09)
  75. UNION ALL SELECT '\"', '"'
  76. UNION ALL SELECT '\/', '/'
  77. ) substitutions(FromString, ToString)
  78. SELECT @token=Replace(@token, '\\', '\')
  79. SELECT @result=0, @escape=1
  80. --Begin to take out any hex escape codes
  81. WHILE @escape>0
  82. BEGIN
  83. SELECT @index=0,
  84. --find the next hex escape sequence
  85. @escape=PATINDEX('%\x[0-9a-f][0-9a-f][0-9a-f][0-9a-f]%', @token collate SQL_Latin1_General_CP850_Bin)
  86. IF @escape>0 --if there is one
  87. BEGIN
  88. WHILE @index<4 --there are always four digits to a \x sequence
  89. BEGIN
  90. SELECT --determine its value
  91. @result=@result+POWER(16, @index)
  92. *(CHARINDEX(SUBSTRING(@token, @escape+2+3-@index, 1),
  93. @characters)-1), @index=@index+1 ;
  94. END
  95. -- and replace the hex sequence by its unicode value
  96. SELECT @token=STUFF(@token, @escape, 6, NCHAR(@result))
  97. END
  98. END
  99. --now store the string away
  100. INSERT INTO @Strings (StringValue) SELECT @token
  101. -- and replace the string with a token
  102. SELECT @JSON=STUFF(@json, @start, @end+1,
  103. '@string'+CONVERT(NCHAR(5), @@identity))
  104. END
  105. -- all strings are now removed. Now we find the first leaf.
  106. WHILE 1=1 --forever until there is nothing more to do
  107. BEGIN
  108. SELECT @Parent_ID=@Parent_ID+1
  109. --find the first object or list by looking for the open bracket
  110. SELECT @FirstObject=PATINDEX('%[{[[]%', @json collate SQL_Latin1_General_CP850_Bin)--object or array
  111. IF @FirstObject = 0 BREAK
  112. IF (SUBSTRING(@json, @FirstObject, 1)='{')
  113. SELECT @NextCloseDelimiterChar='}', @type='object'
  114. ELSE
  115. SELECT @NextCloseDelimiterChar=']', @type='array'
  116. SELECT @OpenDelimiter=@firstObject
  117. WHILE 1=1 --find the innermost object or list...
  118. BEGIN
  119. SELECT
  120. @lenJSON=LEN(@JSON+'|')-1
  121. --find the matching close-delimiter proceeding after the open-delimiter
  122. SELECT
  123. @NextCloseDelimiter=CHARINDEX(@NextCloseDelimiterChar, @json,
  124. @OpenDelimiter+1)
  125. --is there an intervening open-delimiter of either type
  126. SELECT @NextOpenDelimiter=PATINDEX('%[{[[]%',
  127. RIGHT(@json, @lenJSON-@OpenDelimiter)collate SQL_Latin1_General_CP850_Bin)--object
  128. IF @NextOpenDelimiter=0
  129. BREAK
  130. SELECT @NextOpenDelimiter=@NextOpenDelimiter+@OpenDelimiter
  131. IF @NextCloseDelimiter<@NextOpenDelimiter
  132. BREAK
  133. IF SUBSTRING(@json, @NextOpenDelimiter, 1)='{'
  134. SELECT @NextCloseDelimiterChar='}', @type='object'
  135. ELSE
  136. SELECT @NextCloseDelimiterChar=']', @type='array'
  137. SELECT @OpenDelimiter=@NextOpenDelimiter
  138. END
  139. ---and parse out the list or Name/value pairs
  140. SELECT
  141. @contents=SUBSTRING(@json, @OpenDelimiter+1,
  142. @NextCloseDelimiter-@OpenDelimiter-1)
  143. SELECT
  144. @JSON=STUFF(@json, @OpenDelimiter,
  145. @NextCloseDelimiter-@OpenDelimiter+1,
  146. '@'+@type+CONVERT(NCHAR(5), @Parent_ID))
  147. WHILE (PATINDEX('%[A-Za-z0-9@+.e]%', @contents collate SQL_Latin1_General_CP850_Bin))<>0
  148. BEGIN
  149. IF @Type='object' --it will be a 0-n list containing a string followed by a string, number,boolean, or null
  150. BEGIN
  151. SELECT
  152. @SequenceNo=0,@end=CHARINDEX(':', ' '+@contents)--if there is anything, it will be a string-based Name.
  153. SELECT @start=PATINDEX('%[^A-Za-z@][@]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)--AAAAAAAA
  154. SELECT @token=RTrim(Substring(' '+@contents, @start+1, @End-@Start-1)),
  155. @endofName=PATINDEX('%[0-9]%', @token collate SQL_Latin1_General_CP850_Bin),
  156. @param=RIGHT(@token, LEN(@token)-@endofName+1)
  157. SELECT
  158. @token=LEFT(@token, @endofName-1),
  159. @Contents=RIGHT(' '+@contents, LEN(' '+@contents+'|')-@end-1)
  160. SELECT @Name=StringValue FROM @strings
  161. WHERE string_id=@param --fetch the Name
  162. END
  163. ELSE
  164. SELECT @Name=null,@SequenceNo=@SequenceNo+1
  165. SELECT
  166. @end=CHARINDEX(',', @contents)-- a string-token, object-token, list-token, number,boolean, or null
  167. IF @end=0
  168. --HR Engineering notation bugfix start
  169. IF ISNUMERIC(@contents) = 1
  170. SELECT @end = LEN(@contents) + 1
  171. Else
  172. --HR Engineering notation bugfix end
  173. SELECT @end=PATINDEX('%[A-Za-z0-9@+.e][^A-Za-z0-9@+.e]%', @contents+' ' collate SQL_Latin1_General_CP850_Bin) + 1
  174. SELECT
  175. @start=PATINDEX('%[^A-Za-z0-9@+.e][A-Za-z0-9@+.e]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)
  176. --select @start,@end, LEN(@contents+'|'), @contents
  177. SELECT
  178. @Value=RTRIM(SUBSTRING(@contents, @start, @End-@Start)),
  179. @Contents=RIGHT(@contents+' ', LEN(@contents+'|')-@end)
  180. IF SUBSTRING(@value, 1, 7)='@object'
  181. INSERT INTO @hierarchy
  182. (Name, SequenceNo, Parent_ID, StringValue, Object_ID, ValueType)
  183. SELECT @Name, @SequenceNo, @Parent_ID, SUBSTRING(@value, 8, 5),
  184. SUBSTRING(@value, 8, 5), 'object'
  185. ELSE
  186. IF SUBSTRING(@value, 1, 6)='@array'
  187. INSERT INTO @hierarchy
  188. (Name, SequenceNo, Parent_ID, StringValue, Object_ID, ValueType)
  189. SELECT @Name, @SequenceNo, @Parent_ID, SUBSTRING(@value, 7, 5),
  190. SUBSTRING(@value, 7, 5), 'array'
  191. ELSE
  192. IF SUBSTRING(@value, 1, 7)='@string'
  193. INSERT INTO @hierarchy
  194. (Name, SequenceNo, Parent_ID, StringValue, ValueType)
  195. SELECT @Name, @SequenceNo, @Parent_ID, StringValue, 'string'
  196. FROM @strings
  197. WHERE string_id=SUBSTRING(@value, 8, 5)
  198. ELSE
  199. IF @value IN ('true', 'false')
  200. INSERT INTO @hierarchy
  201. (Name, SequenceNo, Parent_ID, StringValue, ValueType)
  202. SELECT @Name, @SequenceNo, @Parent_ID, @value, 'boolean'
  203. ELSE
  204. IF @value='null'
  205. INSERT INTO @hierarchy
  206. (Name, SequenceNo, Parent_ID, StringValue, ValueType)
  207. SELECT @Name, @SequenceNo, @Parent_ID, @value, 'null'
  208. ELSE
  209. IF PATINDEX('%[^0-9]%', @value collate SQL_Latin1_General_CP850_Bin)>0
  210. INSERT INTO @hierarchy
  211. (Name, SequenceNo, Parent_ID, StringValue, ValueType)
  212. SELECT @Name, @SequenceNo, @Parent_ID, @value, 'real'
  213. ELSE
  214. INSERT INTO @hierarchy
  215. (Name, SequenceNo, Parent_ID, StringValue, ValueType)
  216. SELECT @Name, @SequenceNo, @Parent_ID, @value, 'int'
  217. if @Contents=' ' Select @SequenceNo=0
  218. END
  219. END
  220. INSERT INTO @hierarchy (Name, SequenceNo, Parent_ID, StringValue, Object_ID, ValueType)
  221. SELECT '-',1, NULL, '', @Parent_ID-1, @type
  222. --
  223. RETURN
  224. END

答案1

得分: 3

你应该只使用OPENJSON函数进行正确的JSON解析。

  1. SELECT
  2. timestamp,
  3. SENSORNAME,
  4. uuid,
  5. PULSE,
  6. SPO2,
  7. PI,
  8. DIA,
  9. SYS,
  10. TEMP,
  11. HR,
  12. QRS,
  13. QTC,
  14. QT,
  15. ST,
  16. j2.value AS ECGWAVE_value
  17. FROM OPENJSON(@json)
  18. WITH (
  19. timestamp bigint,
  20. SENSORNAME varchar(100),
  21. uuid varchar(30),
  22. PULSE decimal(18,9),
  23. SPO2 decimal(18,9),
  24. PI decimal(18,9),
  25. DIA decimal(18,9),
  26. SYS decimal(18,9),
  27. TEMP decimal(18,9),
  28. HR decimal(18,9),
  29. ECGWAVE nvarchar(MAX) AS JSON,
  30. QRS decimal(18,9),
  31. QTC decimal(18,9),
  32. QT decimal(18,9),
  33. ST decimal(18,9)
  34. ) j1
  35. CROSS APPLY OPENJSON(j1.ECGWAVE) j2;

如果由于兼容性级别问题导致该函数不可用,您可以从通常具有最高兼容性级别的master数据库中执行它。

英文:

You should just use proper JSON parsing using the OPENJSON function.

  1. SELECT
  2. timestamp,
  3. SENSORNAME,
  4. uuid,
  5. PULSE,
  6. SPO2,
  7. PI,
  8. DIA,
  9. SYS,
  10. TEMP,
  11. HR,
  12. QRS,
  13. QTC,
  14. QT,
  15. ST,
  16. j2.value AS ECGWAVE_value
  17. FROM OPENJSON(@json)
  18. WITH (
  19. timestamp bigint,
  20. SENSORNAME varchar(100),
  21. uuid varchar(30),
  22. PULSE decimal(18,9),
  23. SPO2 decimal(18,9),
  24. PI decimal(18,9),
  25. DIA decimal(18,9),
  26. SYS decimal(18,9),
  27. TEMP decimal(18,9),
  28. HR decimal(18,9),
  29. ECGWAVE nvarchar(MAX) AS JSON,
  30. QRS decimal(18,9),
  31. QTC decimal(18,9),
  32. QT decimal(18,9),
  33. ST decimal(18,9)
  34. ) j1
  35. CROSS APPLY OPENJSON(j1.ECGWAVE) j2;

db<>fiddle

If the function is not available due to your compatibility level, you can execute it from the master database, which normally has the highest compatibility level.

huangapple
  • 本文由 发表于 2023年6月12日 22:55:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/76457879.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定