我想詢問一個遞迴的概念寫法,附上圖輔助描述
我有一張表,這張表是透過方法PraseJson
丟入變數後回傳的表。
我的目的是想要寫一段SQL
當isTerminal = 0
時,我就要呼叫方法PraseJson
,當isTerminal = 1
時,我就要呼叫方法InsertToDB
並在InsertToDB
做完以後,將其回傳的@Id,存放在暫存@Table中。
目前已經寫出以下遞迴,如果符合isTerminal = 0
的條件,就一直呼叫方法PraseJson
,程式寫法與執行完後的長相如下 :
方法PraseJson
是使用Sql的openjson
的語法去做字串分解的
--定義變數
declare @DeclaringType nvarchar(max)=N'Book'
declare
@json nvarchar(max)=N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
--遞迴
;with cte as (
select
*
from
PraseJson(@json, 0)
)
select
x.*
from
cte x
union all
select
z.*
from
cte y
cross apply PraseJson('{"' + Key + '":' + Value + '}',isList) z
where y.isTerminal = 0
但我想不出來要如何將以下條件加入我的遞迴寫法中呢?
當isTerminal = 0
時,我就要呼叫方法PraseJson
當isTerminal = 1
時,我就要呼叫方法InsertToDB
並在InsertToDB
做完以後,將其回傳的@targetId,存放在暫存Table中
InsertToDB
之後table的長相
select出暫存Table的長相
目前因方法裡面牽涉的table與呼叫其他方法較多,因此先沒有詳細敘述此部分,想詢問一個概念上的寫法,謝謝!
--補充--PraseJson
是Function,其作用是將傳入的Json字串使用sql的openJson分解成key、value(2016以上的版本才有),並透過isJson判斷是不是Terminal的值,以供後續判斷value值是否要再解析,最後回傳一個table變數。
InsertToDB
是預存程序,其作用是將key、value做insert到table中,會回傳一個存入DB後自動生成的id。
暫存Table的長相會是許多InsertToDB
執行完成後回傳的id,會使用暫存Table是因為我後續要再做其他事情。
我的目標是要透過丟入一個Json字串,解析他以後存入table,希望最後可以select出存入DB後的id。所以簡單來說是想詢問,當我有A方法與B方法時,會依照條件重複的呼叫這兩個方法,此時要怎麼運用SQL去撰寫遞迴呢?
不確定是不是這樣,給你參考
PraseJson
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[PraseJson](@json NVARCHAR(MAX), @key NVARCHAR(1000) NULL)
RETURNS @TVP TABLE
(
[Key] NVARCHAR(1000),
isType BIT,
isList BIT,
isTermital BIT,
[Value] NVARCHAR(MAX)
)
AS
BEGIN
IF @key IS NULL
INSERT INTO @TVP
SELECT [key] AS [Key],
CASE WHEN [type] >= 4 THEN 1 ELSE 0 END AS isType,
CASE WHEN [type] = 4 THEN 1 ELSE 0 END AS isList,
CASE WHEN [type] <= 3 THEN 1 ELSE 0 END AS isTermital,
[value] AS [Value]
FROM OPENJSON(@json)
ELSE
INSERT INTO @TVP
SELECT [key] AS [Key],
CASE WHEN [type] >= 4 THEN 1 ELSE 0 END AS isType,
CASE WHEN [type] = 4 THEN 1 ELSE 0 END AS isList,
CASE WHEN [type] <= 3 THEN 1 ELSE 0 END AS isTermital,
[value] AS [Value]
FROM OPENJSON(@json, '$.' + @key)
RETURN
END
程式
DECLARE @json NVARCHAR(4000) = N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
SET @json = REPLACE(REPLACE(REPLACE(@json,' ',''),CHAR(10),''),CHAR(13),'')
;WITH CTE AS
(
SELECT *
FROM dbo.PraseJson(@json, 'Book')
UNION ALL
SELECT B.*
FROM (
SELECT * FROM CTE WHERE isTermital=0 AND isList=0
) AS A
CROSS APPLY dbo.PraseJson(A.[Value], NULL) AS B
)
SELECT * FROM CTE
WHERE isTermital=1
結果
Key isType isList isTermital Value
---------|----------|----------|--------------|-------------
IssueDate 0 0 1 02-15-2019
Type 0 0 1 AnyType
Name 0 0 1 Annie
Sex 0 0 1 Female
2019/03/19 更新:
因為不知道 PraseJson 真實邏輯,所以結果不太一樣,但邏輯大概是這樣~
PraseJson
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[PraseJson](@json NVARCHAR(MAX), @key NVARCHAR(1000))
RETURNS @TVP TABLE
(
topKey NVARCHAR(1000),
[Key] NVARCHAR(1000),
isType BIT,
isList BIT,
isTermital BIT,
[Value] NVARCHAR(MAX)
)
AS
BEGIN
INSERT INTO @TVP
SELECT @key AS topKey,
[key] AS [Key],
CASE WHEN [type] >= 4 THEN 1 ELSE 0 END AS isType,
CASE WHEN [type] = 4 THEN 1 ELSE 0 END AS isList,
CASE WHEN [type] <= 3 THEN 1 ELSE 0 END AS isTermital,
[value] AS [Value]
FROM OPENJSON(@json, '$."' + @key + '"')
RETURN
END
程式
DECLARE @json NVARCHAR(4000) = N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
SET @json = REPLACE(REPLACE(REPLACE(@json,' ',''),CHAR(10),''),CHAR(13),'')
;WITH CTE AS
(
SELECT *
FROM dbo.PraseJson(@json, 'Book')
UNION ALL
SELECT B.*
FROM (
SELECT * FROM CTE WHERE isTermital=0
) AS A
CROSS APPLY dbo.PraseJson('{"' + A.[Key] + '":' + A.[Value] + '}', A.[Key]) AS B
)
--最後執行 InsertToDB
SELECT B.targetId
FROM (
SELECT * FROM CTE WHERE isTermital=1
) AS A
CROSS APPLY dbo.InsertToDB() AS B
遞迴的結果
2019/03/20 更新-函數版本:
需另外新增 InsertToDB 和 Loop 函數
InsertToDB
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[InsertToDB](@key NVARCHAR(1000), @value NVARCHAR(MAX))
RETURNS NVARCHAR(1000)
AS
BEGIN
RETURN @key + ':' + @value
END
GO
Loop
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[Loop](@json NVARCHAR(MAX), @key NVARCHAR(1000))
RETURNS @TVP TABLE
(
[targetid] NVARCHAR(1000)
)
AS
BEGIN
DECLARE @Temp TABLE
(
topKey NVARCHAR(1000),
[Key] NVARCHAR(1000),
isType BIT,
isList BIT,
isTermital BIT,
[Value] NVARCHAR(MAX)
)
--先將JSON轉換後的結果存入@Temp
INSERT INTO @Temp
SELECT * FROM dbo.PraseJson(@json, @key)
INSERT INTO @TVP
--如果isTermital=1呼叫InsertToDB
SELECT dbo.InsertToDB(A.[Key], A.[Value]) AS targetid
FROM (
SELECT * FROM @Temp WHERE isTermital=1
) AS A
--CROSS APPLY dbo.InsertToDB(A.[Key]) AS B --如果InsertToDB是資料表函數
UNION ALL
--如果isTermital=0繼續遞迴
SELECT B.targetid AS targetid
FROM (
SELECT * FROM @Temp WHERE isTermital=0
) AS A
CROSS APPLY dbo.[Loop]('{"' + A.[Key] + '":' + A.[Value] + '}', A.[Key]) AS B
RETURN
END
程式
DECLARE @json NVARCHAR(4000) = N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
SET @json = REPLACE(REPLACE(REPLACE(@json,' ',''),CHAR(10),''),CHAR(13),'')
SELECT * FROM dbo.[Loop](@json, 'Book')
結果
不好意思,可能我沒有表達的很詳盡,PraseJson與InsertToDB這兩個方法已經寫好了,只是要將它們組起來的時候,不知道怎麼組,所以想說詢問一個概念上的寫法,但真的非常感謝您提供的方法!
請問有辦法將
--最後執行 InsertToDB
合併到上面的遞迴之中嗎?
很感謝您的耐心回覆與解答~
不行~
CTE 遞迴和函數遞迴不太一樣
每次 UNION ALL 的結果會成為下次遞迴的起始資料
因此不能把 PraseJson 和 InsertToDB 的結果放在一起
最多只能把最後 InsertToDB 的部分再包成另一個 CTE
要寫在一起的話函數遞迴才能辦到,函數版本我更新在上面回覆。
不過函數遞迴有32層限制,所以要慎用~~
DECLARE @json NVARCHAR(4000) = '{"0":{"1":{"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"10":{"11":{"12":{"13":{"14":{"15":{"16":{"17":{"18":{"19":{"20":{"21":{"22":{"23":{"24":{"25":{"26":{"27":{"28":{"29":{"30":{"31":{"32":1}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}'
SELECT * FROM dbo.[Loop](@json, '0')
超過預存程序、函數、觸發程序或檢視巢狀層級的最大值 (上限為 32)。
我看了你問的三個問題
其實是同一個問題
就是「把 JSON 資料存進 SQL Table」
更精確一點地說
是「把一整個 JSON 剖析成 key-value pair存進單一個 Table」
我實在想不出有什麼這麼做的必要性或實際應用的案例經驗
也可能因為如此,不會有什麼人回答這問題
不過我還是 Google 到了這篇
參考看看合不合用
dbo.JSONHierarchy
祝你好運