带有IN子句的SQLAlchemy原始SQL参数替换

时间:2013-01-24 22:45:07

标签: python sqlalchemy

我有一个SQL语句SELECT foo FROM bar WHERE id IN %s。我有一个整数列表,例如[1, 2, 3]我希望将其转换为类似SELECT foo FROM bar WHERE id IN (1, 2, 3)的SQL语句。

我使用SQLAlchemy Core作为其连接池,并使一些带有多个VALUES子句的插入更容易编写和维护。我更喜欢用原始SQL编写大部分查询。

要在Pyscopg2中执行此操作,我会cursor.execute('SELECT .. WHERE IN %s', (tuple(my_list),))。但是,我无法在SQLAlchemy中完成这项工作。

engine.execute('SELECT ... WHERE IN %s', tuple(my_list))引发异常:TypeError:并非在字符串格式化期间转换所有参数。如果我只传递列表而不包含在元组中,则会引发同样的异常。

如果我使用engine.execute('SELECT ... WHERE id IN :ids', ids=my_list)等命名参数,我会得到ProgrammingError异常,因为SQLAlchemy会创建错误的SQL:SELECT * FROM foo WHERE id IN :ids(它不会替换我的变量的:ids值)。如果我传递元组,则会引发同样的异常。

如何在SQLAlchemy中使用原始SQL使用WHERE IN()子句?

2 个答案:

答案 0 :(得分:14)

这是一种仅由某些DBAPI支持的异常格式,因为它将一个项目元组呈现为单独的SQL表达式,包括它在参数之间呈现逗号等,因此像execute("select * from table where value in %s", (somelist, ))这样的语句扩展了在数据库级别select * from table where value in (1, 2, 3)

SQLAlchemy不期望这种格式 - 它已经对传入参数进行了一些检查,因为它涉及将参数路由到DBAPI execute()executemany()方法,并且还接受一些不同的样式,这种转换的结果是这里的元组变得扁平化了。你可以通过添加一个元组来隐藏你的元组:

from sqlalchemy import create_engine

engine = create_engine("postgresql://scott:tiger@localhost/test", echo=True)

with engine.connect() as conn:
    trans = conn.begin()


    conn.execute("create table test (data integer)")
    conn.execute(
            "insert into test (data) values (%s)",
            [(1, ), (2, ), (3, ), (4, ), (5, )]
        )

    result = conn.execute(
                "select * from test where data in %s",
                (
                    ((1, 2, 3),),
                )
            )

    print result.fetchall()

以上样式仅适用于某些DBAPI。快速测试确认它适用于psycopg2和MySQLdb,但不适用于sqlite3。它更多地与DBAPI用于将绑定参数发送到数据库的底层系统有关; psycopg2和MySQLdb都进行Python字符串插值和它们自己的转义,但像cx_oracle这样的系统会将参数单独传递给OCI,所以这种情况在这种情况下不起作用。

SQLAlchemy当然在使用SQL表达式构造时提供in_()运算符,但这不适用于直接字符串。

答案 1 :(得分:10)

我使用SQLAlchemy 0.9.8,python 2.7,MySQL 5.X和MySQL-Python作为连接器,在这种情况下,需要一个元组。我的代码如下:

id_list = [1, 2, 3, 4, 5] # in most case we have an integer list or set
s = text('SELECT id, content FROM myTable WHERE id IN :id_list')
conn = engine.connect() # get a mysql connection
rs = conn.execute(s, id_list=tuple(id_list)).fetchall()

希望一切都适合你。