Postgres jsonb 查询动态值

Kar*_*mar 3 sql postgresql node.js sequelize.js typeorm

在 users 表中,我有一个 jsob 列experience,其 json 结构如下:

[
    {
        "field": "devops",
        "years": 9
    },
    {
        "field": "backend dev",
        "years": 7
    } 
... // could be N number of objects with different values
]
Run Code Online (Sandbox Code Playgroud)

业务需求

客户可以要求在任何领域有经验的人员以及在每个领域各自有多年的经验

这是一个示例查询

SELECT * FROM users
WHERE
jsonb_path_exists(experience, '$[*] ? (@.field == "devops" && @.years > 5)') and
jsonb_path_exists(experience, '$[*] ? (@.field == "backend dev" && @.years > 5)')
LIMIT 3;
Run Code Online (Sandbox Code Playgroud)

问题

假设我收到请求

[
  { field: "devops", years: 5 }, 
  { field: "java", years: 6 }, 
  { field: "ui/ux", years: 2 }] // and so on
Run Code Online (Sandbox Code Playgroud)

如何动态创建查询而不用担心 sql 注入?

技术堆栈

  • Nodejs
  • 打字稿
  • 类型ORM
  • Postgres

Erw*_*ter 6

指数

首先,你需要索引支持。我建议jsonb_path_ops像这样的索引:

CREATE INDEX users_experience_gin_idx ON users USING gin (experience jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

看:

询问

以及一个可以利用该索引的查询(100% 相当于您的原始索引):

CREATE INDEX users_experience_gin_idx ON users USING gin (experience jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

需要Postgres 12或更高版本,其中添加了 SQL/JSON 路径语言。

索引支持与 Postgres 中的运算符绑定。该运算@?符相当于jsonb_path_exists(). 看:

动态生成查询

SELECT *
FROM   users
WHERE  experience @? '$[*] ? (@.field == "devops" && @.years > 5 )'
AND    experience @? '$[*] ? (@.field == "backend dev" && @.years > 5)'
LIMIT  3;
Run Code Online (Sandbox Code Playgroud)

生成上述形式的查询:

SELECT 'SELECT * FROM users
WHERE  experience @? '
       || string_agg(quote_nullable(format('$[*] ? (@.field == %s && @.years > %s)'
                                         , f->'field'
                                         , f->'years')) || '::jsonpath'
                   , E'\nAND    experience @? ')
       || E'\nLIMIT  3'
FROM   jsonb_array_elements('[{"field": "devops", "years": 5 }, 
                              {"field": "java", "years": 6 }, 
                              {"field": "ui/ux", "years": 2 }]') f;
Run Code Online (Sandbox Code Playgroud)

全自动化

如何动态创建查询而不用担心 sql 注入?

将上面的查询生成放入 PL/pgSQL 函数中以动态执行:

SELECT * FROM users
WHERE  experience @? '$[*] ? (@.field == "devops" && @.years > 5)'::jsonpath
AND    experience @? '$[*] ? (@.field == "java" && @.years > 6)'::jsonpath
AND    experience @? '$[*] ? (@.field == "ui/ux" && @.years > 2)'::jsonpath
LIMIT  3;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT * FROM f_users_with_experience('[{"field": "devops", "years": 5 }, 
                                      , {"field": "backend dev", "years": 6}]');
Run Code Online (Sandbox Code Playgroud)

或者使用不同的LIMIT

SELECT * FROM f_users_with_experience('[{"field": "devops", "years": 5 }]', 123);
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

您应该熟悉 PL/pgSQL 来使用它并理解它。

SQL注入是不可能的,因为......

  1. 强制执行有效的 JSON 输入
  2. JSON 值与原始 JSON 双引号连接。
  3. 最重要的是,每个生成的jsonpath值都用单引号引起来quote_nullable()

在讨论 SQL/JSON 路径表达式主题时,我使用一个来断言有效输入:

jsonb_path_exists (_filter_arr, '$[*] ? (!exists(@.field) || !exists(@.years))')
Run Code Online (Sandbox Code Playgroud)

检查 JSON 数组中的每个对象以及两个必需键 ( field, years) 之一是否缺失。