在SQLAlchemy中删除左外连接右侧的NULL

3 投票
1 回答
3462 浏览
提问于 2025-04-17 16:13

我有一个叫做 Track 的表和一个叫做 Artist 的表。 Artist 表里有一个 id,而 Track 表里有一个外键 artist_id。我想删除那些没有任何关联曲目的艺术家。

在 SQL 中,这样写:

delete from artists 
 where id in (select artists.id
                from artists left outer join tracks 
                  on tracks.artist_id = artists.id
               where tracks.id is null);

这个方法运行得很好。但是当我试着在 SQLAlchemy 中复制这个操作时:

artists = session.query(Artist.id).outerjoin((Track, Artist.id == Track.artist_id)).filter(Track.id == None)
print('deleting %d unused artists' % artists.count())
session.query(Artist).filter(Artist.id.in_(artists.all())).delete()

虽然 print() 可以正常工作(并且显示正确的行数),但是删除的时候却出现了错误:

...
sqlalchemy.orm.evaluator.UnevaluatableError: Cannot evaluate clauselist with operator <function comma_op at 0x2d3e0d8>
During handling of the above exception, another exception occurred:
...
"Could not evaluate current criteria in Python. "
sqlalchemy.exc.InvalidRequestError: Could not evaluate current criteria in Python. Specify 'fetch' or False for the synchronize_session parameter.

那么我该如何在 SQLAlchemy 中做到这一点呢?我不介意方法不同,只要能删除所有没有曲目的艺术家就行。

顺便说一下,我也试过 artists.delete()(这次是选择艺术家实例,而不是像上面那样选择 id),但也出现了错误——在这种情况下,外连接“丢失”了,导致 SQL 不一致。

更新

如果这对任何人有用的话,如果你的模型里有一个反向引用(artists,如下所示),那么有一种比外连接简单得多的方法:

session.query(Artist).filter(Artist.tracks == None).delete(synchronize_session=False)

1 个回答

5

这个错误信息其实在告诉你怎么解决问题,你需要指定一个 synchronize_session 的值,可以是 "fetch" 或者 False。Sqlalchemy 试图避免做一些额外的工作,它想通过直接更新与 session 关联的对象状态,来反映数据库中的变化。

不过,对于这个查询,它没办法做到这一点,所以你需要告诉它要么去“获取”当前的 Artist 对象,看看它们是否被删除,要么就选择忽略这个问题,让会话保持在一个无效的状态。

你还会在使用 in_(query.all()) 时遇到一些麻烦,因为 sqlalchemy 不知道怎么处理元组的列表,而且这也不是原始查询的完美复现。其实只用 in_(query) 就足够了。

撰写回答