重新排列SQLAlchemy Select对象中的列

时间:2014-09-18 13:39:34

标签: python sql sqlalchemy

问题

如何重新排序SQLAlchemy查询对象中的列?

实施例

给定一个表示以下查询的SQLAlchemy核心Select对象:

>>> print s
SELECT sum(accounts.amount) AS amount_sum, accounts.name 
FROM accounts GROUP BY accounts.name
 LIMIT :param_1

我想形成一个具有完全相同结构的Select对象,但是列的顺序是移动的

SELECT accounts.name,  sum(accounts.amount) AS amount_sum
FROM accounts GROUP BY accounts.name
 LIMIT :param_1

我尝试过的不起作用

我希望在不显着扩展SQL的情况下这样做。我已尝试使用with_only_columns进行操作并嵌套查询,我再次想要避免

>>> print s.with_only_columns(list(s.columns)[::-1])
SELECT name, amount_sum 
FROM (SELECT sum(accounts.amount) AS amount_sum, accounts.name AS name 
FROM accounts GROUP BY accounts.name
 LIMIT :param_1) GROUP BY accounts.name
 LIMIT :param_2

修改

要清楚上面的例子只是一个例子。我正在寻找一个通用的解决方案来执行任意查询。理想情况下,该函数将按正确的顺序使用Select对象和列名列表。

编辑2

创建s的代码。同样,我正在寻找一般问题的解决方案,而不仅仅是一个明确修复下面排序的代码片段。

import sqlalchemy

metadata = sqlalchemy.MetaData()

t = sqlalchemy.Table('accounts', metadata,
             sqlalchemy.Column('name', sqlalchemy.String),
             sqlalchemy.Column('amount', sqlalchemy.Integer),
             sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
             )

s = (sqlalchemy.select([sqlalchemy.sql.functions.sum(t.c.amount).label('sum'),    
                         t.c.name])
        .group_by(t.c.name)
        .limit(10))

print s


SELECT sum(accounts.amount) AS sum, accounts.name 
FROM accounts GROUP BY accounts.name
 LIMIT :param_1

4 个答案:

答案 0 :(得分:4)

您使用的是with_only_columns,但您放入其中的列必须来自最初发送到select()的列表,而不是选择本身的导出列

您必须在外部保留这些列,或通过select.inner_columns访问者访问这些列。

下面的会话说明了各种行为。 " .c。"任何可选择的集合代表"我们可以从"中选择的列,这就是使用" someselect.c.somecol"意味着你要从那个选择语句中选择,就像表一样。

>>> from sqlalchemy import select
>>> from sqlalchemy.sql import table, column

>>> t = table('t', column('a'), column('b'), column('c'))

>>> stmt1 = select([t])
>>> print(stmt1)
SELECT t.a, t.b, t.c FROM t

>>> print(stmt1.with_only_columns([t.c.b, t.c.a]))
SELECT t.b, t.a FROM t

>>> print(stmt1.with_only_columns([stmt1.c.b, stmt1.c.a]))
SELECT b, a FROM (SELECT t.a AS a, t.b AS b, t.c AS c FROM t)

>>> stmt1_cols = dict((c.key, c) for c in stmt1.inner_columns)

>>> stmt1_cols
{'a': <sqlalchemy.sql.elements.ColumnClause at 0x102d0e090; a>, 'c': <sqlalchemy.sql.elements.ColumnClause at 0x102de7c50; c>, 'b': <sqlalchemy.sql.elements.ColumnClause at 0x10047bb50; b>}

>>> dict(t.c)
{'a': <sqlalchemy.sql.elements.ColumnClause at 0x102d0e090; a>, 'c': <sqlalchemy.sql.elements.ColumnClause at 0x102de7c50; c>, 'b': <sqlalchemy.sql.elements.ColumnClause at 0x10047bb50; b>}

>>> assert stmt1_cols == dict(t.c)

>>> print(stmt1.with_only_columns([stmt1_cols['b'], stmt1_cols['a']]))
SELECT t.b, t.a FROM t

答案 1 :(得分:2)

我找到了另一个解决方案。您可以使用inner_columns属性。此属性将迭代器返回到_raw_columns的内部变量。它存储传递给select函数的原始列。 一些例子:

test = Table('test', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),  
    Column('num', Integer, default=0),  
)
metadata.create_all(engine)
statement = select([test]).group_by(test.c.id)
print(statement)
# convert iterator to list
cols = list(statement.inner_columns)
# columns in reverse order
print(statement.with_only_columns(cols[::-1]))
# only some columns
print(statement.with_only_columns([cols[2], cols[0]]))
# convert columns list to ColumnCollection object
columns = ColumnCollection()

for c in cols:
    columns.add(c)

print(statement.with_only_columns([columns.name, columns.id]))

为简化此操作,您可以构建自定义Select类,如下所示:

class RearrangeSelect(Select):

    def reverse_columns(self):
        return self.with_only_columns(list(self.inner_columns)[::-1])

    def reorder_columns(self, *args):
        columns = ColumnCollection()

        for c in self.inner_columns:
            columns.add(c)

        query_cols = []
        for name in args:
            c = getattr(columns, name, None)
            if c is None:
                raise ValueError('Bad column name "%s"' % name)

            query_cols.append(c)

        return self.with_only_columns(query_cols)


statement = RearrangeSelect([test]).group_by(test.c.id)
print(statement)
print(statement.reverse_columns())
print(statement.reorder_columns('num', 'name'))
print(statement.reorder_columns('name', 'id'))

完整示例代码here

答案 2 :(得分:1)

来自sqlalchemy with_only_columns手册:

还应注意使用正确的 传递给Select.with_only_columns的列对象集。 由于该方法基本上等同于调用 select首先构造给定的 列,传递给.Select.with_only_columns的列 通常应该是通过的子集 到select构造,而不是那些可用的构造 来自.c的{​​{1}}集合。那 是:

select

s = select([table1.c.a, table1.c.b]).select_from(table1)
s = s.with_only_columns([table1.c.b])

后者会产生SQL:

# usually incorrect
s = s.with_only_columns([s.c.b])

文档结束

如您所见而不是

SELECT b
FROM (SELECT t1.a AS a, t1.b AS b
FROM t1), t1

你应该使用

s.with_only_columns(list(s.columns)[::-1])

作为选项,您可以在创建第一个s.with_only_columns([table.c.name, func.sum(table.c.col2.amount)]) 实例时保存列,然后使用 在Select电话中。

答案 3 :(得分:1)

作为另一种变体,你可以使用这个黑客:

def table_cols(columns, table):
    result_columns = []
    for c in columns:
        nc = c.copy()
        nc.table = table
        result_columns.append(nc)

    return result_columns

# now you can do this
s.with_only_columns(table_cols(s.c, your_table)[::-1])

table_cols函数从Select对象复制列,然后绑定到表,因此生成的查询将使用此表。