【问题标题】:Is there a way to apply a conditional against a list of values in a jsonb object?有没有办法对 jsonb 对象中的值列表应用条件?
【发布时间】: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               |
+------------------+-----------------+

任何建议将不胜感激!

【问题讨论】:

    标签: sql postgresql kotlin


    【解决方案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 那个左连接代码有问题——很抱歉。我将其替换为可以处理缺少键条件的简化查询。
    猜你喜欢
    • 2017-02-03
    • 1970-01-01
    • 2019-06-21
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 2017-03-23
    • 1970-01-01
    • 2011-02-02
    相关资源
    最近更新 更多