Django Model: Getting result from a table using unique foreign key - sql

I have two models that look like this:
class Author(models.Model):
name = models.CharField(max_length=200)
class Book(models.Model):
author = models.ForeignKey('Author', on_delete=models.CASCADE)
isbn_id = models.CharField(max_length=50, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
How can I make a query such that:
I get each author with the total number of books created, number of books with an isbn.
Each time a book is created for an author, I can get the last_created, last_modified.
This is an example of the result I am trying to achieve;
s/n| author| no_books_with_isbn| all_books| last_created| last_modified
1. Faye 2 2 ... ....
2. Soya 2 5
3. Jake 6 6
4. Gurj 4 4
5. Zoom 2 10

You need to annotate lots of aggregations to your queryset (Reference: Aggregation [Django Docs]). To get the counts you can use the Count [Django Docs] function and for the last_created / last_modified you can use the Max [Django Docs] function to achieve what you want:
from django.db.models import Count, Max, Q
queryset = Author.objects.annotate(
all_books=Count('book'),
no_books_with_isbn=Count(
'book',
filter=Q(book__isbn_id__isnull=False)
),
last_created=Max('book_created_at'),
last_modified=Max('book_updated_at')
)
for author in queryset:
print(author.name, author.no_books_with_isbn, author.all_books, author.last_created, author.last_modified)

Related

Many-to-many relation Laravel Eloquent. Fetch related table with specific related id

I am writing a query in Laravel 8 using Eloquent. There is a many-to-many relation between subjects and streams and their pivot table is courses.
subjects:
id
name
streams
id
name
courses
id
subject_id
stream_id
I have to fetch subjects with specific stream.
I have written the following query.
$this->subjects = Subject::has('streams',$this->stream)->get();
I am having problem in this.
courses table :
id
stream_id
subject_id
1
1
1
2
1
2
3
1
3
4
1
4
5
2
1
6
2
2
7
3
1
This is the sample data. If I want to fetch subject where stream id is 1 then it is only fetching subject with ids 3 and 4 and not fetching subjects with id 1 and 2. Can anyone explain me why it is skipping subject ids with 1 and 2 ?
PLease anyone can help me with this.
Thank you in advance . :)
Edited
class Subject
{
public function streams()
{
return $this->belongsToMany(Stream::class, 'courses',
'subject_id', 'stream_id');
}
}
class Stream
{
public function subjects()
{
return $this->belongsToMany(Subject::class, 'courses',
'stream_id', 'subject_id');
}
}
For fetching models having a specific related model you can use whereHas() method. https://laravel.com/docs/8.x/eloquent-relationships#querying-relationship-existence
Assuming that $this->stream is a Stream model, you can use a closure for whereHas method:
$stream = $this->stream;
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('streams.id', $stream->id);
})->get();
Edit: since $this->stream is an id you should do
$stream = $this->stream;
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('streams.id', $stream);
})->get();
You should specify the table name where you are referencing the id to avoid ambiguity.
$this->subjects = Subject::whereHas('streams', function($query) use ($stream){
$query->where('stream.id', $stream->id);
})->get();

Django queryset field replaced with related table field

I have 2 tables (Product and CustomerProduct)
CustomerProduct is the intermediate table between Customer and Product. It assigns customer specific pricing to certain products.
Product Model
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=12, decimal_places=2)
sample data
id
name
price
1
orange
1.5
2
apple
2.2
3
kiwi
3.5
CustomerProduct Model
class CustomerProduct(models.Model):
customer = models.ForeignKey(
"Customer",
on_delete=models.CASCADE,
related_name="customer_customerproducts",
)
product = models.ForeignKey(
"Product",
on_delete=models.CASCADE,
related_name="product_customerproducts",
)
price = models.DecimalField(
max_digits=12,
decimal_places=2,
)
sample data
id
customer_id
product_id
price
1
233
1
1.2
2
233
2
2.0
Expected Result
I want to query the all products but the Product.price adjusted according to CustomerProduct.price if related field exists. The expected data (example in json but want queryset):
[
{
id: 1
name: "orange"
price: 1.2 // The price adjusted according to customer price
}
{
id: 2
name: "apple"
price: 2.0 // The price adjusted according to customer price
}
{
id: 3
name: "kiwi"
price: 3.5 // Price remain original because CustomerProduct not exists.
}
]
The How
I totally have no idea how to accomplish this in Django. How to do that?
You can use Coalesce [Django docs] with Subquery expressions [Django docs] to annotate a value on your queryset which will be the price that you want. Coalesce gives the first non-null value of the expressions passed to it, so we will pass it a subquery that will get the related price from CustomerProduct or the field price itself in that order to achieve what you want:
from django.db.models import F, OuterRef, Subquery
from django.db.models.functions import Coalesce
customer = Customer.objects.get(pk=some_pk) # get the customer for which you want the results
# Last related price, will use it as a subquery
related_price = CustomerProduct.objects.filter(
product=OuterRef('pk'),
customer=customer,
).order_by('-pk').values('price')[:1]
products = Product.objects.annotate(
price_offered=Coalesce(
Subquery(related_price), # If there is a matching object in the subquery use it
F('price'), # Else use the price of the Product itself
)
)
for product in products:
print(product.pk, product.name, product.price_offered)

Keep a relation map in Objection.js while removing the table

I'm developing a reddit-like site where votes are stored per-user (instead of per-post). Here's my relevant schema:
content
id | author_id | title | text
---|-----------|-------------|---
1 | 1 (adam) | First Post | This is a test post by adam
vote: All the votes ever voted by anyone on any post
id | voter_id | content_id | category_id
---|-------------|------------------|------------
1 | 1 (adam) | 1 ("First Post") | 1 (upvote)
2 | 2 (bob) | 1 ("First Post") | 1 (upvote)
vote_count: Current tally ("count") of total votes received by a post by all users
id | content_id | category_id | count
---|------------------|--------------|-------
1 | 1 ("First Post") | 1 (upvote) | 2
I've defined a voteCount relation in Objection.js model for the content table:
class Content extends Model {
static tableName = 'content';
static relationMappings = {
voteCount: {
relation: Model.HasManyRelation,
modelClass: VoteCount,
join: {
from: 'content.id',
to: 'vote_count.content_id'
}
}
}
}
But I recently (learned and) decided that I don't need to keep (and update) a separate vote_count table, when in fact I can just query the vote table and essentially get the same table as a result:
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
So now I wanna get rid of the vote_count table entirely.
But it seems that would break my voteCount relation since there won't be a VoteCount model (not shown here but it's the corresponding the model for the vote_count table) no more either. (Right?)
How do I keep voteCount relation while getting rid of vote_count table (and thus VoteCount model with it)?
Is there a way to somehow specify in the relation that instead of looking at a concrete table, it should look at the result of a query? Or is it possible to define a model class for the same?
My underlying database in PostgreSQL if that helps.
Thanks to #Belayer. Views were exactly the solution to this problem.
Objection.js supports using views (instead of table) in a Model class, so all I had to do was create a view based on the above query.
I'm also using Knex's migration strategy to create/version my database, and although it doesn't (yet) support creating views out of the box, I found you can just use raw queries:
module.exports.up = async function(knex) {
await knex.raw(`
CREATE OR REPLACE VIEW "vote_count" AS (
SELECT content_id
, category_id
, COUNT(*) AS count
FROM vote
GROUP
BY content_id
, category_id
)
`);
};
module.exports.down = async function(knex) {
await knex.raw('DROP VIEW "vote_count";');
};
The above migration step replaces my table vote_count for the equivalent view, and the Objection.js Model class for it (VoteCount) worked as usual without needing any change, and so did the relation voteCount on the Content class.

django complex datamodel

I am creating a small Django project which show stats collected from twitter data
for example my tables are
hashDetails
---------------------------------------------
id hashname tweetPosted trendDate userid
---------------------------------------------
1 #abc 44 2-2-2016 #xyz
2 #abc 55 2-2-2016 #qwer
3 #xcs 55 3-2-2016 #qwer
4 #xcs 55 4-2-2016 #qwer
---------------------------------------------
userDetails
----------------------------------------------
id userid profileImage profileImage
----------------------------------------------
1 #xyz image2.jpg www.abc.com
2 #qwer image3.jpg www.xadf.com
----------------------------------------------
for this if i create models.py
class userDetails(models.Model):
userid= models.CharField(max_length=30)
profileImage= models.CharField(max_length=30)
profileImage= models.CharField(max_length=30)
class hashDetails(models.Model):
hashname= models.CharField(max_length=30)
tweetPosted= models.IntegerField()
trendDate= models.DateTimeField()
userid = models.ForeignKey(userDetails, to_field ='userid')
but i don't wanna make userid unique cause
i want something like i can enter data in both table manually
and when i query in my view it will search result from both table
example
if i want all trends by #xyz
or if i want list of all users who did #abc trend
or if i want result of all trends in specific date
in short i want both table to behave like one
I can't use userid as unique my daily data will be about 20MB so you can assume its difficult to find ids
I found one solution of my problem and its working for me
i just create normal 2 model without foreignkey or any relation
and define my function in views.py
and got my result what i want
def teamdetail(request,test_id):
hashd = hashDetails.objects.get(hashname=test_id)
userd= userDetails.objects.all()
context = {'hashinfo': hashd, 'username':userd}
return render(request,'test/hashDetails.html',context)

One to many relationship on the same table

Here is the situation:-
I have a table called Users. This contains user data for students and tutors as most of the data required is the same.
Having completed the system I am now told that the client would like to be able to assign students to tutors.
Is there a legitimate/ clean way I can create a one to many relationship within a single table, perhaps via a link table?
I've tried to think this through but whatever solution I come up with seems messy.
I would be grateful for any input.
Thanks
Phill
Have you tried the following approach?
Make a new table, for example TutorStudent (choose a more appropriate name if needed). It should have two columns:
Tutor_ID
Student_ID
Both columns shall be the (composite) primary key, each column will be a foreign key to your Users table User_ID (I assume this is what you have).
So, if you have a tutor named Newton that has two students, Tesla and Edison, your Users table will have something like this:
User_ID, Name
1, Newton
2, Tesla
3, Edison
and your TutorStudent table will have following values:
Tutor_ID, Student_ID
1, 2
1, 3
Relatively simple and doesn't require any modifications to your existing table.
Do take care when deleting users - use the delete cascade feature of your database system or do some maintenance work afterwards so your TutorStudent table doesn't go stale when updating/removing your users.
My ideal for the same situation
Example: one book have many category:
Basic solution:
book table has recorded book information
category table has recored category information ex: 100 documents
book_category_relation table has single book (book_id) has category(category_id) 1 book may be have 100 category_id
Ideal solution:
First calculate total your category: ex 100 document. Each category equal value 1 bit: max 31 bit so 100 category we have ceil floor(100%31) = 4 groups
category_id = 1 : 1 (1%31) <=> 000000001 group 0 = floor(1/31)
category_id = 2 : 2 (2%31)<=> 000000010 group 0 = floor(2/31)
category_id = 3 : 4 (3%31)<=> 000000100 group 0 = floor(3/31)
category_id = 4 : 8(4%31)<=> 000001000 group 0 = floor(4/31)
...........................
category_id = 31: 2^31(31%31) <=>1000..000 group 0 if moduler 31 equal zero so number group = (31/31 -1)=0;
category_id = 32: 1(32%31) <=> 0000000001 group 1 = floor(32/31)
category_id = 33: 2(33%31) <=> 0000000010 group 1 = floor(33/31)
Ok now we add 4 fields in design book table (group_0,group_1,group_2,group_3) with int(11) unsigned and add index that fields
if book has category id = n so we can the following calculate formula:
bit code = (n%31 ==0)?31: (n%31)
number group field = (n%31==0)?(n/31 -1):floor(n/31)
ex: book in category_id = 100 so:
bit code = (100%31) =7 <=>2^7 = 128,
group = floor(100%31) = 3 <=> in group_3
so if you need query all book in category_id = 100, query string is:
SELECT * FROM book WHERE group_3&128
Note: MySQL not index working if bitwise in where.
But you can check in this link:
Bitwise operations and indexes