SQLalchemy to get data from multiple table in single object - python

I have 3 tables, users, projects and project_users is_admin table and I am trying to write ORM to get data from them.
My Models are here: https://pastebin.com/ZrmhKyNL
In simple SQL, we could join and select particular columns and get the desired output. But in ORM when I write query like this:
sql = """ select * from
projects p, users u, projects_users pu
where
p.name = '%s' and
p.id = pu.project_id and
pu.user_id = u.id and
p.is_active = true and
u.is_active = true
""" % project_name
and it works well and returns response in this format:
[
{
All columns of above 3 tables.
}
]
But when I try to convert this to sqlalchamey ORM, it doesn't work well:
return (
db.query(User)
.filter(Project.id == ProjectsUser.project_id)
.filter(ProjectsUser.user_id == User.id)
.filter(Project.is_active == True)
.filter(User.is_active == True)
.filter(Project.name == project_name)
.all()
)
I want is_admin value to be returned along with user object. This seems very common use case, but I couldn't find any solution related to SQLalchemy ORM.

If this is a one-to-one relationship, meaning that for every user you can have one and only one entry in the admin table, you could add a column to serve as back-reference:
class User(Base):
id = ...
name = ...
last_login = ...
admin = relationship("Admin", uselist=False, back_populates="user")
class Admin(Base):
id = ...
is_admin = ...
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship("User", back_populates="admin")
Then you can query only the table user, and access the values from the relationship via user.admin.is_admin.
Read more:
One To One - SqlAlchemy docs
relationship.back_populates - SqlAlchemy docs

Related

how can we update one to one relationship models db data from django?

i am new to django and i created onetoOneField relationship model with inbuilt User model of django but i cant figure out how can i update that model class table value.
My model
class category(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
TYPES = [
("BASIC","BASIC"),
("PLUS","PLUS"),
("PRO","PRO")
]
User_Type = models.CharField(max_length=40,choices=TYPES,default="BASIC")
Product_Limit = models.IntegerField(default=0)
Amount_Paid = models.IntegerField(default=0)
Tried these 2 cases but getting some error
#key = category.objects.update_or_create(id=request.user.id,User_Type= request.user.category.User_Type,Product_Limit=2,Amount_Paid=request.user.category.Amount_Paid)
user = request.user.category.update_or_create(Product_Limit=10)
Try this:
category = Category.objects.filter(user=request.user).update_or_create(
user=user, Product_Limit=10
)
This will make two SQL queries:
First, Django will make a SELECT ... FROM category WHERE user_id = .... As category and user has a one to one relationship, this should return 0 or 1 row.
If the first query has no record, Django will make an INSERT INTO category query with user_id=request.user.id and Product_Limit=10, the other fields following the default values.
If the first query has a record, Django will make a UPDATE category SET user_id = ..., Product_Limit = ... WHERE user_id = ... query to update the Product_Limit field.

SQLAlchemy filtering Children in one-to-many relationships

I have defined my models as:
class Row(Base):
__tablename__ = "row"
id = Column(Integer, primary_key=True)
key = Column(String(32))
value = Column(String(32))
status = Column(Boolean, default=True)
parent_id = Column(Integer, ForeignKey("table.id"))
class Table(Base):
__tablename__ = "table"
id = Column(Integer, primary_key=True)
name = Column(String(32), nullable=False, unique=True)
rows=relationship("Row", cascade="all, delete-orphan")
to read a table from the db I can simply query Table and it loads all the rows owned by the table. But if I want to filter rows by 'status == True' it does not work. I know this is not a valid query but I want to do something like:
session.query(Table).filter(Table.name == name, Table.row.status == True).one()
As I was not able to make the above query work, I came up with a new solution to query table first without loading any rows, then use the Id to query Rows with filters and then assign the results to the Table object:
table_res = session.query(Table).option(noload('rows')).filter(Table.name == 'test').one()
rows_res = session.query(Row).filter(Row.parent_id == 1, Row.status == True)
table_res.rows = rows_res
But I believe there has to be a better way to do this in one shot. Suggestions?
You could try this SQLAlchemy query:
from sqlalchemy.orm import contains_eager
result = session.query(Table)\
.options(contains_eager(Table.rows))\
.join(Row)\
.filter(Table.name == 'abc', Row.status == True).one()
print(result)
print(result.rows)
Which leads to this SQL:
SELECT "row".id AS row_id,
"row"."key" AS row_key,
"row".value AS row_value,
"row".status AS row_status,
"row".parent_id AS row_parent_id,
"table".id AS table_id,
"table".name AS table_name
FROM "table" JOIN "row" ON "table".id = "row".parent_id
WHERE "table".name = ?
AND "row".status = 1
It does a join but also includes the contains_eager option to do it in one query. Otherwise the rows would be fetched on demand in a second query (you could specify this in the relationship as well, but this is one method of solving it).

Flask-SQLAlchemy: How to return a list of single objects after performing a join operation?

I have two tables Users and Organization.
I have the following SQL mapped classes:
class User(db.Model):
__tablename__ = 'user'
id = db.Column('id', db.Integer, primary_key=True)
first_name = db.Column('first_name', db.String)
second_name = db.Column('second_name', db.String)
last_name = db.Column('last_name', db.String)
# organization_id = db.Column('organization_id', db.Integer)
organization_id = db.Column('organization_id',db.Integer,db.ForeignKey('organization.id'),nullable=False)
# organization = db.relationship('Organization',backref='user',lazy=False)
email = db.Column('email', db.String)
mobile_no = db.Column('mobile_no', db.String)
designation = db.Column('designation', db.String)
role_id = db.Column('role_id', db.Integer)
password = db.Column('password', db.String)
class Organization(db.Model):
__tablename__ = 'organization'
id = db.Column('id',db.Integer,primary_key=True)
name = db.Column('name',db.String)
type_id = db.Column('type_id',db.Integer)
ward_id = db.Column('ward_id',db.Integer)
attrs = ['id','name','type_id','ward_id']
user_organization = db.relationship('User',backref='organization',lazy=False)
I want to perform a join operation on User.onrganization_id and Organization.id with the results looking like this
user.id | user.first_name organization.name | user.email | user.mobile_no
I tried this code:
q = db.session.query(User,Organization).join(Organization).all()
This give me a result as list with collection of object:
[(<User 2>, <Organization 1>)]
I want the return type to be a list of single object and not a collection of objects. Like this
[(<UsersAndOrganization1>)] //Name of the object doesn't have to be the same
Since you have lazy=False on your relationship, SQLAlchemy will perform the join automatically when you do User.query.all(). In the query results each user will have organization property populated, so you will be able to access its fields as, for example, user.organization.name.
Regardless of the lazy option in the relationship configuration, you can always achieve the same effect by explicitly specifying load options in the query:
users = User.query.options(db.joinedload(Organization)).all()
More info can be found in the docs here (lazy=False is synonym to lazy='joined').
Also, try enabling SQLALCHEMY_ECHO in your Flask app config and see the actual SQL queries emitted by SQLAlchemy. It is always very useful to see what happens under the hood.

SQLAlchemy ignore filter when related column does not exist

I am having trouble constructing a query using SQLalchemy. Here is a simplified representation of the models I have defined:
Models
Project
class Project(Base):
__tablename__ = 'project'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# User associations
users = relationship(
'User',
secondary='user_project_association'
)
User
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# Project associations
projects = relationship(
'Project',
secondary='user_project_association'
)
User <-> Project (association)
class UserProjectAssociation(Base):
__tablename__ = 'user_project_association'
# User association.
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
user = relationship('User', backref='project_associations')
# Project association.
project_id = Column(Integer, ForeignKey('project.id'), primary_key=True)
project = relationship('Project', backref='user_associations')
Query
I want to perform a query on the projects table such that the result contains information about the projects as well as information about the associated users - if there are any. I am including a filter based on the user name. I am eventually going to send the result as JSON via a REST API so I would prefer the results as python {dict} objects rather than SQLAlchemy objects. The query I am performing looks like:
# Add return fields
query = session.query(
Project.id,
Project.name,
User.id.label('users.id'),
User.name.label('users.name')
)
# Add join statements
query = query.outerjoin(User, Project.users)
# Add filters
query = query.filter(
Project.name == 'proj1',
User.name != 'jane.doe' # <--- I think this is causing the issue.
)
# Execute
results = query.all()
data = [result._asdict() for result in results]
print(data)
Results
The database contains a project called proj1 which doesn't have any associated users. In this particular scenario, I am filtering on a user column and the user association does not exist. However, I am still expecting to get a row for the project in my results but the query returns an empty list. The result I am expecting would look something like this:
[{'id': 1, 'name': 'proj1', 'users.id': None, 'users.name': None}]
Can someone explain where I am going wrong?
You have to account for the NULL values that result from the left join, since != compares values and NULL is the absence of value, so the result of NULL != 'jane.doe' is NULL, not true:
query = query.filter(
Project.name == 'proj1',
or_(User.name == None, User.name != 'jane.doe')
)
Note that SQLAlchemy handles equality with None in a special way and produces IS NULL. If you want to be less ambiguous you could also use User.name.is_(None).

LEFT JOIN with OR condition in python django

All I want is to write the following SQL query in Python Django syntax. I researched a lot but could not get the result I want. Could you please help me.
SELECT * FROM thread
LEFT JOIN user
ON (thread.user1_id = user.id OR thread.user2_id = user.id)
WHERE user.id = 9
My model:
class Thread(models.Model):
last_message = models.TextField(max_length=100)
user1_id = models.IntegerField()
user2_id = models.IntegerField()
The exact query cannot be created using the Django ORM without resorting to some raw SQL. The problem is the OR in the join condition.
But, since your not selecting any rows from the user table, you can create a different query with the same results.
Start by defining your models using ForeignKeys.
from django.db import models
from django.contrib.auth.models import User
class Thread(models.Model):
last_message = models.TextField(max_length=100)
user1 = models.ForeignKey(User)
user2 = models.ForeignKey(User)
class Meta:
db_table = 'thread' # otherwise it will be: appname_thread
Then you can preform the query.
from django.db.models import Q
threads = Thread.objects.filter(Q(user1__id=9) | Q(user2__id=9))
The resulting SQL will be something like this:
SELECT * FROM thread
LEFT JOIN user AS user1
ON (thread.user1_id = user1.id)
LEFT JOIN user AS user2
ON (thread.user2_id = user2.id)
WHERE (user1.id = 9 OR user2.id = 9)
You can improve this by avoiding the join altogether.
# note the single underscore!
threads = Thread.objects.filter(Q(user1_id=9) | Q(user2_id=9))
Generating the following SQL.
SELECT * FROM thread
WHERE (user1_id = 9 OR user2_id = 9)
If you need to also obtain the user instances in the same query, in which case you really need the join, you have two options.
Either use the first query that includes joins and afterwards select the correct user based in the id in Python.
for thread in threads:
if thread.user1.id == 9:
user = thread.user1
else:
user = thread.user2
Or use raw SQL to get the exact query you want. In this case, you can use the models without the ForeignKeys as you defined them.
threads = Thread.objects.raw('''
SELECT *, user.id AS user_id, user.name as user_name FROM thread
LEFT JOIN user
ON (thread.user1_id = user.id OR thread.user2_id = user.id)
WHERE user.id = 9''')
for thread in threads:
# threads use the ORM
thread.id
# user fields are extra attributes
thread.user_id
thread.user_name

Categories