SQLAlchemy不包括DELETE中的CTE

时间:2017-10-18 00:34:20

标签: python postgresql sqlalchemy

我正在尝试在SQLAlchemy(核心)中使用带DELETE的CTE。到目前为止,我的尝试都没有成功,因为CTE没有包含在已编译的SQL语句中。最终,这将针对PostgreSQL数据库运行,该数据库支持这种语句。

一个人为举例的Python代码:

from sqlalchemy import *
from sqlalchemy.dialects import postgresql
metadata = MetaData()
tbl = Table('foo', metadata,
        Column('id', Integer, primary_key = True),
        Column('name', String)
    )

mycte = select([tbl.c.name]).where(tbl.c.id == 123).cte('ctetbl')
delete_stmt = tbl.delete().where(tbl.c.name == mycte.c.name)
print("Regular:", delete_stmt.compile())
print("Postgres:", delete_stmt.compile(dialect = postgresql.dialect()))

我期待的是(或类似的东西):

WITH ctetbl AS (
    SELECT name FROM foo
    WHERE id = 123
)
DELETE FROM foo WHERE foo.name = ctetbl.name

我得到的是:

DELETE FROM foo WHERE foo.name = ctetbl.name

我在这里缺少什么?

1 个答案:

答案 0 :(得分:1)

WITH ctetbl AS (
    SELECT name FROM foo
    WHERE id = 123
)
DELETE FROM foo WHERE foo.name = ctetbl.name

您实际上并未将CTE作为DELETE语句中的源表包含在内。由于no support for USING clause,SQLAlchemy语句中发生了类似的事情并且会抛出编译器。你可以add support for it with a compiler extension

from sqlalchemy import *
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Delete, Update
import re

@compiles(Delete, 'postgresql')
def compile_delete(element, compiler, **kw):
    using_clause = None
    extra_froms = Update._extra_froms.__get__(element)

    if extra_froms:
        # Pre-compile extra_froms in order to populate CTEs before
        # compiling the delete statement itself
        using_clause = "USING %s" % ', '.join(
            compiler.process(fr, asfrom=True, **kw)
            for fr in extra_froms
        )

    text = compiler.visit_delete(element, **kw)

    if using_clause:
        # NOTE: This will blow up badly, if your CTEs also
        # contain DELETE statements.
        text = re.sub(
            r"(DELETE FROM \S+)",
            lambda m: "%s %s" % (m.group(1), using_clause),
            text
        )

    return text

然后

delete_stmt = tbl.delete().where(tbl.c.name == mycte.c.name)

将编译为

WITH ctetbl AS 
(SELECT foo.name AS name 
FROM foo
WHERE foo.id = %(id_1)s)
 DELETE FROM foo USING ctetbl WHERE foo.name = ctetbl.name

使用USING你也可以进行自我加入:

   ...: tbl_alias = tbl.alias()
   ...: delete_stmt = tbl.delete().\
   ...:     where(tbl.c.name == tbl_alias.c.name).\
   ...:     where(tbl_alias.c.id == 123)
   ...: 
   ...: print("Postgres:", delete_stmt.compile(dialect = postgresql.dialect()))
   ...: 
Postgres: DELETE FROM foo USING foo AS foo_1 WHERE foo.name = foo_1.name AND foo_1.id = %(id_1)s

当然,您也可以使用标量子选择引用CTE,而无需编译器扩展:

   ...: delete_stmt = tbl.delete().where(tbl.c.name == mycte.select().as_scalar())
   ...: 
   ...: print("Postgres:", delete_stmt.compile(dialect = postgresql.dialect()))
Postgres: WITH ctetbl AS 
(SELECT foo.name AS name 
FROM foo 
WHERE foo.id = %(id_1)s)
 DELETE FROM foo WHERE foo.name = (SELECT ctetbl.name 
FROM ctetbl)

但那里的乐趣......