I have this sql query:
select
rooms.*,
COUNT(DISTINCT(o.resident_id)) as resident_count,
COUNT(reviews.id) as review_count,
COUNT(photos.id) as photo_count,
AVG(reviews.rating) as mean_review
from
t_rooms rooms
JOIN
t_room_listings listings on listings.room_id = rooms.id
JOIN
t_occupancies o on o.listing_id = listings.id
LEFT JOIN
t_reviews reviews on reviews.occupancy_id = o.id
LEFT JOIN
t_photos photos on photos.occupancy_id = o.id
GROUP BY rooms.id
Which I know I can write in ORM query form as:
q = (session
.query(
Room,
func.count(func.distinct(Occupancy.resident_id)).label('resident_count'),
func.count(Review.id).label('review_count'),
func.count(Photo.id).label('photo_count'),
(
(3 + func.avg(Review.rating)) / (1 + func.count(Review.rating))
).label('bayesian_rating')
)
.select_from(
join(Room, RoomListing).join(Occupancy).outerjoin(Review).outerjoin(Photo)
)
.group_by(Room.id)
)
for room, res_ct, rev_ct, p_ct in q:
wish_that_I_could_write(room.res_ct, room.rev_ct, room.p_ct, room.score)
But how can I declare resident_count, review_count etc as column_propertys in my Room class, so that I don't need to construct this query each time?
You may achieve this result with mapping query to object like so:
class ExtendedRoom(object):
pass
# q is your query
mapper(ExtendedRoom, q.statement.alias())
for room in session.query(ExtendedRoom).all():
# now room have review_count and other attributes
print(room.review_count)
Here simplified example with column_property.
from sqlalchemy import create_engine, Column, Integer, MetaData, Table, String, func
from sqlalchemy.sql import select
from sqlalchemy.orm import sessionmaker, mapper, column_property
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
Base = declarative_base()
session = Session()
metadata = MetaData()
room = Table('room', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('num', Integer, default=0),
)
metadata.create_all(engine)
statement = select([room]).group_by(room.c.id).alias()
class Room(object):
pass
mapper(Room, statement, properties={
'count': column_property(func.count(statement.c.num)),
})
print(session.query(Room).all())
Related
I have already read similar questions in SO and on Google, as well as the official SQLAlchemy docs, but still couldn't figure out how to solve my problem.
Consider the following structure (non-relevant fields removed for simplicity):
header_table = Table(
'header',
metadata,
Column('id', Integer, primary_key=True),
Column('parent_header_id', Integer)
)
item_table = Table(
'item',
dal.metadata,
Column('id', Integer, primary_key=True),
Column('header_id', Integer)
)
class Header:
id: int
parent_header_id: int
# Relationships
items: List[Item]
children: List[Header]
class Item:
id: int
header_id: int
mapper(Header, header_table, properties={
'children': relationship(Header, foreign_keys=[header_table.c.parent_header_id]),
})
Just to summarise: you can nest headers (max of 1 level of nesting), and each header can have items.
I'm trying to load all headers, with their items and children, and the items of the children.
header_alias = aliased(Header)
records = (
session.query(Header)
.outerjoin(Header.items)
.outerjoin(Header.children.of_type(header_alias))
# .outerjoin(Header.children.of_type(header_alias).items) <<< THE PROBLEM IS HERE (READ BELOW)
.options(contains_eager(Header.items))
.options(contains_eager(Header.children.of_type(header_alias)))
.all()
)
How do I load the items of the children?
The code commented out in the example is wrong, I just put it there as an example of what I'm trying to do.
Note: The code above works, but it's lazy loading the items of the children, I'm trying to get rid of this lazy loading.
Big thanks to #zzzeek (Mike Bayer), author of SQLAlchemy, who answered the question in Github.
https://github.com/sqlalchemy/sqlalchemy/discussions/6876
OK you have to alias "items" also, this is SQL so every table has to
be in the FROM clause only once. Here's a full running example
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import aliased
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
Base = declarative_base()
metadata = Base.metadata
header_table = Table(
"header",
metadata,
Column("id", Integer, primary_key=True),
Column("parent_header_id", ForeignKey("header.id")),
)
item_table = Table(
"item",
metadata,
Column("id", Integer, primary_key=True),
Column("header_id", ForeignKey("header.id")),
)
class Header(Base):
__table__ = header_table
children = relationship("Header")
items = relationship("Item")
class Item(Base):
__table__ = item_table
id: int
header_id: int
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
s.add(
Header(
items=[Item(), Item()],
children=[Header(items=[Item()]), Header(items=[Item(), Item()])],
)
)
s.commit()
s.close()
header_alias = aliased(Header)
item_alias = aliased(Item)
records = (
s.query(Header)
.outerjoin(Header.items)
.outerjoin(Header.children.of_type(header_alias))
.outerjoin(header_alias.items.of_type(item_alias))
.options(
contains_eager(Header.items),
contains_eager(Header.children.of_type(header_alias)).options(
contains_eager(header_alias.items.of_type(item_alias))
),
)
.all()
)
s.close()
for r in records:
print(r)
print(r.items)
for c in r.children:
print(c)
print(c.items)
I have made a table from one service (not using sqlalchemy), and now i want to query the tables using sqlalchemy
i used a syntax like this
from app import db, engine, Base
from sqlalchemy import Table, Column, String, Integer, TIMESTAMP, Float
class Post(Base):
__table__ = Table('posts', Base.metadata,
autoload=True, autoload_with=engine)
And this is working, i can query my objects.
The annoyance is, that i can't get the properties before runtime, because (obviously), there is nothing before it compiles. Is there a way to add the types when using something like this, or do i have to assume, that they are there?
For existing tables we can reflect the table-specific information (columns, FKs, etc.) and still add our own extra ORM-specific attributes like relationships:
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
print(sa.__version__) # 1.3.20
connection_uri = "mssql+pyodbc://#mssqlLocal64"
engine = sa.create_engine(connection_uri, echo=False)
# show existing sample data
with engine.connect() as conn:
results = conn.execute(sa.text("SELECT * FROM users"))
print([dict(r) for r in results])
# [{'id': 1, 'name': 'Gord'}]
results = conn.execute(sa.text("SELECT * FROM posts"))
print([dict(r) for r in results])
# [{'id': 1, 'user_id': 1, 'post': 'Hello world!'}]
Base = declarative_base()
meta = Base.metadata
class User(Base):
__table__ = sa.Table("users", meta, autoload_with=engine)
class Post(Base):
__table__ = sa.Table("posts", meta, autoload_with=engine)
author = sa.orm.relationship("User")
def __repr__(self):
return f"<Post(id={self.id}, author_name='{self.author.name}', post='{self.post}')>"
Session = sessionmaker(bind=engine)
session = Session()
my_post = session.query(Post).get(1)
print(my_post) # <Post(id=1, author_name='Gord', post='Hello world!')>
I wanna to get Primary Key of last inserted, I already know two way for this :
1) "lastrowid" with "raw SQL"
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text
engine = create_engine('sqlite://')
meta = MetaData()
tbl = Table('tbl', meta,
Column('f1', Integer, primary_key=True),
Column('f2', String(64))
)
tbl.create(engine)
sql = text("INSERT INTO tbl VALUES (NULL, 'some_data')")
res = engine.execute(sql)
print(res.lastrowid)
2) "inserted_primary_key" with "insert()"
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String
engine = create_engine('sqlite://')
meta = MetaData()
tbl = Table('tbl', meta,
Column('f1', Integer, primary_key=True),
Column('f2', String(64))
)
tbl.create(engine)
ins = tbl.insert().values(f2='some_data')
res = engine.execute(ins)
print(res.inserted_primary_key)
but my problem is "declarative_base()"
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite://')
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()
class TBL(Base):
__tablename__ = 'tbl'
f1 = Column(Integer, primary_key=True)
f2 = Column(String(64))
Base.metadata.create_all(engine)
rcd = TBL(f2='some_data')
session.add(rcd)
session.commit()
If i do this:
res = session.add(rcd)
It give me "None". or if i do this:
res = session.commit()
same thing happend. My question is:
Is there any good way to access "lastrowid" or "inserted_primary_key" in case of "declarative_base()"?
What is the best approach ?
After calling session.commit(), accessing rcd.f1 will return its generated primary key. SQLAlchemy automatically reloads the object from database after it has been expired by the commit.
I have a query that I create, it looks like
items = Session.query(Widgets.id).filter_by(
state=WidgetStates.NEW
)
when I look at the str representation of it I see this as the planned query
str(items)
'SELECT widgets.id AS widgets_guid \nFROM widgets \nWHERE widgets.state = %(state_1)s'
However, when I execute the query to get a count with echo=True I see a different query being exected:
items.count()
2014-08-09 11:59:48,875 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM widgets, (SELECT widgets.id AS widgets_id
FROM widgets
WHERE widgets.state = %(state_1)s) AS anon_1
WHERE widgets.type IN (%(type_1)s)
The problem is that it's going to count the entire widgets table where type equals "FOO_WIDGET". But it's not going to filter the count by state it as I would have expected it to.
I think the issue relates to the Widget model having a polymorphic_identity discriminator applied to it:
class Widget(Model):
class types(object):
FOO_WIDGET = 'foo'
__mapper_args__ = {
'polymorphic_identity': Widget.types.FOO_WIDGET
}
But the issue is it's not using the items query to count, it's using two different tables to get the selected count and one of them does not have any filtering on it. How do I get this query to work as expected?
Runnable Example
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Table, Column, Unicode, Integer, create_engine, MetaData, func
from sqlalchemy.orm import scoped_session, sessionmaker
metadata = MetaData()
Base = declarative_base(metadata=metadata)
widgets = Table(
'widgets', metadata,
Column('id', Integer, primary_key=True),
Column('type', Unicode),
Column('state', Unicode)
)
class Widget(Base):
__table__ = widgets
class types(object):
FOO_WIDGET = 'foo'
BAR_WIDGET = 'bar'
__mapper_args__ = {
'polymorphic_on': widgets.c.type,
}
class FooWidget(Widget):
__mapper_args__ = {
'polymorphic_identity': Widget.types.FOO_WIDGET
}
db_engine = create_engine('sqlite:///:memory:', echo=True)
Session = scoped_session(sessionmaker())
Session.configure(bind=db_engine)
metadata.create_all(db_engine)
items = Session.query(FooWidget.id).filter_by(
state='new'
)
print str(items)
print 'i expect the next statement to print something approximating:'
print '''
select count(*) from widgets where type = 'foo' and state = 'new'
'''
print items.count()
# What this actually prints
'''
2014-08-28 09:55:15,055 INFO sqlalchemy.engine.base.Engine SELECT count(*) AS count_1
FROM widgets, (SELECT widgets.id AS widgets_id
FROM widgets
WHERE widgets.state = ?) AS anon_1
WHERE widgets.type IN (?)
'''
To run this example you need SQLAlchemy (Tested here with SQLA 0.9.7, in my actual app it's 0.7.x, bug exists in both versions)
from sqlalchemy import Column, Integer, String, MetaData, Table, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
#table columns describing
params = dict( lid = Column(Integer, primary_key=True),
cab_name = Column(String))
table_name = dict(__tablename__ = "table")
def cab_init(self, **kwargs ):
for k,v in kwargs.items():
setattr(self, k, v)
methods = dict( __init__ = cab_init )
cab_item = type("cab_item", (declarative_base(),), dict ( methods.items()
+ params.items()
+ table_name.items() ) )
engine = create_engine('sqlite:///:memory:', echo=False)
meta = MetaData()
table = Table('table', meta, #(*1)
Column('lid', Integer,primary_key=True),
Column('cab_name', String),
)
#table = Table(name = 'table', metadata = meta, args = params.values()) #(*2)
meta.create_all(engine)
session = sessionmaker(engine)()
session.merge(cab_item(lid=10, cab_name="blah"))
session.commit()
(*1) works just fine, (*2) produces an error
sqlalchemy.exc.OperationalError: (OperationalError) no such table:
table u'SELECT "table".lid AS table_lid, "table".cab_name AS
table_cab_name \nFROM "table" \nWHERE "table".lid = ?' (10,)
Why?
I found the solution
params is declared wrong. The right way is
params2 = (Column('lid', Integer, primary_key=True),
Column('cab_name',String))
Call Table ctor like table = Table('table', meta, *params2)