How to create an SQL View with SQLAlchemy?
Update: SQLAlchemy now has a great usage recipe here on this topic, which I recommend. It covers different SQL Alchemy versions up to the latest and has ORM integration (see comments below this answer and other answers). And if you look through the version history, you can also learn why using literal_binds
is iffy (in a nutshell: binding parameters should be left to the database), but still arguably any other solution would make most users of the recipe not happy. I leave the below answer mostly for historical reasons.
Original answer: Creating a (read-only non-materialized) view is not supported out of the box as far as I know. But adding this functionality in SQLAlchemy 0.7 is straightforward (similar to the example I gave here). You just have to write a compiler extension CreateView
. With this extension, you can then write (assuming that t
is a table object with a column id
)
createview = CreateView('viewname', t.select().where(t.c.id>5))
engine.execute(createview)
v = Table('viewname', metadata, autoload=True)
for r in engine.execute(v.select()):
print r
Here is a working example:
from sqlalchemy import Table
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Executable, ClauseElement
class CreateView(Executable, ClauseElement):
def __init__(self, name, select):
self.name = name
self.select = select
@compiles(CreateView)
def visit_create_view(element, compiler, **kw):
return "CREATE VIEW %s AS %s" % (
element.name,
compiler.process(element.select, literal_binds=True)
)
# test data
from sqlalchemy import MetaData, Column, Integer
from sqlalchemy.engine import create_engine
engine = create_engine('sqlite://')
metadata = MetaData(engine)
t = Table('t',
metadata,
Column('id', Integer, primary_key=True),
Column('number', Integer))
t.create()
engine.execute(t.insert().values(id=1, number=3))
engine.execute(t.insert().values(id=9, number=-3))
# create view
createview = CreateView('viewname', t.select().where(t.c.id>5))
engine.execute(createview)
# reflect view and print result
v = Table('viewname', metadata, autoload=True)
for r in engine.execute(v.select()):
print r
If you want, you can also specialize for a dialect, e.g.
@compiles(CreateView, 'sqlite')
def visit_create_view(element, compiler, **kw):
return "CREATE VIEW IF NOT EXISTS %s AS %s" % (
element.name,
compiler.process(element.select, literal_binds=True)
)
sqlalchemy creating VIEW with ORM
the library sqlalchemy-utils now includes functionality for creating views, and it associates the view with sqlalchemy's metadata so that it is possible to create the view using Base.metadata.create_all
example:
# installation: pip install sqlalchemy-utils
from sqlalchemy_utils import create_view
from sqlalchemy import select, func
# engine Base & Table declaration elided for brevity
stmt = select([
TableB.id.label('table_b_id'),
TableB.coupon_code,
TableB.number_of_rebought_items,
TableA.id.label('table_a_action_id'),
TableA.time,
TableA.parameters
]).select_from(TableB.__table__.outerjoin(TableA, TableB.generate_action == TableA.id))
# attaches the view to the metadata using the select statement
view = create_view('my_view', stmt, Base.metadata)
# provides an ORM interface to the view
class MyView(Base):
__table__ = view
# will create all tables & views defined with ``create_view``
Base.metadata.create_all()
# At this point running the following yields 0, as expected,
# indicating that the view has been constructed on the server
engine.execute(select([func.count('*')], from_obj=MyView)).scalar()
SQLAlchemy Create View in PostgresQL
I found the solution. The issue was to do with autocommit. Setting autocommit to true when creating the engine solved the issue as follows:
engine = db.create_engine('postgresql://user:pass@localhost:5432/db_name').execution_options(autocommit=True)
Special mention to @ilja-everilä
Use a SQL view with a SQLAlchemy ORM class?
I found a "solution" that works in my use case. Since I know the column "coin" in my view will hold unique values, I re-defined that column as a primary key. I guess this will be true for any column that is being grouped by.
I hope this solution works for everyone who runs into this post :-)
Is possible to mapping view with class using mapper in SqlAlchemy?
You can do this, but you have to define a primary key manually. Assuming that id
is a column of v
which you want to use as the primary key (as is the case in my original example code), this works:
from sqlalchemy import orm
class ViewName(object):
def __init__(self, name):
self.name = name
orm.mapper(ViewName, v, primary_key=[v.c.id])
Session = orm.sessionmaker(bind=engine)
for r in Session().query(ViewName):
print r.id, r.number
To test this, just paste this snippet at the end of my working example in the answer linked above. See the documentation for more details (e.g. you can use properties to define foreign keys).
EDIT (van in a comment to my answer linked above):
Alternatively, you can change the view definiton in my original code (and your question) slightly and write:
v = Table('viewname', metadata, Column('id', Integer, primary_key=True), autoload=True)
i.e., add the primary key in the table definition already. Then you don't need the primary_key
argument in the orm.mapper
and the rest of the code in your question works out of the box.
SQLAlchemy view handling
The use of reflect
parameter of MetaData
has been deprecated since version 0.8 and should be replaced with the more expressive MetaData.reflect()
method, which allows reflecting views as well:
meta = MetaData(Engine)
meta.reflect(views=True)
nav = meta.tables['name_address_vw']
Related Topics
Openpyxl 1.8.5: Reading the Result of a Formula Typed in a Cell Using Openpyxl
How to Create an Empty Array and Then Append to It in Numpy
How to Use Valgrind with Python
How to Read the Contents of an Url with Python
Convert from Ascii String Encoded in Hex to Plain Ascii
How to Create a Slug in Django
Typeerror: Unhashable Type: 'List' When Using Built-In Set Function
Panda's Dataframe - Renaming Multiple Identically Named Columns
How to Add an Empty Column to a Dataframe
Region: Ioerror: [Errno 22] Invalid Mode ('W') or Filename
Multiprocessing VS Multithreading VS Asyncio in Python 3
Why Does the 'Is' Operator Behave Differently in a Script VS the Repl
How to Replace (Or Strip) an Extension from a Filename in Python
Django-Registration & Django-Profile, Using Your Own Custom Form
Python Integer Incrementing with ++
Why Do I Get "Pickle - Eoferror: Ran Out of Input" Reading an Empty File