Is there a way to build a Query
object in SQLAlchemy which will be the equivalent of:
SELECT * FROM (VALUES (1, 2, 3)) AS sq;
well "VALUES" in an insert is the standard SQL, the standalone "VALUES" keyword is a Postgresql thing. There's a quick compiler recipe for this one at PGValues (copied here in case I change the wiki someday):
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))
I'm not exactly sure but you can try the following options:
SELECT * FROM (select 1 as col1 union select 2 as col1 union select 3 as col1) AS sq
from sqlalchemy import select, func
select(['*']).select_from(func.values([1, 2, 3, 4]))
Not the best solution but it worked for me:
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
with the aliased columns example:
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',)]
WARNING: This solution is good for simple values. Be careful with the special symbols or words, they should be properly escaped.
I suggest my version of zzzeek's snippet which
None
as NULL
column
typeARRAY
literals (useful for PostgreSQL)How to make use of bindparam() in a custom Compiled expression?
I went a bit further, to implement SELECT INTO <table> VALUES (...)
.
The following implementation is designed to work with PostgreSQL and Python 3, and handles values of differing types (boolean, integer, float, JSON and 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_())
To test this:
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)