sqlalchemy: How do I do this simple subquery in sqlalchemy? - sqlalchemy

Two models:
class this(DeclarativeBase):
__tablename__ = 'this'
'Columns'
id = Column(Integer, primary_key=True)
'Relations'
that = relation('that', foreign_keys=id, backref='this')
class that(DeclarativeBase):
__tablename__ = 'that'
'Columns'
id = Column(Integer, primary_key=True)
this_id = Column(Integer, ForeignKey('this.id'))
I want to run this simple SQL Query:
SELECT id, (SELECT COUNT(*) FROM that WHERE this_id = this1.id) AS thatcount FROM this AS this1
I can achieve the same RESULTS in sqlalchemy by doing:
results = session.query(model.this.id,
func.count(model.that.id).label('thatcount')) \
.join(model.that) \
.group_by(model.this.id)
BUT, the resultant SQL is not what I want:
SELECT
this.id AS this_id,
count(that.id) AS thatcount
FROM this
INNER JOIN that ON this.id = that.this_id
GROUP BY this.id
I am missing a couple of fundamental ideas in sqlalchemy...
1) How do I "label" tables in FROM clauses?
2) How do I create subqueries that reference results from the parent query?
Hopefully this is something simple that I am just not understanding, as I'm relatively new to sqlalchemy... Of course I can just run raw SQL, but I am impressed by sqlalchemy and I'm sure this is possible.
Any help would be much appreciated!

qry = select([
this.id,
select([func.count().label('xx')], this.id == that.this_id).as_scalar().label('thatcount'),
])
produces:
SELECT this.id, (SELECT count(*) AS xx
FROM that
WHERE this.id = that.this_id) AS thatcount
FROM this
To answer your questions directly:
use label()
you do not need that, you just use the whereclause of the select to indicate the join condition between the main query and the subquery.
Note that I prefer func.count(that.id) to func.count() though, as it is more explicit.

Related

Adding where clause after HANA placeholders in SQLAlchemy

I'm trying to construct a query like this:
SELECT "Inventory".item_id, "Inventory".region
FROM "Inventory" ('PLACEHOLDER' = ('$$P_Param$$', '0'))
WHERE "Inventory".region = :region_1
My select statement is adding the HANA placeholders at the end though:
select_stmt = select(
inventory.item_id,
inventory.region
).suffix_with(placeholders).where(inventory.region== 'USA')
SELECT "Inventory".item_id, "Inventory".region
FROM "Inventory"
WHERE "Inventory".region = :region_1 ('PLACEHOLDER' = ('$$P_Param$$', '0'))
How do I suffix the from clause with the placeholders and add the where clause afterwards?
class Inventory(Base):
__tablename__ = 'Inventory'
__table_args__ = {'schema': '_SYS_BIC'}
date = Column(DateTime, primary_key=True)
item_id = Column(Integer, primary_key=True)
region = Column(String)
placeholder = "('PLACEHOLDER' = ('$$P_Param$$', '0'))"
The SAP Hana dialect does not seem to support placeholders.
As you saw suffix_with does exactly what it says, it appends a suffix to the full query.
Without dialect support, you could try using a from_statement with text which will allow to load what you need using your own SQL statements.

SqlAlchemy - make a query where Relationship Attribute is a list

I have two models:
class Profile(Base):
__tablename__ = 'profiles'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='profiles_list',
secondary=stageP_profile
)
class Project(Base):
__tablename__ = 'projects'
id = Column(Integer, primary_key=True)
...
stagesP_list = relationship(
'StageP',
back_populates='projects_list',
secondary=stageP_project
)
I need to select Profiles for which at least one value of the Profile.stagesP_list is contained in the project.stagesP_list.
Please help to compose the query or indicate the direction in which to search.
If you have project instance loaded, you can compose the following query:
project = ...
stageP_ids = [obj.id for obj in project.stagesP_list]
query = session.query(Profile).filter(
Profile.stagesP_list.any(StageP.id.in_(stageP_ids))
)
You can also perform joins on the database directly from having only project_id:
query = (
session.query(Profile)
.join(StageP, Profile.stagesP_list)
.join(Project, StageP.projects_list)
.where(Project.id == project_id)
.distinct()
)

Execute a double inner join in sqlalchemy

I have this SQL-Query, which I would like to turn into sqlalchemy code.
I'm working here with an already existing database in production which makes it difficult to change.
SELECT B.IDTestung, B.Ber1_Konzentration,
T.Testungstyp, T.Timestamp,
K.Name_Kl, K.Vorname_Kl
FROM BCRS AS B
INNER JOIN
ttestungen AS T on B.IDTestung = T.IDTESTUNG
INNER JOIN
tklienten K on T.IDKLIENT = K.IDKLIENT
So, basically there's the table BCRS, which contains an ID for the "Testung" and the table ttestungen itself has an ID to identify the client. So there are two inner joins to combine three tables.
How do I write this in sqlalchemy?
What I got so far:
from auswertungen.data.data import Tklienten, BCR, Ttestungen
tests = session.query(BCR).join(Ttestungen.tklienten).all()
This makes the first join, but I don't know how to make the second. I tried to just add another .join(...) after the first one, but that didn't work.
Here's is the DB-Definitions (excerpt):
(automatically created with sqlacodegen)
class BCR(Base):
__tablename__ = 'BCRS'
IDBCRS_Testung = Column(INTEGER(11), primary_key=True)
IDTestung = Column(ForeignKey('ttestungen.IDTESTUNG', ondelete='CASCADE'), unique=True)
[...]
ttestungen = relationship('Ttestungen')
class Ttestungen(Base):
__tablename__ = 'ttestungen'
IDTESTUNG = Column(INTEGER(10), primary_key=True)
IDKLIENT = Column(ForeignKey('tklienten.IDKLIENT'), index=True)
[...]
tklienten = relationship('Tklienten')
You can try this below:
test = session.query(BCR).join(Ttestungen, BCR.IDTestung == Ttestungen.IDTESTUNG).join(Tklienten, Ttestungen.IDKLIENT == Tklienten.IDKLIENT).all()

How to make this query in sqlalchemy?

SELECT
maintener.*,
(SELECT COUNT(*)
FROM device d
WHERE d.in_stock_maintener_id = maintener.id) AS in_stock_devices
FROM maintener;
I'm creating a report that show all mainteners but i need to show the number of devices that each one of that mainteners has by looking at the devices model reference in_stock_maintener_id;
I have this models in my persist sqlalchemy.
class Maintener(persist.Base):
__tablename__ = 'maintener'
id = Column(Integer, primary_key=True)
name = Column(String(255))
document_number = Column(String(30))
phone_1 = Column(String(12))
phone_2 = Column(String(12))
email = Column(String(255))
class Device(persist.Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
serial = Column(String(45))
in_stock = Column(SmallInteger)
in_stock_maintener_id = Column(ForeignKey(u'maintener.id'), nullable=True, index=True)
in_stock_maintener = relationship(u'Maintener', lazy='noload', \
primaryjoin='Device.in_stock_maintener_id == Maintener.id')
If anyone could help me, i'll be grateful =)
sq = (
session
.query(func.count())
.select_from(Device)
.filter(Device.in_stock_maintener_id == Maintener.id)
).as_scalar()
q = session.query(Maintener, sq.label('in_stock_devices'))
Query above will return an enumerable of tuple(Maintener, Integer).
If you would like to have columns instead (as per your comment), then you can either specify the columns you want in the query implicitly:
q = session.query(Maintener.id, Maintener.name, sq.label('in_stock_devices'))
or if you would like all columns (as in SELECT *), then you could query the Table instead of the mapped entity:
q = session.query(Maintener.__table__, sq.label('in_stock_devices'))
Above I assumed that you use declarative extension.

Usage of a COUNT(DISTINCT field) with a GROUP BY clause in Django

Problem
I want to use a COUNT(DISTINCT field) with a GROUP BY clause in Django. As I understand, the COUNT(DISTINCT... can only be achieved by using an extra for the query set.
My simplified model is :
class Site(models.Model):
name = models.CharField(max_length=128, unique=True)
class Application(models.Model):
name = models.CharField(max_length=64)
version = models.CharField(max_length=13, db_index=True)
class User(models.Model):
name = models.CharField(max_length=64)
site = models.ForeignKey(Site, db_index=True)
class Device(models.Model):
imei = models.CharField(max_length=16, unique=True)
applications = models.ManyToManyField(Application, null=True, db_index=True, through='ApplicationUsage')
user = models.ForeignKey(User, null=True, db_index=True)
class ApplicationUsage(models.Model):
activity = models.DateField(db_index=True)
application = models.ForeignKey(Application)
device = models.ForeignKey(Device)
My goal is to have a liste of Site objects with a count of distinct device for each site given an application activity through a time period, something like
stats_site.name deviceCount
ALBI 32
AMPLEPUIS 42
...
I try this code :
qs = models.Site.objects.filter(user__device__applicationusage__activity__range=[startDay, endDay])\
.extra(select={'deviceCount' : 'COUNT(DISTINCT `stats_device`.`id`)'})\
.values('name', 'deviceCount')\
The generated SQL is :
SELECT (COUNT(DISTINCT stats_device.id)) AS deviceCount, stats_site.name
FROM stats_site
INNER JOIN stats_user ON (stats_site.id = stats_user.site_id)
INNER JOIN stats_device ON (stats_user.id = stats_device.user_id)
INNER JOIN stats_applicationusage ON (stats_device.id = stats_applicationusage.device_id)
WHERE stats_applicationusage.activity BETWEEN '2013-07-01' AND '2013-07-03'
And the result is obviously wrong since it lacks the GROUP BY clause, which should be GROUP BY stats_site.name
The problem is: I don't know how to add the correct GROUP BY using the annotate function or other.
Solution
Using distinct=True on the Count function with annotate:
qs = models.Site.objects.filter(habileouser__device__applicationusage__activity__range=[startDay, endDay])\
.annotate(deviceCount=Count('habileouser__device', distinct=True))\
.values('name', 'deviceCount')
The annotate method of a queryset will calculate an aggregate value for each element of the queryset, and when used after a values call will aggregate over the values of the values. I think this should work:
qs = models.Site.objects.filter(
user__device__applicationusage__activity__range=[startDay, endDay]
).values('name').annotate(Count('user__device', distinct=True))
If you have an ordering specified you may need to remove it as discussed here:
https://docs.djangoproject.com/en/dev/topics/db/aggregation/#interaction-with-default-ordering-or-order-by