SQLAlchemy中的VALUES子句

时间:2013-09-17 19:27:38

标签: python sql sqlalchemy

有没有办法在SQLAlchemy中构建一个Query对象,它将等同于:

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

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

7 个答案:

答案 0 :(得分:4)

插入中的“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))

答案 1 :(得分:3)

这现在在 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

答案 2 :(得分:2)

我建议使用zzzeek的代码片段

  • None呈现为NULL
  • 指定基于column类型
  • 的文字类型
  • 处理ARRAY文字(对PostgreSQL有用)

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

答案 3 :(得分:1)

from sqlalchemy import select, func

select(['*']).select_from(func.values([1, 2, 3, 4]))

答案 4 :(得分:1)

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

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',)]

警告: 此解决方案适用于简单值。要特别注意 符号或文字,应正确转义。

答案 5 :(得分:0)

为了实现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)

答案 6 :(得分:-1)

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

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