如何针对 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"]';

(注意:我添加了另一个状态 - 再次确保条件正常工作。)

然后,我使用带有openjsoncross 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 条件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL 的 WHERE 子句中动态调用列

从搜索表单动态构建 WHERE 子句时如何防止 SQL 注入?

在 SQL 中动态构建 WHERE 子句,无需硬解析即可执行

如何动态构建LINQ

SQL查询动态列的方法

有没有比在开头使用 1=1 更好的方法来动态构建 SQL WHERE 子句?