有没有办法对 jsonb 对象中的值列表应用条件?

Posted

技术标签:

【中文标题】有没有办法对 jsonb 对象中的值列表应用条件?【英文标题】:Is there a way to apply a conditional against a list of values in a jsonb object? 【发布时间】:2020-09-18 20:07:20 【问题描述】:

目前,我在 PostgreSQL 表中有一个 jsonb 列,它存储格式为 json 的对象


  "subscriptions": 
    "subscription1": 
      "subscribed": boolean
    ,
    "subscription2": 
      "subscribed": boolean
    
  

可以有任意数量的订阅1/2/等。在文档中,都具有不同的名称。我正在尝试找出文档是否包含我拥有的每种订阅类型的 subscriptions.subscription1.subscribed == true 等。

我有一个我需要查询的订阅密钥列表,所以我的第一反应是遍历它们并查询数据库以获得这些计数:

subscriptionsKeys.forEach  subscription ->
    // find number of users where `subscription` == true
    val queryResult = repository.getNumberOfUsersSubscribed(subscription)

    // queryResult contains the number of users subscribed to `subscription`

其中 getNumberOfUsersSubscribed() 定义为:

@SqlQuery(
    """
    SELECT count(*) as number_of_users FROM table
    WHERE jsonb_extract_path_text(body,'subscriptions',:subscriptionKey,'subscribed') = 'true'
    """
)
fun getNumberOfUsersSubscribedToOptOutList(
    subscriptionKey: String
): Int

这很好,但是对于大量订阅,这意味着我将在表上执行 count(*) n 次,在我们的生产环境中超过 60 次查询每个都处理超过 4500 万条记录。

我希望有某种方法可以构造一个 SQL/jsonb 查询,允许我传入一个键列表/数组 ['subscription1','subscription2',etc] 将应用上述 = true 条件针对每一行的输入列表,然后以以下示例格式返回结果:

+------------------------------------+
| subscription_key | number_of_users |
+------------------+-----------------+
| subscription1    | 6               |
| subscription2    | 59              |
| etc.             | n               |
+------------------+-----------------+

任何建议将不胜感激!

【问题讨论】:

【参考方案1】:

只要您的库允许您将数组传递到 text[] 占位符,那么这应该可以工作。虽然它不会特别快,但它应该比您已经使用的方法更快。

with yourtable (id, body) as (
  values (1, '
  "subscriptions": 
    "subscription1": 
      "subscribed": true
    ,
    "subscription2": 
      "subscribed": true
    
  
'::jsonb)
), search_terms as (
  select * 
    from unnest(array['subscription1', 'subscription2']) as st(term)
)
select st.term, count(*) 
  from yourtable y
       cross join lateral jsonb_each(y.body->'subscriptions') as b(k, v)
       join search_terms st 
         on st.term = b.k
        and b.v->>'subscribed' = 'true'
 group by st.term
;

┌───────────────┬───────┐
│     term      │ count │
├───────────────┼───────┤
│ subscription1 │     1 │
│ subscription2 │     1 │
└───────────────┴───────┘
(2 rows)

根据您的评论,如果您想将未明确将subscribed 显示为false 的订阅计数:

with yourtable (id, body) as (
  values (1, '
  "subscriptions": 
    "subscription1": 
      "subscribed": true
    ,
    "subscription2": 
      "subscribed": true
    ,
    "subscription3": 
      "subscribed": false
    
  
'::jsonb),
        (2, '
  "subscriptions": 
    "subscription1": 
      "subscribed": true
    ,
    "subscription2": 
      "subscribed": true
    ,
    "subscription3": 
      "subscribed": false
    
  
'::jsonb),
        (3, ''::jsonb)
), search_terms as (
  select * 
    from unnest(array['subscription1', 'subscription2', 'subscription3', 'subscription4']) as st(term)
)
select st.term, 
       count(*) 
         filter (where 
           coalesce(
             body->'subscriptions'
               ->st.term 
               ->>'subscribed', ''
           ) != 'false')
  from yourtable t
       cross join search_terms st
 group by st.term
 order by st.term
;

┌───────────────┬───────┐
│     term      │ count │
├───────────────┼───────┤
│ subscription1 │     3 │
│ subscription2 │     3 │
│ subscription3 │     1 │
│ subscription4 │     3 │
└───────────────┴───────┘
(4 rows)

【讨论】:

太棒了!这似乎工作得很好。我很好奇您是否还知道如何修改它,文档中缺少的订阅 1/订阅 2 也适用于计数?换句话说,如果subscription1.subscribed = truesubscription1 完全丢失,两者都应该添加到计数中吗?我正在尝试自己弄清楚,但是这种级别的 SQL 对我来说有点陌生。感谢您的回答! 我尝试了编辑,我发现了两个问题:首先,当表中有一条记录时检查 null 有效,如果添加第二条记录(或第三条等),则 null计数永远不会增加。例如,如果您复制 valuesid=2 并运行查询,subscription4 仍将是 1(它应该是 2)。其次,如果 subscriptions 节点完全丢失(因此 jsonb 的示例值将只是 ,这不会被视为真实/计数。我正在尝试自己修复它,但是如果你想帮助我也很感激! @Josh 那个左连接代码有问题——很抱歉。我将其替换为可以处理缺少键条件的简化查询。

以上是关于有没有办法对 jsonb 对象中的值列表应用条件?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle APEX 中的条件值列表?

在 WHERE IN 子句中使用 JSONB 数组中的值

如何为 postgresql 中的唯一(不包括顺序)JSONB 列创建约束

PostgreSQL JsonB根据对象属性查询JSON数组中的对象

有没有办法在条件下使用多种数据类型过滤Python中的列?

是否可以将 Hibernate 与 PostgreSql 的 JSONB 数据类型一起使用?