尝试在模型上进行自连接,但我很困惑如何在 sqlmodel ORM 中执行此操作。相当于在 Postgresql 中使用以下 SQL 的东西。
SELECT tt.id, ttp.parent_name, tt.name
FROM target_table tt,
(select DISTINCT bb.parent_id, bbref.name parent_name
from target_table tt, target_table ttref
WHERE tt.parent_id is not null
AND tt.parent_id = ttref.id
order by 1) ttp
WHERE tt.parent_id = ttp.parent_id
ORDER BY 1
我有一个使用 sqlmodel ORM 构建的相应对象,例如:
class TargetTable(SQLModel, table=True):...
正常读/写工作正常。
with Session(engine) as session:
print(select(TargetTable))
for tt in session.exec(select(TargetTable)):
print(tt)
我现在需要执行一项操作,需要检索上面 SQL 中所述的一些数据。我不确定 - 相反,我不知道如何构建定义以及与第二个表的相应连接。
我正在尝试:
select(TargetTable).join(select(TargetTable.label('tt')).join(
(TargetTable.label('ttref').filter(tt.parent_id != NULL, tt.parent_id == ttref.id)
).label(ttp)).filter(tt.parent_id = ttp.parent_id)
但它不起作用,我对到底如何去做感到困惑。
感谢任何指点或解决方案。
这个问题没有任何特定于 SQLModel 的内容,您必须使用 SQLAlchemy 来执行此操作。这种方法会发出以下查询。
SELECT
tt.id,
ttp.parent_name,
tt.name
FROM
target_table AS tt
JOIN (
SELECT
DISTINCT tt.parent_id AS parent_id,
ttref.name AS parent_name
FROM
target_table AS tt
JOIN target_table AS ttref ON tt.parent_id IS NOT NULL
AND tt.parent_id = ttref.id
ORDER BY
1
) AS ttp ON tt.parent_id = ttp.parent_id
ORDER BY
1
这是一个完整的代码,其中在 SQLAlchemy 中定义了模型,但您应该能够复制粘贴查询部分并与 SQLModel 一起使用。
from sqlalchemy import ForeignKey, create_engine, null, select, text
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, aliased, mapped_column
class Base(DeclarativeBase):
pass
engine = create_engine("sqlite:///temp.db", echo=True)
class TargetTable(Base):
__tablename__ = "target_table"
id: Mapped[int] = mapped_column(primary_key=True)
parent_id: Mapped[int | None] = mapped_column(ForeignKey(id))
name: Mapped[str]
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(TargetTable(name="name 1"))
session.add(TargetTable(name="name 2", parent_id=1))
session.add(TargetTable(name="name 3", parent_id=2))
session.commit()
with Session(engine) as session:
tt = aliased(TargetTable, name="tt")
ttref = aliased(TargetTable, name="ttref")
ttp = (
select(tt.parent_id, ttref.name.label("parent_name"))
.distinct()
.join(ttref, (tt.parent_id != null()) & (tt.parent_id == ttref.id))
.order_by(text("1"))
.subquery()
.alias("ttp")
)
statement = (
select(tt.id, ttp.c.parent_name, tt.name)
.join(ttp, tt.parent_id == ttp.c.parent_id)
.order_by(text("1"))
)
for i in session.execute(statement):
print(i)
*** 更新:添加完整的非工作代码以响应@python_user 询问。我尝试遵循 python_user 的响应建议,将查询包含在此处的代码中。
### 2. ONLY USING SQLMODEL but with the query as advice by @python_user on SO - NOT WORKING!!
# below code block for hierarchy modeling was my attempt to use all sqlmodel class and properties
# did not go well
# ERROR:
# Traceback(most recent call last):
# File "/Users/tapas/PycharmProjects/ResiChainLab/test/checkmodel.py", line 38, in < module >
# ttp = alias((select(tt.parent_id, ttref.name.label('parent_name')).distinct().join(ttref, (tt.parent_id is not None)
# ^^^^^^^^^^^^
# AttributeError: 'Alias' object has no attribute 'parent_id'
###
from typing import Optional
from sqlmodel import SQLModel, create_engine, Session, select
from sqlmodel import Field, alias
engine = create_engine("sqlite:///cptest.db", echo=True)
class TargetTable(SQLModel, table=True):
__tablename__ = "target_table"
id: Optional[int] = Field(primary_key=True)
parent_id: int | None = Field(foreign_key='target_table.id')
name: str
SQLModel.metadata.create_all(bind=engine)
with Session(engine) as session:
session.add(TargetTable(name="name 1"))
session.add(TargetTable(name="name 2", parent_id=1))
session.add(TargetTable(name="name 3", parent_id=2))
session.commit()
with Session(engine) as session:
tt = alias(TargetTable, 'tt') # sqlmodel alias()
ttref = alias(TargetTable, 'ttref')
ttp = alias((select(tt.parent_id, ttref.name.label('parent_name'))
.distinct()
.join(ttref, (tt.parent_id is not None) & (tt.parent_id == ttref.id))
.subquery()), 'ttp', True
)
resultset = session.execute(select(tt.id, ttp.c.parent_name, tt.name)
.join(ttp, tt.parent_id == ttp.c.parent_id)
.order_by(tt.id)
)
for result in resultset:
print(result)
因此,我将上面的内容更改为仅包含 sqlalchemy 用于别名并指定 @python_users 答案中的 order_by 索引,现在它在大多数情况下都有效 - 除了由于某些原因,它似乎忽略了非空过滤器加入。
最新代码是:
#### FINAL - HYBRID & WORKING
# uses sqlalchemy for aliases and specifying order by indices with text()
from typing import Optional
from sqlmodel import SQLModel, create_engine, Session, select
from sqlmodel import Field
from sqlalchemy import text
from sqlalchemy.orm import aliased
engine = create_engine("sqlite:///cptest-2.db", echo=True)
class TargetTable(SQLModel, table=True):
__tablename__ = "target_table"
id: Optional[int] = Field(primary_key=True)
parent_id: int | None = Field(foreign_key='target_table.id')
name: str
SQLModel.metadata.create_all(bind=engine)
with Session(engine) as session:
session.add(TargetTable(name="name 1"))
session.add(TargetTable(name="name 2", parent_id=1))
session.add(TargetTable(name="name 3", parent_id=2))
session.commit()
with Session(engine) as session:
tt = aliased(TargetTable, name="tt")
ttref = aliased(TargetTable, name="ttref")
ttp = (select(tt.parent_id, ttref.name.label("parent_name"))
.distinct()
.filter(tt.parent_id == ttref.id, tt.parent_id is not None)
.order_by(text("1"))
.subquery()
.alias("ttp")
)
resultset = session.execute(select(tt.id, ttp.c.parent_name, tt.name)
.join(ttp, tt.parent_id == ttp.c.parent_id)
.order_by(text("1"))
)
for result in resultset:
print(result)