7

I'm trying to delete records based on a query that includes joins to a couple of aliased tables.

Here are the tables in question:

class Match(Base):
    
    id_ = Column(Integer, primary_key=True)
    tournament_id = Column(Integer, ForeignKey("myschema.tournament.id_"))
    round_id = Column(TINYINT, index=True)
    player_id_p1 = Column(Integer, ForeignKey("myschema.player.id_"))
    player_id_p2 = Column(Integer, ForeignKey("myschema.player.id_"))

    p1 = relationship("Player", foreign_keys=[player_id_p1])
    p2 = relationship("Player", foreign_keys=[player_id_p2])


class Tournament(Base):

    id_ = Column(Integer, primary_key=True)
    original_id = Column(Integer, index=True)
    tour_id = Column(TINYINT, index=True)

    match = relationship("Match", backref="tournament")


class Player(Base):

    id_ = Column(Integer, primary_key=True)
    original_id = Column(Integer, index=True)
    tour_id = Column(TINYINT, index=True)

    match = relationship(
        'Match',
        primaryjoin=("or_(Player.id_ == Match.player_id_p1, Player.id_ == Match.player_id_p2)"),
        overlaps="p1, p2",
    )

Worth mentioning that these tables are populated from a third party database which has tournaments, players and matches from two tennis tours; the ATP and WTA. In that database each tour has separate tournament, player and match tables. I've imported them into combined tables in my database and used a tour_id field to identify which tour/table they originally came from. I need the ability to delete records from the Match table based upon the original tournament and player ids.

I first tried this query:

p1 = sa.orm.aliased(Player)
p2 = sa.orm.aliased(Player)
stmt = sa.delete(Match)
stmt = stmt.join(Tournament)
stmt = stmt.join(p1, p1.id_ == Match.player_id_p1)
stmt = stmt.join(p2, p2.id_ == Match.player_id_p2)
stmt = stmt.where(
    Tournament.tour_id == tour_id,
    Tournament.original_id == 16907,
    p1.tour_id == tour_id,
    p1.original_id == 79810,
    p2.tour_id == tour_id,
    p2.original_id == 37136,
    Match.round_id == 5,
)
session.execute(stmt)

However, I got the error:

'Delete' object has no attribute 'join'

This related answer states that in the 1.x syntax then SA will take the tables from within filter and convert to USING in SQL. From this I built the following query in 2.0 syntax:

p1 = sa.orm.aliased(Player)
p2 = sa.orm.aliased(Player)
stmt = sa.delete(Match)
stmt = stmt.where(
    Tournament.tour_id == 0,
    Tournament.original_id == 16907,
    p1.tour_id == 0,
    p1.original_id == 79810
    p2.tour_id == 0,
    p2.original_id == 37136,
    Match.round_id == 5,
)
session.execute(stmt)

However, I then get the error:

Exception has occurred: InvalidRequestError       (note: full exception trace is shown but execution is paused at: <module>)
Could not evaluate current criteria in Python: "Can't evaluate criteria against alternate class <class 'Tournament'>". Specify 'fetch' or False for the synchronize_session execution option.

I wasn't sure what effect the recommended action would have so I also adapted the solution here as follows:

p1 = sa.orm.aliased(Player)
p2 = sa.orm.aliased(Player)
s_qry = sa.select(Match.id_)
s_qry = s_qry.join(Tournament)
s_qry = s_qry.join(p1, p1.id_ == Match.player_id_p1)
s_qry = s_qry.join(p2, p2.id_ == Match.player_id_p2)
s_qry = s_qry.where(
    Tournament.tour_id == tour_id,
    Tournament.original_id == 16907,
    p1.tour_id == tour_id,
    p1.original_id == 79810,
    p2.tour_id == tour_id,
    p2.original_id == 37136,
    Match.round_id == 5,
)
s_qry = s_qry.subquery()
stmt = sa.delete(Match).where(Match.id_.in_(s_qry))
session.execute(stmt)

But I now get the error:

Exception has occurred: InvalidRequestError       (note: full exception trace is shown but execution is paused at: <module>)
Could not evaluate current criteria in Python: "Cannot evaluate Select". Specify 'fetch' or False for the synchronize_session execution option.

It might be worth mentioning that in this instance there is no record that corresponds to the query criteria.

What would be the best way to achieve what I'm looking to do?

Jossy
  • 589
  • 2
  • 12
  • 36

1 Answers1

1

AFAIK, the where clause in SQLAlchemy is a single statement operation. To consider multiple conditions, you either need to use the and_ operator, or chain multiple .where calls.

In other words, I believe this might solve your issues -

  1. Use the and_ operator:
stmt = stmt.where(and_(Tournament.tour_id == tour_id,
                       Tournament.original_id == 16907,
                       p1.tour_id == tour_id,
                       p1.original_id == 79810,
                       p2.tour_id == tour_id,
                       p2.original_id == 37136,
                       Match.round_id == 5))
  1. Chain multiple .where calls:
stmt = stmt.where(Tournament.tour_id == tour_id).\
            where(Tournament.original_id == 16907).\
            where(p1.tour_id == tour_id).\
            where(p1.original_id == 79810).\
            where(p2.tour_id == tour_id).\
            where(p2.original_id == 37136).\
            where(Match.round_id == 5)
hyit
  • 496
  • 4
  • 10
  • Thanks. Given this a go for the final two solutions I tried in my post - same result for both options for both solutions. FWIW I've got `select` statements that seem to run fine without `and_` or multiple `where` calls. I have however noticed that `join` definitely only supports a single statement now though... – Jossy Oct 26 '21 at 21:10
  • I see. What about following the error message and specifying the synchronize_session argument? See the `execution_options` [here](https://docs.sqlalchemy.org/en/14/orm/session_basics.html#orm-expression-update-delete) – hyit Oct 27 '21 at 06:42
  • I get the same error message if I set `synchronize_session` to `"fetch"` or `False` :( – Jossy Oct 28 '21 at 20:30
  • You'll need to clarify a bit @Jossy; which version of the code did you use? It seems to me that the second solution you have is the closest to what you're searching for. – hyit Oct 28 '21 at 22:16
  • @Jossy one more point of interest would be to try `Match.delete` over `sa.delete(Match)`. Some useful links: - [insert/update/delete DML no longer accept keyword constructor arguments](https://docs.sqlalchemy.org/en/14/changelog/migration_20.html#insert-update-delete-dml-no-longer-accept-keyword-constructor-arguments) - [ORM Query execution](https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#orm-query-is-internally-unified-with-select-update-delete-2-0-style-execution-available) - [Duplicate children with backref](https://github.com/sqlalchemy/sqlalchemy/discussions/6294) – hyit Oct 28 '21 at 22:18
  • @Jossy Check this to see how you can delete or update cascade I think you need to use it in your models. https://docs.sqlalchemy.org/en/14/orm/cascades.html – Jose Lora Nov 02 '21 at 15:48