【问题标题】:Rearrange columns in SQLAlchemy Select object重新排列 SQLAlchemy Select 对象中的列
【发布时间】:2014-09-19 22:52:33
【问题描述】:

问题

如何重新排序 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的代码。同样,我正在寻找一个一般问题的解决方案,而不仅仅是一个明确修复以下顺序的代码 sn-p。

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

【问题讨论】:

  • 你能发布创建Select对象的代码吗?
  • @SlavaBacherikov 完成。

标签: python sql sqlalchemy


【解决方案1】:

您使用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

【讨论】:

    【解决方案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

    【讨论】:

    • 这太棒了!我认为 _raw_columns 属性是我正在寻找的。​​span>
    • 这不是公开的api,所以以后可能会改变。因此更好地访问inner_columns 属性,它返回_raw_columns 上的迭代器。
    【解决方案3】:

    作为另一种变体,您可以使用此 hack:

    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 对象复制列,然后绑定到表,因此生成的查询将使用此表。

    【讨论】:

      【解决方案4】:

      来自 sqlalchemy with_only_columns 手册:

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

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

      并且不是

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

      后者会产生 SQL:

      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 实例时保存列,然后使用 在with_only_columns 电话中。

      【讨论】:

      • 如何在更一般的情况下执行此操作,即我通过了一个任意复杂的查询,并且不一定有原始表和列的构造很容易(除了在原始查询)。请参阅原始问题中的编辑
      • 作为变体,您可以使用下面发布的 hack。另一种选择是将选择字段保存到某个 var 中,然后在您需要反向顺序时再次使用它们。
      • 你能想出一个不只解决这个问题的通用解决方案吗?如果我给你结果查询和一个按首选顺序排列的字符串列名列表,你会怎么做。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-24
      • 1970-01-01
      • 1970-01-01
      • 2011-12-27
      • 2020-07-28
      • 1970-01-01
      • 2015-07-12
      相关资源
      最近更新 更多