Given a table with the following schema:
create table json_data (
id integer PRIMARY KEY NOT NULL,
default_object VARCHAR(10) NOT NULL,
data jsonb NOT NULL
);
For each of entity in the table I want to retrieve value of data['first']['name']
field, or if it's null value of data[json_data.default_object]['name']
, or if the latter is also null then return some default value. In "pure" SQL I can write the following code to satisfy my needs:
insert into
json_data(
id,
default_object,
data
)
values(
0,
'default',
'{"first": {"name": "first_name_1"}, "default": {"name": "default_name_1"}}'
),
(
1,
'default',
'{"first": {}, "default": {"name": "default_name_2"}}'
);
select
id,
coalesce(
json_data.data -> 'first' ->> 'name',
json_data.data -> json_data.default_object ->> 'name',
'default_value'
) as value
from
json_data;
I tried to "translate" the "model" above into an SQLAlchemy entity:
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
class JsonObject(Base):
__tablename__ = 'json_data'
id = sa.Column(sa.Integer, primary_key=True)
default_object = sa.Column(sa.String(10), nullable=False)
data = sa.Column(postgresql.JSONB, nullable=False)
@hybrid_property
def name(self) -> str:
obj = self.data.get('first')
default_obj = self.data.get(self.default_object)
return (obj.get('name') if obj else default_obj.get('name')) or default_obj.get('name')
@name.setter
def name(self, value: str):
obj = self.data.setdefault('first', dict())
obj['name'] = value
@name.expression
def name(self):
return sa.func.coalesce(
self.data[('first', 'name')].astext,
self.data[(self.default_object, 'name')].astext,
'default_value',
)
But it seems that expression for the name
hybrid property doesn't work as I expect. If I query entities by name
property, like:
query = session.query(JsonObject).filter(JsonObject.name == 'name')
The query
is expanded by SQLAlchemy into a something like this:
SELECT json_data.id AS json_data_id, json_data.default_object AS json_data_default_object, json_data.data AS json_data_data
FROM json_data
WHERE coalesce((json_data.data #> %(data_1)s), (json_data.data #> %(data_2)s), %(coalesce_1)s) = %(coalesce_2)s
It uses path operator instead of index operator. What should I do to make SQLAlchemy create an expression such as I wrote in the beginning of the question?
Ok, the solution I found is quite straightforward. As SQLAlchemy documentation tells:
Index operations return an expression object whose type defaults to JSON by default, so that further JSON-oriented instructions may be called upon the result type.
Therefore we can use "chained" python indexing operators. So the following code looks legit to me:
class JsonObject(Base):
# Almost the same stuff, except for the following:
@name.expression
def name(self):
return sa.func.coalesce(
self.data['first']['name'].astext,
self.data[self.default_object]['name'].astext,
'default_value',
)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.