web-dev-qa-db-ja.com

JSON列に特定の値が含まれているテーブルからすべての行を取得する

PostgreSQLデータベースのJSON列からデータを取得するのに苦労しています。 usersテーブルには、JSON列であるknown_ips列があり、次のように、指定されたユーザーに既知のIPアドレスのフラット配列を保持しています。

# select email, known_ips from users limit 3;
       email       |          known_ips                                     
-------------------+-------------------------------
 [email protected] | ["192.168.1.1","192.168.1.2"]
 [email protected] | ["192.168.1.3"]
 [email protected] | ["192.168.1.2"]
(3 rows)

私がやろうとしていることは、known_ips列のIPアドレスの配列(この例では、192.168.1.1または192.168.1.2)からIPアドレスを持つすべてのユーザーを選択することです。したがって、この場合、user1とuser3はIPアドレス.1または.2を持っているので、.3アドレスを探していないので、user2は返さず、user2は返さないようにします。

私は次のクエリを試しましたが、すべて役に立ちません:

  • 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...
                                                                 ^
    
  • 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....
    
  • 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...
                                                                 ^
    
  • 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...
                                                                 ^
    
  • 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...
                                                                 ^
    
  • 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...
                                                                 ^
    
  • 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...
                                                                 ^
    
  • 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...
                                                                 ^
    

明らかな何かが欠けているように感じますが、表示されません。なぜPostgresは文字列ではなく列を定義していると思いますか?データを適切に取得するにはどうすればよいですか?

6
Oldskool

二重引用符は名前の区切り文字です。それらは、非標準の文字を含む名前(または列名、テーブル名など)を区切るために予約されているか、明示的に大文字と小文字を区別する必要があるものです(PostgreSQLでも標準に準拠しているためです)。 。

したがって、"192.168.1.1"を使用したすべての試行が失敗するのはそのためです。PostgreSQLは、実際にそれらを名前(具体的には、各コンテキストの列名)として解釈します。

192.168.1.1が無効なトークンシーケンスであるため、引用符のない1つのケースは失敗します。数値と他のいくつかの定数は引用符なしでPostgreSQLで表すことができますが、そこで指定するトークンは数値または他の何かとして解釈できません。

最後に、PostgreSQLがそれらをJSONリテラルとして解釈しようとしているため、IPの周りに単一引用符を使用しているものは失敗します。どうして? ip列はjson型であるため、これはjson_array_elementsによって返される列値の型です。

したがって、2回目の試行を成功させるには、まずIPを有効なJSON文字列アイテムとして表す必要があります。つまり、次のように、それらを二重引用符で囲み、thenを単一引用符で囲む必要があります。

where ip in ('"192.168.1.1"','"192.168.1.2"')

しかし、それはあなたにこのエラーを与えます:

演算子が存在しません:json = json

オプションは次のとおりです。

  • iptextに変換します。

    where ip::text in ('"192.168.1.1"','"192.168.1.2"')
    
  • ipjsonbに変換

    where ip::jsonb in ('"192.168.1.1"','"192.168.1.2"')
    

どちらでもあなたはうまくいくはずです。

ただし、このようにデータをフィルタリングすると、出力が重複する可能性があることに注意してください。問題は、json_array_elements関数が指定されたjson値を行セットに変換し、転置された各項目についてソース行の列を繰り返すことです。したがって、例として、FROM句は次の行セットを効果的に生成します。

       email       |          known_ips            |      ip
-------------------+-------------------------------+---------------
 [email protected] | ["192.168.1.1","192.168.1.2"] | "192.168.1.1"
 [email protected] | ["192.168.1.1","192.168.1.2"] | "192.168.1.2"
 [email protected] | ["192.168.1.3"]               | "192.168.1.3"
 [email protected] | ["192.168.1.2"]               | "192.168.1.2"

User1の場合、各ipはIN述語と一致するため、対応する電子メールが2回返されます。

これを解決するには、これの代わりに:

...
from users, json_array_elements(known_ips) as ip
where ip::jsonb in ('"192.168.1.1"','"192.168.1.2"')

あなたはこのようなことをすることができます:

...
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"')
)
5
Andriy M

より良い解決策があることは知っていますが、このSQLは機能するはずです。

SELECT *
FROM users u
WHERE u.known_ips::TEXT LIKE '%192.168.1.2%'
2
Sahap Asci