从表中获取 JSON 列包含特定值的所有行

Old*_*ool 6 postgresql syntax json

我正在努力从我的 PostgreSQL 数据库中的 JSON 列中获取数据。在我们的users表中,我们有一known_ips列,它是一个 JSON 列,保存给定用户已知的 IP 地址的平面数组,如下所示:

# select email, known_ips from users limit 3;
       email       |          known_ips                                     
-------------------+-------------------------------
 user1@example.com | ["192.168.1.1","192.168.1.2"]
 user2@example.com | ["192.168.1.3"]
 user3@example.com | ["192.168.1.2"]
(3 rows)
Run Code Online (Sandbox Code Playgroud)

我想要做的是从其列中的一组 IP 地址(在本例中为 192.168.1.1 或 192.168.1.2)中选择具有 IP 地址的所有用户known_ips。因此,在这种情况下,user1 和 user3 的 IP 地址为 .1 或 .2,因此我希望返回它们,而不是 user2,因为我不是在寻找 .3 地址。

我尝试了以下查询,但都无济于事:

  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in("192.168.1.1","192.168.1.2");
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ... json_array_elements(known_ips) as ip where ip in("192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in('192.168.1.1','192.168.1.2') limit 5;
    ERROR:  invalid input syntax for type json
    LINE 1: ... json_array_elements(known_ips) as ip where ip in('192.168.1...
                                                                 ^
    DETAIL:  Token "." is invalid.
    CONTEXT:  JSON data, line 1: 192.168....
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in(192.168.1.1,192.168.1.2) limit 5;
    ERROR:  syntax error at or near ".168"
    LINE 1: ...array_elements(known_ips) as ip where ip in(192.168.1.1,192...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in("192.168.1.1","192.168.1.2");
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ... json_array_elements(known_ips) as ip where ip in("192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in(array["192.168.1.1","192.168.1.2"]);
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ...array_elements(known_ips) as ip where ip in(array["192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip = any(array["192.168.1.1","192.168.1.2"]);
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ...ay_elements(known_ips) as ip where ip = any(array["192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip in(unnest(array["192.168.1.1","192.168.1.2"]));
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ...lements(known_ips) as ip where ip in(unnest(array["192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)
  • database=# select email, ip from users, json_array_elements(known_ips) as ip where ip = any(array["192.168.1.1","192.168.1.2"]);
    ERROR:  column "192.168.1.1" does not exist
    LINE 1: ...ay_elements(known_ips) as ip where ip = any(array["192.168.1...
                                                                 ^
    
    Run Code Online (Sandbox Code Playgroud)

我觉得我错过了一些明显的东西,但我只是没有看到。为什么 Postgres 认为我定义的是列而不是字符串?如何正确获取数据?

And*_*y M 5

双引号是名称分隔符。它们被保留用于分隔包含非标准字符的名称(列名、表名等)或那些您想明确区分大小写的名称(因为这是它们在 PostgreSQL 中的效果,这也符合标准) .

所以,这就是为什么所有尝试使用 "192.168.1.1"失败的原因:PostgreSQL 确实将它们解释为名称(特别是每个上下文中的列名称)。

没有引号的一种情况仅因为192.168.1.1无效的标记序列而失败。数字和其他一些常量可以在 PostgreSQL 中表示而不用引号,但您在那里指定的标记不能解释为数字或其他任何东西。

最后,在 IP 周围使用单引号的方法失败了,因为 PostgreSQL 试图将它们解释为 JSON 文字。为什么?因为该ip列属于类型json– 即由 返回的列值的类型json_array_elements

因此,为了使您的第二次尝试成功,您应该首先将 IP 表示为有效的 JSON 字符串项。这意味着您需要将它们用双引号括起来,然后用单引号括起来,如下所示:

where ip in ('"192.168.1.1"','"192.168.1.2"')
Run Code Online (Sandbox Code Playgroud)

但是,这会给你这个错误:

运算符不存在:json = json

您的选择是:

要么应该让你去。

但请注意,像这样过滤数据可能会导致输出重复。问题是,该json_array_elements函数将指定json值转换为行集,为每个转置项重复源行的列。因此,对于您的示例,FROM 子句有效地生成以下行集:

       email       |          known_ips            |      ip
-------------------+-------------------------------+---------------
 user1@example.com | ["192.168.1.1","192.168.1.2"] | "192.168.1.1"
 user1@example.com | ["192.168.1.1","192.168.1.2"] | "192.168.1.2"
 user2@example.com | ["192.168.1.3"]               | "192.168.1.3"
 user3@example.com | ["192.168.1.2"]               | "192.168.1.2"
Run Code Online (Sandbox Code Playgroud)

因为对于 user1 每个 ip将匹配 IN 谓词,因此您将收到两次返回的相应电子邮件。

要解决这个问题,而不是这样:

...
from users, json_array_elements(known_ips) as ip
where ip::jsonb in ('"192.168.1.1"','"192.168.1.2"')
Run Code Online (Sandbox Code Playgroud)

你可以这样做:

...
from users
where exists
(
  select *
  from json_array_elements(known_ips) as ip
  where ip::jsonb in ('"192.168.1.1"','"192.168.1.2"')
)
Run Code Online (Sandbox Code Playgroud)