sqlalchemy orm | fastAPI查询+连接三个表得到parents和所有children

sqlalchemy orm | fastAPI querying + joining three tables to get parents and all children

我试图在我的 fastAPI 中创建一个路由,该路由返回所有 parents.portfolios 和所有 children 或 stocks 的列表与它们中的每一个相关联加上关联中的额外数据table(对于该关系)。

响应应该看起来有点像这样

[ { "parrent1_attr1": bla,
    "parrent1_attr2": bla,
    "children": [ {
        "child1_attr1": bla,
        "child1_attr2": bla},
        {"child2_attr1": bla,
         "child2_attr2": bla}]
},
etc...]

现在产生这个的路线看起来像这样:

@router.get("/")
def get_all_portfolios(db: Session = Depends(get_db), current_user: int = Depends(oauth2.get_current_user)):

    results = db.query(models.Portfolio).options(joinedload(models.Portfolio.stocks)).all()
    return results

但这给了我错误的结果。

这就是结果。

[ { "parrent1_attr1": bla,
    "parrent1_attr2": bla,
    "children": [ {
          "association_table_attr1": bla
           "association_table_attr2": bla},]

所以我从关联 table 中获取数据,而不是从 children 中获取数据。

我手头的模型都在这里

class Portfolio(Base):
    __tablename__ = "portfolios"

    id = Column(Integer, primary_key=True, nullable=False)
    ...
    stocks = relationship("PortfolioStock", back_populates="portfolio")

class Stock(Base):
    __tablename__ = "stocks"

    id = Column(Integer, primary_key=True, nullable=False)
    ...
    portfolios = relationship("PortfolioStock", back_populates="stock")

class PortfolioStock(Base):
    __tablename__ = "portfolio_stocks"
    id = Column(Integer, primary_key=True)
    stock_id = Column(Integer, ForeignKey("stocks.id", ondelete="CASCADE"))
    portfolio_id = Column(Integer, ForeignKey("portfolios.id", ondelete="CASCADE"))
    count = Column(Integer, nullable=True)
    buy_in = Column(Float, nullable=True)
    stock = relationship("Stock", back_populates="portfolios")
    portfolio = relationship("Portfolio", back_populates="stocks")

如果您需要更多信息,请告诉我。感谢您的帮助。

我发现给协会起一个自己的名字会更容易,因为这很容易混淆,但在这种情况下 Portfolio.stocks 实际上是协会对象的列表,而不是实际的股票。您必须将它们从关联对象中移除。在我下面的示例中,我去 assoc.stock.id 购买股票。那不应该触发另一个查询,因为我们使用 joinedload 来 pre-load 它。如果股票有名称,我们会用 assoc.stock.name.

来引用它
with Session(engine) as session:
    q = session.query(Portfolio).options(joinedload(Portfolio.stocks).joinedload(PortfolioStock.stock))
    for portfolio in q.all():
        print (f"Listing associated stocks for portfolio {portfolio.id}")
        for assoc in portfolio.stocks:
            print (f"    Buy in {assoc.buy_in}, count {assoc.count} and stock id {assoc.stock.id}")

查询看起来像这样:

SELECT portfolios.id AS portfolios_id, stocks_1.id AS stocks_1_id, portfolio_stocks_1.id AS portfolio_stocks_1_id, portfolio_stocks_1.stock_id AS portfolio_stocks_1_stock_id, portfolio_stocks_1.portfolio_id AS portfolio_stocks_1_portfolio_id, portfolio_stocks_1.count AS portfolio_stocks_1_count, portfolio_stocks_1.buy_in AS portfolio_stocks_1_buy_in 
FROM portfolios LEFT OUTER JOIN portfolio_stocks AS portfolio_stocks_1 ON portfolios.id = portfolio_stocks_1.portfolio_id LEFT OUTER JOIN stocks AS stocks_1 ON stocks_1.id = portfolio_stocks_1.stock_id

对于任何正在寻找答案的人,这是我修复它的方法。

我使用了上面提到的 Ian 的查询(非常感谢)。 然后我只是手动声明了我想要的结构。

整个代码如下所示

    results = (
        db.query(models.Portfolio)
        .options(joinedload(models.Portfolio.stocks).joinedload(models.PortfolioStock.stock))
        .all()
    )
    result_list = []
    for portfolio in results:
        result_dict = portfolio.__dict__
        stock_list = []
        for sto in result_dict["stocks"]:
            sto_dict = sto.__dict__
            temp_sto = {}
            temp_sto = sto_dict["stock"]
            setattr(temp_sto, "buy_in", sto_dict["buy_in"])
            setattr(temp_sto, "count", sto_dict["count"])
            stock_list.append(temp_sto)
        result_dict["stocks"] = stock_list
        result_list.append(result_dict)
    return result_list

我在这里所做的是首先声明一个空列表,其中将存储我们的最终结果,并将 returned。 然后我们遍历查询(因为查询给了我们一个列表)。 所以我们现在有每个“SQL 炼金术模型”作为 portfolio

然后我们可以将其转换为字典并使用 result_dict = portfolio.__dict__ 为其分配一个新变量 __dict__ 方法将模型转换为您可以轻松使用的 Python 字典。

因为 result_dict 包含 PortfolioStock models 的列表,它是关联 table 模型。这些存储在 stocks 键中,我们必须迭代它们也获得这些价值。而这里我们只是重复这个过程。

我们使用 __dict__ 将模型转换为字典,然后制作一个新的空字典 temp_sto={} 并将其设置为等于 stock 键,该键是链接 child 加入我们的协会 table。所以现在我们有了要访问的 child 或 stock。我们可以简单地将新的空字典设置为等于那个,这样我们就可以继承其中包含的所有信息。 然后我们只需要添加我们可能想要的关联 table 中的所有其他信息,这些信息可以通过我们在 for 循环 sto_dict.

开头定义的字典访问

一旦我们有了这个,我们就将它附加到我们在这个 for 循环之外但在 portfolio loop 内定义的空列表中。 将 result_dict["stocks"] 键(所以基本上是你希望所有 children 包含在其中的键)等于我们刚刚将所有字典附加到的列表,然后将该字典附加到我们的 result_list. 最后要做的就是 return 该列表,我们就完成了。

我在下面提供了一种不可知论的方法


query = db.query(Parent).options(joinedload(Parent.relationship).joinedload(AssociationTable.child).all()
result_list = []
for parent in query:
    parent_dict = parent.__dict__
    child_list = []
    for association in parent_dict["relationship_name"]:
        association_dict = association.__dict__
        temp_child_dict = {}
        temp_child_dict = association_dict["child"]
        setattr(temp_child_dict, "name_you_want", association_dict["key_of_value_you_want"])
        # repeat as many times as you need
        child_list.append(temp_child_dict)
    parent_dict["children"] = child_list
    result_list.append(parent_dict)
return result_list

希望对遇到类似情况的你有所帮助。