Django perform a join on multiple tables - python

I have the following tables:
class A:
field_1 = models.CharField()
field_2 = models.IntegerField()
class B:
a = models.ForeignKey(A, related_name='table_b')
some_other_field = models.CharField()
class C:
b = models.ForeignKey(B, related_name="table_c")
other_field = models.CharField()
Let's assume ids are provided for objects on table A, I need to get all the C objects that are related to table A through table B. I have the following query, which gives me what I need but I am wondering if there is a better way to do this, I was reading into prefetch_related and select_related but can't wrap my head around on how to use them so far:
c_list = C.objects.filter(b__in=B.objects.filter(a__pk__in=table_a_ids))
Also, I would like to group them by other_field.

No need for .select_related(…) or .prefetch_related(…). You can filter with:
c_list = C.objects.filter(b__a_id__in=table_a_ids)

Related

SQL archenemy, map custom query to a class

I have a class that contains author id and article id:
class SearchResults(Base):
__abstract__ = True
author_id = Column(String)
article_id = Column(String)
I would like to return only two columns: author_id, article_id (filtering is excluded from the examples bellow)
This class cannot have a corresponding table while it's an result of a search query.
I am struggling to map it automatically to a class with the SQL archenemy.
When class is passed like an query argument like this:
search_results = db.query(SearchResults).select_from(models.Article).join(models.Author).all()
ORM is failing with an error:
sqlalchemy.exc.InvalidRequestError: SQL expression, column, or mapped entity expected - got '<class 'app.db.models.SearchResults'>'
When columns are specified I am getting Tuple instead of a class:
search_results = db.query(models.Acticle.id, models.Author.author_id).select_from(models.Article).join(models.Author).all()
is it possible to map non-table results to a class?
Please take a look at the Mapping a Class against Arbitrary Selects or Mapping a Class against Multiple Tables sections of the documentation.
For the second example you can get tables for Article and Author by using:
author_table = models.Author.__table__
article_table = modesl.Article.__table__

How to make an Inner Join in django without foreign key?

how to make statement of join on in django 1.11,
i want to create this statement :
select t1.name ,t2.str, t2.num
from table_1 as t1
join table_2 as t2 on t2.product_id = t1.id and t2.section_num = 2;
the models:
class t1(UTModelTS):
alt_keys = product_alt_keys
name = utCharField()
...
class t2(UTModelTS):
alt_keys= [('pr_id', 'section')]
str = utCharField()
num = models.IntegerField()
...
i tried
t1 = t1.objects.filter(**params).exclude(**exclude)
t1 = t1.select_related('t2')
`
but this make no sense since acoridng to django doc :
select_related
Returns a QuerySet that will “follow” foreign-key relationships...
from https://docs.djangoproject.com/en/2.2/ref/models/querysets/ .
No, there isn't an effective / elegant way unfortunately.
although you can use .raw()/RawSQL() method for this exact thing. Even if it could it probably would be a lot slower than raw SQL.
https://docs.djangoproject.com/en/2.2/topics/db/sql/
You should not add on statement. Django's ORM will performs inner join for you automatically.
Imagine this is our models. User Table and Post table.
class User(models.Model):
name = models.CharField(max_length=30)
surname = models.CharField(max_length=50)
class Post(models.Model):
title = models.CharField(max_length=50)
text = models.TextField()
user = models.ForeignKey(to='User', on_delete=models.CASCADE) # this is FK field to users
Usage
qs = Post.objects.select_related('user') # This will performs SQL INNER JOIN.
print(qs.query) # use query attribute to show what query is performed.
This will be generated SQL query
SELECT "myapp_post"."id", "myapp_post"."title", "myapp_post"."text", "myapp_post"."user_id", "myapp_user"."id", "myapp_user"."name", "myapp_user"."surname" FROM "myapp_post" INNER JOIN "myapp_user" ON ("myapp_post"."user_id" = "myapp_user"."id")
Look your models,,,
class t1(UTModelTS):
alt_keys = product_alt_keys
name = utCharField()
t2 = models.ForeignKey(to='t2', on_delete=models.CASCADE)
class t2(UTModelTS):
alt_keys= [('pr_id', 'section')]
str = utCharField()
num = models.IntegerField()
add t2 FK to your t1 Model.
Querying
qs = t1.objects.select_related('t2')
OR
qs = t1.objects.select_related('t2').filter(**lookup_kwargs).
select_related() returns QuerySet object, you can use QuerySet methods after select_related().

How would I do these multiple joins as a Django queryset?

I have this query that joins multiple tables together:
select
p.player_id
, d.player_data_1
, l.year
, l.league
, s.stat_1
, l.stat_1_league_average
from
stats s
inner join players p on p.player_id = s.player_id
left join player_data d on d.other_player_id = p.other_player_id
left join league_averages as l on l.year = s.year and l.league = s.year
where
p.player_id = 123
My models look like this:
class Stats(models.Model):
player_id = models.ForeignKey(Player)
stat_1 = models.IntegerField()
year = models.IntegerField()
league = models.IntegerField()
class Player(models.Model):
player_id = models.IntegerField(primary_key=True)
other_player_id = models.ForeignKey(PlayerData)
class PlayerData(models.Model):
other_player_id = models.IntegerField(primary_key=True)
player_data_1 = models.TextField()
class LeagueAverages(models.Model):
year = models.IntegerField()
league = models.IntegerField()
stat_1_league_average = models.DecimalField()
I can do something like this:
Stats.objects.filter(player_id=123).select_related('player')
to do the first join. For the second join, I tried:
Stats.objects.filter(player_id=123).select_related('player').select_related('player_data')
but I got this error:
django.core.exceptions.FieldError: Invalid field name(s) given in select_related: 'player_data'. Choices are: player
How would I do the third join considering that year and league aren't foreign keys in any of the tables? Thanks!
select_related(*fields) Returns a QuerySet that will “follow” foreign-key relationships, [...]
According to the django documentation select_related follows foreign-key relationships. player_data is neighter a foreign key, nor even an field of Stats. If you'd want to INNER join PlayerData and Player you could follow its foreign-keys. In your case use the
double-underscore to get to PlayerData:
Stats.objects.all()
.select_related('player_id')
.select_related('player_id__other_player_id')
As for joining LeagueAverages: There is not a way to join models without an appropriate foreign key, but to use raw sql. Have a look at a related question: Django JOIN query without foreign key. By using .raw(), your LEFT join (which by the way is also not that easy without using raw: Django Custom Left Outer Join) could also be taken care of.
Quick notes about your models:
Each model by default has an automatically incrementing primary key that can be accessed via .id or .pk. So there is no need to add for example player_id
A models.ForeignKey field references an object not it's id. Therefore it's more intuitive to rename for example player_id to player. If you name your field player django allows you automatically to access it's id via player_id

SQLAlchemy Single Table Inheritance with polymorphic_identity lacks WHERE

The situation:
I have one data table 'my_table' with several columns (lets call them 'a', 'b' and 'c').
Using SQLAlchemy I've created 3 classes within my model:
class MyTableBase():
a = Column(int)
class MyClassOne(MyTableBase):
b = Column(int)
class MyObjectTwo(MyTableBase):
c = Column(int)
I want to override default query(MyClassOne).all() to return only records with a=10 and query(MyClassTwo).all() to return only records with a=20.
That should provide quite elegant way to store many different (but very similar) kinds of objects in one table while playing with them separately at the class/object level.
Any suggestions? Maybe it's my lack of searching skills but simply can't find it :/
Update:
My code now:
class MyTableBase:
__tablename__ = u'my_table_base'
a = Column(VARCHAR(length=20)) #to distinct different types
__mapper_args__ = {"polymorphic_on": a}
class MyClassOne(MyTableBase):
__mapper_args__ = {"polymorphic_identity": "aaaa"}
b = Column(int)
class MyClassTwo(MyTableBase):
__mapper_args__ = {"polymorphic_identity": "bbbb"}
c = Column(int)
My DB table has two records - one has the value of "aaaa" and the other "bbbb" in the "a" column. Query for MyClassOne and MyClassTwo returns both rows. Any ideas?
UPDATE:
To select the data I do use:
cls_query = Session.query(MyClassOne)
print 'cls_query: ',cls_query
cls_query.all()
The print result is:
cls_query: SELECT my_table_base.id AS my_table_base_id,
my_table_base.a AS my_table_base_a,
my_table_base.b AS my_table_base_b
FROM my_table_base
Please notice it's taking only columns from required class MyClassOne (column c is not present in the SELECT statement), but still it lacks WHERE part.
As found in here:
Flask-SQLAlchemy Single Table Inheritance
The correct answer is __tablename__ = None in all subclasses.
class MyTableBase:
__tablename__ = u'my_table_base'
a = Column(VARCHAR(length=20)) #to distinct different types
__mapper_args__ = {"polymorphic_on": a}
class MyClassOne(MyTableBase):
__tablename__ = None
__mapper_args__ = {"polymorphic_identity": "aaaa"}
b = Column(int)
class MyClassTwo(MyTableBase):
__tablename__ = None
__mapper_args__ = {"polymorphic_identity": "bbbb"}
c = Column(int)
This information is not present in the relevant SQLAlchemy docs :(
Works fine now!

Turning SQL expression into SQLAlchemy query

I have this SQL expression that I'm trying to write in SQL Alchemy
select * from candidates1 c
inner join uploaded_emails1 e
on c.id=e.candidate_id
group by e.thread_id
How would I go about doing that?
The execute method can be used to run raw SQL, like so:
from sqlalchemy import text
sql = text('select * from candidates1 c inner join uploaded_emails1 e on c.id=e.candidate_id group by e.thread_id')
result = db.engine.execute(sql)
... do stuff ...
If you have some models that you're working with, you could use the relationship field type to create a one-to-many relationship between the Candidate and the UploadedEmail, like so:
class Candidate(Base):
__tablename__ = 'candidates1'
id = Column(Integer, primary_key=True)
uploaded_emails = relationship("UploadedEmail", lazy='dynamic')
class UploadedEmail(Base):
__tablename__ = 'uploaded_emails1'
id = Column(Integer, primary_key=True)
candidate_id = Column(Integer, ForeignKey('candidate.id'))
thread_id = Column(Integer)
And in your code, you might use that like this (including the group_by)
candidate_id = 1
c = Candidate.query.filter_by(id=candidate_id).first()
thread_id_results = c.uploaded_emails.with_entities(UploadedEmail.thread_id).group_by(UploadedEmail.thread_id).all()
thread_ids = [row[0] for row in thread_id_results]
Note that you have to use the .with_entities clause to specify the columns you would like to select, and then the fact that you are specifying the thread_id column. If you don't do this, you'll get errors along the lines of "Expression #X of SELECT list is not in GROUP BY clause and contains nonaggregated column ... which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by".
Sorry I didn't provide enough information to answer the question. This ended up working:
x = db_session.query(Candidate1, Uploaded_Emails1).filter(Candidate1.id == Uploaded_Emails1.candidate_id).group_by(Uploaded_Emails1.thread_id).all()

Categories

Resources