Reputation: 3608
I cannot get the SQLAlchemy's has_any()
function to work. I simply want to filter my result on a list contained in a jsonb column:
db.session.query(models.Record.id).\
filter(models.Record.record_metadata["teams"].has_any(team_ids))
models.Record.record_metadata["teams"]
being a list of ids in a jsonb column (record_metadata
) that i want to compare with team_ids
that is also a list of ids
The query generated is :
SELECT record.id AS record_id
FROM record
WHERE ((record.record_metadata -> 'teams')) ?| '["id1", "id2"]';
That gives the error
DataError: (psycopg2.DataError) malformed array literal: "["id1", "id2"]"
LINE 3: WHERE ((record.record_metadata -> 'teams')) ?| '["id1", "id2"]'
^
DETAIL: "[" must introduce explicitly-specified array dimensions.
But regarding Table 9.44. Additional jsonb Operators on Postgres official documentation, the |?
operator should be followed by an array[]
Indeed, when i manually write the query :
SELECT record.id AS record_id
FROM record
WHERE ((record.record_metadata -> 'teams')) ?| array['id1', 'id2'];
It works just fine.
SQLAlchemy's documentation about this function is pretty poor and I can't find any examples.
So is it me doing something wrong with this function? Should I use something else for this case? Or is it has_any()
that is broken?
EDIT :
SELECT record.id AS record_id
FROM record
WHERE ((record.record_metadata -> 'teams')) ?| '{"id1", "id2"}';
Works fine as well, and I found out that has_any()
accepts strings as well.
So it s a (very) nasty fix, but if I format a string with the desired ids :
db.session.query(models.Record.id).\
filter(models.Record.record_metadata["teams"].has_any(
str(team_ids).
replace('[', '{').
replace(']', '}').
replace('\'', '\"')))
It works...
Upvotes: 3
Views: 2028
Reputation: 52949
It'd seem that by default the passed value is JSON encoded, if it is a list
or a dict
. You can work around that by passing an explicit array
literal:
In [15]: from sqlalchemy.dialects.postgresql import array
In [16]: session.query(Foo).\
...: filter(Foo.data['test'].has_any(array(['1', '2']))).all()
2018-06-18 09:07:20,780 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-06-18 09:07:20,780 INFO sqlalchemy.engine.base.Engine SELECT foo.id AS foo_id, foo.data AS foo_data
FROM foo
WHERE ((foo.data -> %(data_1)s)) ?| ARRAY[%(param_1)s, %(param_2)s]
2018-06-18 09:07:20,781 INFO sqlalchemy.engine.base.Engine {'data_1': 'test', 'param_1': '1', 'param_2': '2'}
Out[16]: []
This way you do not need to manually format the array literal from the string representation of a Python list
, which is probably rather error prone.
The error itself is the result of Postgresql trying to parse the passed literal – containing JSON – as an array. As the error states, the text representation of an array may start with a dimension decoration that specifies the array's subscript ranges:
'[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[]
Upvotes: 4