【问题标题】:VALUES clause in SQLAlchemySQLAlchemy 中的 VALUES 子句
【发布时间】:2013-09-22 09:21:32
【问题描述】:

有没有办法在 SQLAlchemy 中构建 Query 对象,相当于:

SELECT * FROM (VALUES (1, 2, 3)) AS sq;

根据我在文档中看到的,VALUES 子句仅在与 INSERT 一起使用时出现。

【问题讨论】:

    标签: python sql sqlalchemy


    【解决方案1】:

    我不太确定,但您可以尝试以下选项:

    SELECT * FROM (select 1 as col1 union select 2 as col1 union select 3 as col1) AS sq
    

    【讨论】:

    • 抱歉,还没有关闭。问题是关于在 SQLAlchemy 中进行 描述的 查询。
    【解决方案2】:

    插入中的“VALUES”是标准 SQL,独立的“VALUES”关键字是 Postgresql 的东西。在PGValues 有一个快速编译器配方(复制到这里,以防有一天我更改 wiki):

    from sqlalchemy import *
    from sqlalchemy.ext.compiler import compiles
    from sqlalchemy.sql.expression import FromClause
    from sqlalchemy.sql import table, column
    
    class values(FromClause):
        def __init__(self, *args):
            self.list = args
    
        def _populate_column_collection(self):
            self._columns.update(
                [("column%d" % i, column("column%d" % i))
                        for i in xrange(1, len(self.list[0]) + 1)]
            )
    
    @compiles(values)
    def compile_values(element, compiler, asfrom=False, **kw):
        v = "VALUES %s" % ", ".join(
            "(%s)" % ", ".join(compiler.render_literal_value(elem, None) for elem in tup)
            for tup in element.list
        )
        if asfrom:
            v = "(%s)" % v
        return v
    
    if __name__ == '__main__':
        t1 = table('t1', column('a'), column('b'))
        t2 = values((1, 0.5), (2, -0.5)).alias('weights')
        print select([t1, t2]).select_from(t1.join(t2, t1.c.a==t2.c.column2))
    

    【讨论】:

    • 谢谢,不知道只有 postgresql 提供这种用途。
    • @zzzeek 看起来像 compiler.render_literal_value 现在需要一个类型。确定它而不是硬编码 sqlalchemy.sql.sqltypes.STRINGTYPE 之类的首选方法是什么?
    • 顺便说一句,自 2008 年以来的 SQLServer 也支持这一点:msdn.microsoft.com/en-us/library/dd776382.aspx
    • @zzzeek 现在没有配方支持了吗?
    • @plaes - 一旦this PR 被合并,它将是。
    【解决方案3】:

    我建议我的 zzzeek 的 sn-p 版本

    • None 渲染为NULL
    • 根据column类型指定字面量类型
    • 处理 ARRAY 字面量(对 PostgreSQL 有用)

    How to make use of bindparam() in a custom Compiled expression?

    【讨论】:

      【解决方案4】:
      from sqlalchemy import select, func
      
      select(['*']).select_from(func.values([1, 2, 3, 4]))
      

      【讨论】:

      • 当我尝试执行它时失败(psycopg2.errors.SyntaxError) syntax error at or near "("
      【解决方案5】:

      不是最好的解决方案,但它对我有用:

      import sqlalchemy as sa
      
      query = sa.select(['*']).select_from(sa.text("(VALUES (1,2,3)) as sq"))
      connection.execute(query).fetchall()
      

      Output: [(1, 2, 3)]

      PS: VALUES 带有别名列示例:

      import sqlalchemy as sa
      
      query_cte = (
          sa.select([sa.column('age'), sa.column('name')])
              .select_from(sa.text("(VALUES (22, 'Bob'), (30, 'Julia')) as t (age, name)"))
      ).cte()
      query_name = sa.select([query_cte.c.name])
      connection.execute(query_name).fetchall()
      

      Output: [('Bob',), ('Julia',)]

      警告: 此解决方案适用于简单值。小心特殊的 符号或单词,它们应该被正确地转义。

      【讨论】:

        【解决方案6】:

        我更进一步,实现了SELECT INTO <table> VALUES (...)

        以下实现旨在与 PostgreSQL 和 Python 3 一起使用,并处理不同类型的值(布尔、整数、浮点、JSON 和 varchar):

        import json
        from sqlalchemy import exc
        from sqlalchemy.dialects import postgresql
        from sqlalchemy.ext.compiler import compiles
        from sqlalchemy.sql import sqltypes, FromClause, Select
        
        class SelectInto(Select):
            def __init__(self, columns, into, *arg, **kw):
                super(SelectInto, self).__init__(columns, *arg, **kw)
                self.into = into
        
        @compiles(SelectInto)
        def _select_into(element, compiler, **kw):
            text = compiler.visit_select(element, **kw)
            text = text.replace("FROM", f"INTO {element.into} \nFROM")
            return text
        
        class Values(FromClause):
            text_type = sqltypes.UnicodeText
        
            def __init__(self, cols, types=None, *args):
                self.cols = cols
                self.vals = args
        
                if isinstance(self.cols, str):
                    self.cols = [c.strip().join('""') for c in self.cols.split(",")]
        
                if not types:
                    self.types = [self.text_type for _ in range(len(self.cols))]
                elif len(cols) == len(types):
                    self.types = [self._map_col_type(t) for t in types]
                else:
                    raise exc.ArgumentError("Types do not correspond to columns")
        
            def _map_col_type(self, col_type):
                if isinstance(col_type, sqltypes.TypeEngine):
                    return col_type
        
                col_type = col_type.lower()
        
                if col_type in ("bool", "boolean"):
                    return sqltypes.Boolean
                elif col_type in ("int", "integer", "number"):
                    return sqltypes.Integer
                elif col_type in ("float", "double"):
                    return sqltypes.Float
                elif col_type in ("json",):
                    return postgresql.json.JSON
                elif col_type in ("jsonb",):
                    return postgresql.json.JSONB
        
                return self.text_type
        
        @compiles(Values)
        def _compile_values(element, compiler, **kw):
            value_cols = ",".join(element.cols)
            value_sets = ", ".join(
                "({values})".format(
                    values=",".join(_compile_value(compiler, val, element.types[idx]) for idx, val in enumerate(tup))
                )
                for tup in element.vals
            )
            return f'(VALUES {value_sets}) AS "values" ({value_cols})'
        
        def _compile_value(compiler, value, type_):
            if value is None:
                return "NULL"
            elif issubclass(type_, sqltypes.JSON):
                if isinstance(value, dict):
                    value = json.dumps(value)
                return f"'{value}'::{type_.__name__}"
        
            if issubclass(type_, sqltypes.String):
                value = str(value)
        
            return compiler.render_literal_value(value, type_())
        

        对此进行测试:

        from sqlalchemy.sql.expression column
        
        select_cols = [column(c) for c in 'one,two,three'.split(',')]
        select_from = Values(['one', 'two', 'three'], ['varchar', 'int', 'bool'], *(('a',1,0),('aa',11,1),('aaa',111,0)))
        select_into = SelectInto(select_cols, 'test_select').select_from(select_from)
        print(select_into)
        

        【讨论】:

        • 这个功能现在可以在一个特定于 postgres 的项目中使用,该项目包装了 sqlalchemy 并使空间索引和高级文本搜索变得容易:github.com/consbio/pg-database-utils
        【解决方案7】:

        这现在在 SQLAlchemy 中原生可用。

        你的例子可以写成:

        from sqlalchemy import select, column, Integer
        from sqlalchemy.sql import Values
        
        select(Values(column('Number', Integer), name='sq').data([(1,), (2,), (3,)]))
        

        似乎没有这方面的任何文档,但你可以看看测试用例https://github.com/sqlalchemy/sqlalchemy/blob/master/test/sql/test_values.py

        【讨论】:

          猜你喜欢
          • 2019-05-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-26
          • 2011-09-06
          相关资源
          最近更新 更多