简体   繁体   English

sqlalchemy orm | fastAPI查询+三表联结获取父子所有

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

I'm trying to make have a route in my fastAPI that gives back a list of all parents.我正在尝试在我的 fastAPI 中创建一条返回所有父母列表的路线。 portfolios and all the children or stocks that are associated with each of them PLUS the extra data that is in the association table (for that relationship). portfolios以及与它们每个相关联的所有子项或stocks加上关联表中的额外数据(对于该关系)。

The response is suppose to look somewhat like this响应应该看起来有点像这样

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

Right now the route that produces this looks like this:现在产生这个的路线看起来像这样:

@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

But this gives me the wrong result.但这给了我错误的结果。

This results in this.结果是这样的。

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

So I get data from the association table back instead of from the children.所以我从关联表而不是从孩子那里取回数据。

The models I have are here.我的模型在这里。

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")

Let me know if you need more information.如果您需要更多信息,请与我们联系。 I appreciate your help.我感谢您的帮助。

I find it to be easier to give the association some name of its own because it is confusing but in this case Portfolio.stocks is actually a list of the association objects and NOT actual stocks.我发现给协会起一个自己的名字会更容易,因为这很容易混淆,但在这种情况下, Portfolio.stocks实际上是一个协会对象的列表,而不是实际的股票。 You have to get those off the association object. In my example below I go and get stock with assoc.stock.id .您必须从协会 object 中获取这些信息。在我下面的示例中,我使用 go 并使用assoc.stock.id获取股票。 That should not trigger another query because we used joinedload to pre-load it.那不应该触发另一个查询,因为我们使用 joinedload 来预加载它。 If the stock had a name we'd reference it with assoc.stock.name .如果股票有名称,我们将使用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}")

The query looks something like this:查询看起来像这样:

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

For anybody that is looking for an answer, here is how I fixed it.对于任何正在寻找答案的人,这就是我修复它的方法。

I used the query from Ian that he mentioned above, (thank you a ton for that).我使用了上面提到的 Ian 的查询(非常感谢)。 And then I just manually declared the structure I wanted to have.然后我只是手动声明了我想要的结构。

The whole code looks like this整个代码看起来像这样

    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

What I'm doing here is firstly declare an empty list where our final results will be stored and which will be returned.我在这里所做的是首先声明一个空列表,我们的最终结果将存储在其中并将返回。 Then we iterate over the query (because the query gives us a list back).然后我们遍历查询(因为查询给了我们一个列表)。 So we have each "SQL alchemy Model" now as portfolio in there.因此,我们现在将每个“SQL 炼金术模型”作为portfolio放在其中。

Then we can convert this into a dictionary and assign it a new variable with result_dict = portfolio.__dict__ The __dict__ method converts the model into a Python dictionary that you can work with easily.然后我们可以将其转换为字典并为其分配一个新变量result_dict = portfolio.__dict__ __dict__方法将 model 转换为您可以轻松使用的 Python 字典。

Since that result_dict contains a list of PortfolioStock models which is the association table model in. These are stored in the stocks key we have to iterate over them to get those values as well.由于result_dict包含一个PortfolioStock models列表,它是关联表 model 中的。这些存储在stocks键中,我们必须迭代它们以获取这些值。 And here we just repeat the process.在这里我们只是重复这个过程。

We convert the model into a dictionary with __dict__ then make a new empty dictionary temp_sto={} and set it equal to the stock key which is the key that is linking the child to our association table.我们将 model 转换为带有__dict__的字典,然后创建一个新的空字典temp_sto={}并将其设置为等于stock键,该键是将子项链接到我们的关联表的键。 So now we have the child or stock we want to access.所以现在我们有了我们想要访问的孩子或stock We can simply set our new empty dicitonary equal to that so we inherit all the information contained within.我们可以简单地将新的空字典设置为等于那个,这样我们就可以继承其中包含的所有信息。 And then we just have to add all other information from the association table that we might want which can be accessed via the dictionary we defined at the beginning of the for loop sto_dict .然后我们只需要添加我们可能需要的关联表中的所有其他信息,这些信息可以通过我们在 for 循环sto_dict开头定义的字典访问。

Once we have this we append it to an empty list we have defined outside of this for loop but inside the portfolio loop .一旦我们有了这个,我们就把它 append 到一个我们在这个 for 循环之外但在portfolio loop内部定义的空列表。 Set the result_dict["stocks"] key (so basically the key where you want all children to be contained in) equal to that list we just appended all the dictionaries to and then append that dictionary to our result_list .result_dict["stocks"]键(所以基本上是您希望所有子项都包含在其中的键)设置为等于我们刚刚将所有字典附加到的列表,然后将 append 该字典添加到我们的result_list And last thing to do is just to return that list, and we're done.最后要做的就是返回该列表,我们就完成了。

I have provided an agnostic approach hopefully down below我在下面提供了一种不可知论的方法


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

I hope this helps you if you are in a similar situation.如果您处于类似情况,我希望这对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM