如何针对 JSON 列构造动态 SQL where 条件
How to construct dynamic SQL where condition against JSON column
我有一个 SQL table 以 Json 格式存储数据。我正在使用下面的样本数据来理解这个问题。每种文档类型都有自己的 JSON 结构。
DocumentID DocumentTypeID Status JsonData
----------------------------------------------------------------------------
1 2 Active {"FirstName":"Foo","LastName":"Bar","States":"[OK]"}
2 2 Active {"FirstName":"James","LastName":"Smith","States":"[TX,NY]"}
3 3 Active {"Make":"Ford","Model":"Focus","Year":"[2020]"}
4 3 Active {"Make":"Tesla","Model":"X","Year":"[2012,2015,2019]"}
然后我还有一个JSON需要在Where条件
中使用
@Condition = '{"FirstName": "James",LastName:"Smith","States":[TX]}'
我还将DocumentTypeID
作为参数
所以在正常情况下 sql 如果我硬编码 属性 名称然后 SQL 看起来像
SELECT * FROM Documents d
WHERE
d.DocumentTypeID = @DocumentTypeID AND
JSON_VALUE(d.JsonData,'$.FirstName') = JSON_VALUE(@Condition,'$.FirstName') AND
JSON_VALUE(d.JsonData,'$.LastName') = JSON_VALUE(@Condition,'$.LastName') AND
JSON_QUERY(d.JsonData,'$.States') = JSON_QUERY(@Condition,'$.States') -- This line is wrong. I have
-- to check if one array is
-- subset of another array
给定
JsonData
列中的 属性 名称和 Condition
将完全匹配给定的 DocumentTypeID。
我已经有另一个 SQL table 存储 DocumentType 及其属性。如果有帮助,我可以为每个 属性 存储 json 路径,这些路径可用于上述查询以动态构造 where condition
DocumentTypeID PropertyName JsonPath DataType
---------------------------------------------------------------------------------
2 FirstName $.FirstName String
2 LastName $.LastName String
2 States $.States Array
3 Make $.Make String
3 Model $.Model String
3 Year $.Year Array
问题
对于每种文档类型,@condition 将具有不同的 JSON 结构。我如何构建动态的where条件?这在 SQL 中甚至可能吗?
我正在使用 C#.NET,所以我考虑在 C# 中构建 SQL 查询并执行 SQL 查询。但在我走那条路之前,我想检查一下是否可以在 TSQL
中做到这一点
遗憾的是,JSON 支持仅在 2016 版本的 SQL 服务器中添加,并且仍有改进的空间。使用包含数组的 JSON 数据非常麻烦,需要 OPENJSON
获取数据,另一个 OPENJSON
获取数组数据。
基于 SQL 的解决方案是可能的 - 但我写的 - 很麻烦。
首先,创建并填充示例 table(请在您以后的问题中为我们省去这一步):
DECLARE @Documents AS TABLE (
[DocumentID] int,
[DocumentTypeID] int,
[Status] varchar(6),
[JsonData] varchar(100)
);
INSERT INTO @Documents ([DocumentID], [DocumentTypeID], [Status], [JsonData]) VALUES
(1, 2, 'Active', '{"FirstName":"Foo","LastName":"Bar","States":["OK"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["TX","NY"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["OK", "NY"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["OH", "OK"]}'),
(3, 3, 'Active', '{"Make":"Ford","Model":"Focus","Year":[2020]}'),
(4, 3, 'Active', '{"Make":"Tesla","Model":"X","Year":[2012,2015,2019]}');
请注意,我向示例数据添加了几行,以验证条件是否正常工作。
此外,作为旁注 - 问题中的一些 JSON 数据格式不正确 - 我必须修复它。
然后,声明搜索参数(注意:我仍然认为发送一个JSON字符串作为搜索条件可能有风险):
DECLARE @DocumentTypeID int = 2,
@Condition varchar(100) = '{"FirstName": "James","LastName":"Smith","States":["TX", "OH"]}';
(注意:我添加了另一个状态 - 再次确保条件正常工作。)
然后,我使用带有 openjson
和 cross apply
的通用 table 表达式将 json 条件转换为表格数据,并将该 cte 加入到table:
WITH CTE AS
(
SELECT FirstName, LastName, [State]
FROM OPENJSON(@Condition)
WITH (
FirstName varchar(10) '$.FirstName',
LastName varchar(10) '$.LastName',
States nvarchar(max) '$.States' AS JSON
)
CROSS APPLY OPENJSON(States)
WITH (
[State] varchar(2) '$'
)
)
SELECT [DocumentID], [DocumentTypeID], [Status], [JsonData]
FROM @Documents
CROSS APPLY
OPENJSON([JsonData])
WITH(
-- Since we already have to use OPENJSON, no point of also using JSON_VALUE...
FirstName varchar(10) '$.FirstName',
LastName varchar(10) '$.LastName',
States nvarchar(max) '$.States' AS JSON
) As JD
CROSS APPLY OPENJSON(States)
WITH(
[State] varchar(2) '$'
) As JDS
JOIN CTE
ON JD.FirstName = CTE.FirstName
AND JD.LastName = CTE.LastName
AND JDS.[State] = CTE.[State]
WHERE DocumentTypeID = @DocumentTypeID
结果:
DocumentID DocumentTypeID Status JsonData
2 2 Active {"FirstName":"James","LastName":"Smith","States":["TX","NY"]}
2 2 Active {"FirstName":"James","LastName":"Smith","States":["OH", "OK"]}
我有一个 SQL table 以 Json 格式存储数据。我正在使用下面的样本数据来理解这个问题。每种文档类型都有自己的 JSON 结构。
DocumentID DocumentTypeID Status JsonData
----------------------------------------------------------------------------
1 2 Active {"FirstName":"Foo","LastName":"Bar","States":"[OK]"}
2 2 Active {"FirstName":"James","LastName":"Smith","States":"[TX,NY]"}
3 3 Active {"Make":"Ford","Model":"Focus","Year":"[2020]"}
4 3 Active {"Make":"Tesla","Model":"X","Year":"[2012,2015,2019]"}
然后我还有一个JSON需要在Where条件
中使用@Condition = '{"FirstName": "James",LastName:"Smith","States":[TX]}'
我还将DocumentTypeID
作为参数
所以在正常情况下 sql 如果我硬编码 属性 名称然后 SQL 看起来像
SELECT * FROM Documents d
WHERE
d.DocumentTypeID = @DocumentTypeID AND
JSON_VALUE(d.JsonData,'$.FirstName') = JSON_VALUE(@Condition,'$.FirstName') AND
JSON_VALUE(d.JsonData,'$.LastName') = JSON_VALUE(@Condition,'$.LastName') AND
JSON_QUERY(d.JsonData,'$.States') = JSON_QUERY(@Condition,'$.States') -- This line is wrong. I have
-- to check if one array is
-- subset of another array
给定
JsonData
列中的 属性 名称和 Condition
将完全匹配给定的 DocumentTypeID。
我已经有另一个 SQL table 存储 DocumentType 及其属性。如果有帮助,我可以为每个 属性 存储 json 路径,这些路径可用于上述查询以动态构造 where condition
DocumentTypeID PropertyName JsonPath DataType
---------------------------------------------------------------------------------
2 FirstName $.FirstName String
2 LastName $.LastName String
2 States $.States Array
3 Make $.Make String
3 Model $.Model String
3 Year $.Year Array
问题
对于每种文档类型,@condition 将具有不同的 JSON 结构。我如何构建动态的where条件?这在 SQL 中甚至可能吗?
我正在使用 C#.NET,所以我考虑在 C# 中构建 SQL 查询并执行 SQL 查询。但在我走那条路之前,我想检查一下是否可以在 TSQL
中做到这一点遗憾的是,JSON 支持仅在 2016 版本的 SQL 服务器中添加,并且仍有改进的空间。使用包含数组的 JSON 数据非常麻烦,需要 OPENJSON
获取数据,另一个 OPENJSON
获取数组数据。
基于 SQL 的解决方案是可能的 - 但我写的 - 很麻烦。
首先,创建并填充示例 table(请在您以后的问题中为我们省去这一步):
DECLARE @Documents AS TABLE (
[DocumentID] int,
[DocumentTypeID] int,
[Status] varchar(6),
[JsonData] varchar(100)
);
INSERT INTO @Documents ([DocumentID], [DocumentTypeID], [Status], [JsonData]) VALUES
(1, 2, 'Active', '{"FirstName":"Foo","LastName":"Bar","States":["OK"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["TX","NY"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["OK", "NY"]}'),
(2, 2, 'Active', '{"FirstName":"James","LastName":"Smith","States":["OH", "OK"]}'),
(3, 3, 'Active', '{"Make":"Ford","Model":"Focus","Year":[2020]}'),
(4, 3, 'Active', '{"Make":"Tesla","Model":"X","Year":[2012,2015,2019]}');
请注意,我向示例数据添加了几行,以验证条件是否正常工作。
此外,作为旁注 - 问题中的一些 JSON 数据格式不正确 - 我必须修复它。
然后,声明搜索参数(注意:我仍然认为发送一个JSON字符串作为搜索条件可能有风险):
DECLARE @DocumentTypeID int = 2,
@Condition varchar(100) = '{"FirstName": "James","LastName":"Smith","States":["TX", "OH"]}';
(注意:我添加了另一个状态 - 再次确保条件正常工作。)
然后,我使用带有 openjson
和 cross apply
的通用 table 表达式将 json 条件转换为表格数据,并将该 cte 加入到table:
WITH CTE AS
(
SELECT FirstName, LastName, [State]
FROM OPENJSON(@Condition)
WITH (
FirstName varchar(10) '$.FirstName',
LastName varchar(10) '$.LastName',
States nvarchar(max) '$.States' AS JSON
)
CROSS APPLY OPENJSON(States)
WITH (
[State] varchar(2) '$'
)
)
SELECT [DocumentID], [DocumentTypeID], [Status], [JsonData]
FROM @Documents
CROSS APPLY
OPENJSON([JsonData])
WITH(
-- Since we already have to use OPENJSON, no point of also using JSON_VALUE...
FirstName varchar(10) '$.FirstName',
LastName varchar(10) '$.LastName',
States nvarchar(max) '$.States' AS JSON
) As JD
CROSS APPLY OPENJSON(States)
WITH(
[State] varchar(2) '$'
) As JDS
JOIN CTE
ON JD.FirstName = CTE.FirstName
AND JD.LastName = CTE.LastName
AND JDS.[State] = CTE.[State]
WHERE DocumentTypeID = @DocumentTypeID
结果:
DocumentID DocumentTypeID Status JsonData
2 2 Active {"FirstName":"James","LastName":"Smith","States":["TX","NY"]}
2 2 Active {"FirstName":"James","LastName":"Smith","States":["OH", "OK"]}