如何重新排序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
对象和列名列表。
创建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
答案 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
对象复制列,然后绑定到表,因此生成的查询将使用此表。