How to SELECT using JSON for WHERE-clause #2023
-
First Check
Example Codeimport uuid
from typing import Any
from uuid import UUID
from sqlalchemy import JSON, create_engine, text
from sqlalchemy.ext.mutable import MutableDict
from sqlmodel import Field, Session, SQLModel, select
class Foo(SQLModel, table=True):
id: UUID = Field(primary_key=True, default_factory=uuid.uuid4)
foo: str
meta: dict[str, Any] = Field(sa_type=MutableDict.as_mutable(JSON))
engine = create_engine("sqlite:///:memory:", echo=True)
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(
Foo(
foo="Bar",
meta={"hello": "world", "foo": {"bar": 42}},
)
)
session.commit()
# Ensure row is available
foo = session.exec(select(Foo)).first()
print(">>> first row:", foo)
# Query by JSON-Content
foo = session.exec(select(Foo).where(Foo.meta["hello"] == "world")).first()
print(">>> select SQLModel:", foo)
# Query like created by the expression above, doesn't work as well
foo = session.execute(text("SELECT foo.id, foo.foo, foo.meta FROM foo "
"WHERE JSON_QUOTE(JSON_EXTRACT(foo.meta, '$.\"hello\"')) = 'world'")).first()
print(">>> select text:", foo)
# removed JSON_QUOTE - now it works!
foo = session.execute(text("SELECT foo.id, foo.foo, foo.meta FROM foo "
"WHERE JSON_EXTRACT(foo.meta, '$.\"hello\"') = 'world'")).first()
print(">>> select text without JSON_QUOTE:", foo)DescriptionI want to use a JSON-Column in my SELECT's WHERE, but the select seems to find no result. session.exec(select(Foo).where(Foo.meta["hello"] == "world")).first() # => NoneAlthough the JSON-Data is contained in that row: session.exec(select(Foo)).first() # => meta={'hello': 'world', 'foo': {'bar': 42}} id=UUID('3511f27f-266e-42e5-a200-e80a8abef4e9') foo='Bar'the generated query seems kind of correct, althought the JSON_QUOTE seems to prevent it from working. if I remove this call, the query works as expected. Another workaround i found, is to add session.exec(select(Foo).where(Foo.meta["hello"].as_string() == "world")).first()Thanks for clarification and maybe updating docs (or ideally fixing the code, so that my original attemt just works 😉). Operating SystemWindows Operating System DetailsNo response Project Version0.0.39 Python VersionNo response Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
|
When you write
session.exec(select(Foo).where(Foo.meta["hello"].as_string() == "world")).first()There's a matching accessor for each type when you need it: So nothing's broken, and the docs do cover it, just tucked into the JSON type section rather than the SQLModel guide. The type-cast accessors are the intended API precisely because a raw |
Beta Was this translation helpful? Give feedback.
.as_string()is the right call here, not a workaround. It's the documented SQLAlchemy way and your original attempt is doing something subtly different than you'd expect.When you write
Foo.meta["hello"], the indexed access gives you back a value that's still typed as JSON, not as text. SoFoo.meta["hello"] == "world"is a JSON-to-JSON comparison. On SQLite that renders asJSON_QUOTE(JSON_EXTRACT(meta, '$."hello"')), andJSON_QUOTEwraps the value in quotes, so you're really comparing the string"world"(quotes included) against your bare'world'. They never match, which is why you getNone..as_string()tells SQLAlchemy to pull the element out as text instead of JSON, so it drops theJSO…