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は文字列ではなく列を定義していると思いますか?データを適切に取得するにはどうすればよいですか?
二重引用符は名前の区切り文字です。それらは、非標準の文字を含む名前(または列名、テーブル名など)を区切るために予約されているか、明示的に大文字と小文字を区別する必要があるものです(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
オプションは次のとおりです。
ip
をtext
に変換します。
where ip::text in ('"192.168.1.1"','"192.168.1.2"')
ip
をjsonb
に変換
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"')
)
より良い解決策があることは知っていますが、このSQLは機能するはずです。
SELECT *
FROM users u
WHERE u.known_ips::TEXT LIKE '%192.168.1.2%'