如何针对 JSON 列构建动态 SQL where 条件
Posted
技术标签:
【中文标题】如何针对 JSON 列构建动态 SQL where 条件【英文标题】:How to construct dynamic SQL where condition against JSON column 【发布时间】:2020-05-21 15:55:41 【问题描述】:我有一个以 Json 格式存储数据的 SQL 表。我正在使用下面的示例数据来理解这个问题。每种文档类型都有自己的 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 完全匹配。
我已经有另一个存储 DocumentType 及其属性的 SQL 表。如果有帮助,我可以为每个可在上述查询中使用的属性存储 json 路径以动态构造 where 条件
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传入,还是可以使用标准参数?我问是因为 Json 的条件可能是安全隐患(SQL 注入) 【参考方案1】:很遗憾,JSON 支持仅在 2016 版本的 SQL Server 中添加,还有改进的空间。处理包含数组的 JSON 数据相当繁琐,需要OPENJSON
获取数据,另一个OPENJSON
获取数组数据。
一个基于 SQL 的解决方案是可能的 - 但我写的 - 很麻烦。
首先,创建并填充示例表(请在您以后的问题中保存我们这一步):
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
的通用表表达式将json 条件转换为表格数据,并将该cte 加入到表中:
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"]
【讨论】:
以上是关于如何针对 JSON 列构建动态 SQL where 条件的主要内容,如果未能解决你的问题,请参考以下文章
从搜索表单动态构建 WHERE 子句时如何防止 SQL 注入?