django complicated agregate in three tables - sql

I have a three tables with one main (Transaction) and two dependant(TransactionDialogMessages, TransactionDialogLastAccess).
class Transaction(models.Model):
status = models.CharField(max_length = 3, default = 'NEW')
user1 = models.ForeignKey(User, related_name='user1', blank=True, null=True, on_delete=models.SET_NULL)
user2 = models.ForeignKey(User, related_name='user2', blank=True, null=True, on_delete=models.SET_NULL)
some_info = models.DateTextField()
class TransactionDialogMessages(models.Model):
transaction = models.ForeignKey(Transaction)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
message = models.TextField()
create_datetime = models.DateTimeField(auto_now = True)
class TransactionDialogLastAccess(models.Model):
transaction = models.ForeignKey(Transaction)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
access_time = models.DateTimeField()
I want to create one query with complex agregation on this three tables, and result in hash (or queryset) with such structure:
{'transaction_id', 'status', 'user1', 'user2', 'some_info', 'count_of_new_messages'}
where count_of_new_messages is agregation field, which computed as
count (TransactionDialogMessages(transaction = transaction_id, user = request.user, create_datetime > TransactionDialogLastAccess(transaction = transaction_id, user = request.user).access_time))
Is it real?
Updated:
I created SQL query, which serves my purpose:
SELECT a.id, a.status, ... , b.COUNT_OF_NEW_MESSAGES FROM transaction a
LEFT JOIN (SELECT b.transaction_id, COUNT(b.transaction_id) AS COUNT_OF_NEW_MESSAGES
FROM transactiondialogmessages b
LEFT JOIN transactiondialoglastaccess c
ON b.transaction_id = c.transaction_id
WHERE b.create_datetime > c.access_time OR c.access_time IS NULL
GROUP BY b.transaction_id ) b
ON a.id = b.transaction_id;
But how it's realize on django orm (without raw)?

Related

Issue when creating on-to-many relationships in SQLAlchemy database

I created a Flask app with a database, with following classes:
db = SQLAlchemy(app)
class Category(db.Model):
__tablename__ = 'Category'
children = relationship("Child")
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Text())
icon = db.Column(db.Text())
subcategories = db.relationship('Subcategory', backref="category")
def __init__(self, name, subcategories, icon):
self.name = name
self.color = icon
self.subcategories = subcategories
class Subcategory(db.Model):
__tablename__ = 'Subcategory'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Text())
color = db.Column(db.Text())
reward_points = db.Column(db.Integer())
category = db.Column(db.Text())
tasks = db.relationship('Task', backref="subcategory")
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
def __init__(self, name, color, reward_points, category, tasks, category_id):
self.name = name
self.color = color
self.reward_points = reward_points
self.category = category
self.tasks = tasks
self.category_id = category_id
class Task(db.Model):
__tablename__ = 'Task'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
task = db.Column(db.Text())
start = db.Column(db.DateTime())
end = db.Column(db.DateTime())
duration = db.Column(db.Integer)
scheduled = db.Column(db.DateTime())
created = db.Column(db.DateTime())
status = db.Column(db.Text())
category = db.Column(db.Text())
subcategory = db.Column(db.Text())
tags = db.Column(db.Text())
subcategory_id = db.Column(db.Integer, db.ForeignKey('subcategory.id'))
def __init__(self, task, start, end, duration, category, subcategory, tags, created, status, scheduled, subcategory_id):
self.task = task
self.start = start
self.end = end
self.duration = duration
self.category = category
self.subcategory = subcategory
self.tags = tags
self.created = created
self.status = status
self.scheduled = scheduled
self.subcategory_id = subcategory_id
My goal is to create one to many relationships between the classes. I am trying to run the db.create_all() command, but am getting following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'Subcategory.category_id' could not find table 'category' with which to generate a foreign key to target column 'id'
What am I doing wrong? Other questions on Stackoverflow with similar issues did not resolve my error.
Changing
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
to
category_id = db.Column(db.Integer, db.ForeignKey('Category.id'))
solved the issue.
Changing "category.id" to "category.c.id" solved the issue as well. If anyone knows why, pls let me know ;-)

Convert SQL query in Django model format

I'm trying to convert an SQL query into django format but as I'm quite new to django I'm having some trouble.
My query:
select make_name, count(*) as count from main_app_make as make
join main_app_model as model on make.id = model.make_id
join main_app_vehicle as vehicle on model.id = vehicle.model_id
group by make.make_name
The result:
Audi 1
Mercedes-Benz 2
My models:
class Make(models.Model):
make_name = models.CharField(max_length=50)
make_logo = models.CharField(max_length=400)
def __str__(self):
return self.make_name
class Model(models.Model):
model_name = models.CharField(max_length=50)
make = models.ForeignKey(Make, on_delete=models.CASCADE)
def __str__(self):
return self.model_name
class Vehicle(models.Model):
type = models.ForeignKey(VehicleType, on_delete=models.CASCADE)
model = models.ForeignKey(Model, on_delete=models.CASCADE)
body_type = models.ForeignKey(Body, on_delete=models.CASCADE)
...
This is what I tried:
options = Make.objects.all().values('make_name').annotate(total=Count('make_name'))
I think you need to include the children models in the Count :
options = Make.objects.values('make_name').annotate(total=Count('model_set__vehicle_set'))
Reference : https://docs.djangoproject.com/en/stable/topics/db/aggregation/#following-relationships-backwards

Selecting only non null fields from the filter rows django

I have this query which are getting the required rows which i want but the problem is most fields/columns have null values I just want to get only those fields from these rows which have non null values
queryset = User.objects.filter(email__exact=email)
Here, is my model
class User(models.Model):
email = models.CharField(max_length=50, null=True, blank=True)
password = models.CharField(db_index=True, max_length=40, null=True)
source = models.CharField(default='unknown', max_length=150, null=True)
domain = models.CharField(max_length=50, null=True, blank=True)
before_at = models.CharField(max_length=255, null=True, blank=True)
username = models.CharField(db_index=True, max_length=150, null=True, blank=True)
hash = models.CharField(max_length=255, null=True, blank=True)
ipaddress = models.CharField(max_length=50, null=True, blank=True)
phonenumber = models.CharField(max_length=100, null=True, blank=True)
def __str__(self):
if self.email != None:
return self.email
elif self.username != None:
return self.username
Alternatively, You can use this:
for q in queryset:
if q.email == None or q.username == None:
queryset = queryset.exclude(email=q.email)
I hope that this would solve your problem.
You can obtain a list of NULLable fields with:
nullable_fields = [f.name for f in User._meta.get_fields() if f.null]
With these fields, we can make a filter:
nonnullq = Q(
*[f('{field}__isnull', False) for field in nullable_fields],
_connector=Q.AND
)
We can use this filter to exclude all records where one if the fields is NULL:
queryset = User.objects.filter(nonnullq, email=email)
That being said, it is rather odd that nearly all fields are NULLable. Often only a small amount is NULLable, since it often makes models more complicated.

SqlAlchemy outerjoin query problem filtering [duplicate]

This question already has answers here:
how left outer join in sqlalchemy?
(1 answer)
sqlalchemy filter children in query, but not parent
(2 answers)
Closed 2 years ago.
I'm using sqlalchemy, and I have problem with this specific query.
I have data_template, devices_data, and device. Each device have value for each data in data_template. Those values are stored in devices_data. I want to list data_template for one device with values that this device has. If there is no value for some data_template, show None.
It has something to do with outerjoin. Here is my model:
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class DataTemplate(Base):
__tablename__ = 'data_template'
id = Column(Integer, primary_key=True)
name = Column(String)
def __repr__(self):
return f"<DataTemplate(name={self.name})>"
class Device(Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
name = Column(String)
def __repr__(self):
return f"<Device(name={self.name})>"
class DeviceData(Base):
__tablename__ = 'device_data'
id = Column(Integer, primary_key=True)
value = Column(Integer, nullable=False)
data_name_id = Column(Integer, ForeignKey(DataTemplate.id), nullable=False)
device_id = Column(Integer, ForeignKey(Device.id), nullable=False)
data_template = relationship('DataTemplate', backref='device_data')
device = relationship('Device', backref='device_data')
def __repr__(self):
return f"<DeviceData(device={self.device.name}, data_template={self.data_template.name}, value={self.value})>"
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
Session.configure(bind=engine)
session = Session()
dev1 = Device(name='Receiver')
dev2 = Device(name='TV')
dat_temp1 = DataTemplate(name="height")
dat_temp2 = DataTemplate(name="width")
dat_temp3 = DataTemplate(name="length")
session.add_all([dev1, dev2, dat_temp1, dat_temp2, dat_temp3])
dd1 = DeviceData(value=100, data_template=dat_temp1, device=dev1)
dd2 = DeviceData(value=50, data_template=dat_temp2, device=dev1)
dd3 = DeviceData(value=200, data_template=dat_temp1, device=dev2)
dd4 = DeviceData(value=40, data_template=dat_temp2, device=dev2)
dd5 = DeviceData(value=30, data_template=dat_temp3, device=dev2)
session.add_all([dd1, dd2, dd3, dd4, dd5])
s = session.query(DataTemplate, DeviceData).outerjoin(DeviceData).filter(DeviceData.device==dev1)
for x in s:
print(x)
with this outerjoin I'm getting:
(<DataTemplate(name=height)>, <DeviceData(device=Receiver, data_template=height, value=100)>)
(<DataTemplate(name=width)>, <DeviceData(device=Receiver, data_template=width, value=50)>)
and is equal to:
SELECT "d"."id", "val"."id"
FROM "DataTemplate" "d"
LEFT JOIN "DeviceData" "val"
ON "d"."id" = "val"."data_name_id"
WHERE "val"."device_id" = 1
but I want to get:
(<DataTemplate(name=height)>, <DeviceData(device=Receiver, data_template=height, value=100)>)
(<DataTemplate(name=width)>, <DeviceData(device=Receiver, data_template=width, value=50)>)
(<DataTemplate(name=length)>, None)
and that query should be:
SELECT "d"."id", "val"."id"
FROM "DataTemplate" "d"
LEFT JOIN "DeviceData" "val"
ON "d"."id" = "val"."data_name_id" AND "val"."device_id" = 1
how do I write this specific query?

Query that fetch messages for a specific user

I have a messages table like the following, how to fetch messages for a specific user, that is, fetch one most recent message with one user.For example, user1 has messages with user2 and user3,how to fetch one most recent message between user1 and user2, one most recent message between user1 and user3?
class Message(Base):
__tablename__ = 'messages'
id = Column(Integer(), primary_key=True)
sender_id = Column(Integer(), ForeignKey('users.id'))
body = Column(Text())
recipient_id = Column(Integer())
created_at = Column(DateTime())
updated_at = Column(DateTime())
The SQL I could figure out is following, but it is not enough.Could you help me? Thanks a lot,:).
session.query(Message).\
filter(or_(Message.user_id==user.id, Message.recipient_id==user.id))
Try
from sqlalchemy import desc
session.query(Message).\
filter(or_(Message.user_id==user.id, Message.recipient_id==user.id)).\
order_by(desc(Message.created_at)).\
first()
Code below should give you an example using created_at as the decision column for the latest. You can easily change it to be updated_at.
Note that for two users (A and B) it will return both latest messages: latest from A->B and latest B->A.
from datetime import date
from sqlalchemy import create_engine, Column, Text, Integer, DateTime, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.sql import func, and_
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///:memory:', echo=True)
session = sessionmaker(bind=engine)()
Base = declarative_base(engine)
class User(Base):
__tablename__ = 'users'
id = Column(Integer(), primary_key=True)
name = Column(Text())
class Message(Base):
__tablename__ = 'messages'
id = Column(Integer(), primary_key=True)
sender_id = Column(Integer(), ForeignKey('users.id'))
body = Column(Text())
recipient_id = Column(Integer(), ForeignKey('users.id'))
created_at = Column(DateTime())
updated_at = Column(DateTime())
sender = relationship(User, backref="messages_sent", foreign_keys=sender_id)
recipient = relationship(User, backref="messages_recv", foreign_keys=recipient_id)
Base.metadata.create_all()
users = u1, u2, u3 = [
User(name='user1'),
User(name='user2'),
User(name='user3'),
]
session.add_all(users)
session.add_all([
Message(sender=u1, recipient=u2, body="bod12-a", created_at = date(2014, 1, 13)),
Message(sender=u1, recipient=u2, body="bod12-X", created_at = date(2014, 1, 3)),
Message(sender=u2, recipient=u3, body="bod23-X", created_at = date(2014, 1, 13)),
Message(sender=u2, recipient=u3, body="bod23-a", created_at = date(2013, 1, 13)),
])
session.flush()
subq = (session
.query(Message.sender_id, Message.recipient_id,
func.max(Message.created_at).label("max_created_at"))
.group_by(Message.sender_id, Message.recipient_id)
).subquery("subq")
q = (session
.query(Message)
.join(subq, and_(
Message.sender_id == subq.c.sender_id,
Message.recipient_id == subq.c.recipient_id,
Message.created_at == subq.c.max_created_at)
)
).all()
for x in q:
print(x.sender_id, x.recipient_id, x.body)