Django queryset field replaced with related table field - sql

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)

Related

SQL request for filter change produtc price. Rails app with PostgreSQL

Please tell me how you can make a query using SQL or ActiveRecord. I have table price_history: id, product_id, price, date . I need to sort those products whose price has changed more than 2 times. I am new to SQL. My application uses Ruby-on-Rails and PostgreSQL . I will be glad for any help. Thank you.
I probably didn't formulate my question clearly. I need to get something like the following:
table: prices_history
id | product | price | date
--------------------------------
1 milk 2.0 01/01/2022
2 milk 2.5 10/01/2022
3 milk 6.0 20/02/2022
4 bread 2.0 01/01/2022
5 bread 2.1 10/01/2022
6 bread 3.0 20/02/2022
SQL request =>
milk: min_price = 2.0, max_price = 6.0, max_price/min_price = 3 > 2? it`s true
bread: min_price = 2.0, max_price = 3.0, max_price/min_price = 1,5 > 2? it`s false
result SQL request = > milk
Thanks to those who answered. Based on your answers, I formed my request and it works.
SELECT
product,
MIN(price) as first_price,
MAX(price) as last_price
FROM
prices_history
GROUP BY
product
HAVING
MAX(price)/MIN(price) > 2;
I think just a simple query will give you the result you need.
SELECT
id,
price,
date,
product_id,
count (product_id)
FROM
price_history
GROUP BY
product_id
HAVING
count (product_id) > 2;
more info on having function: https://www.postgresqltutorial.com/postgresql-having
As per Rails,
In your application, define below association in Product, PriceHistory model
class Product
has_many :price_histories
end
class PriceHostory
belongs_to :product_id
end
Then below query to find the products having more than 2 price histories
Product.joins(:price_histories).group('products.id').having('count(price_histories) > 2')

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

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)

How to do full text search in postresql efficiently through multiple models?

I am working on rails application I have three different models as follows
product model
id | product_name| product_description
User model
id | name | location | timestamp
Shopping Model
id | product_id | user_id | location
Now in my application user can search for different products.
Suppose product description is xyz & somebody from New York bought it. So Now I want to search in such a way that if somebody searches for xyz the entries from product table will be returned & also when somebody searches for New York it should return the all product bought from New York.
I have created a view of product and shopping model table as follows.
SELECT DISTINCT product.id , product.description , shopping.location
FROM product LEFT JOIN shopping ON product.id = shopping.product_id group by product.id, product.description,shopping.location
I am using texticle gem to search through this view.
But problem with the above view is suppose 10 persons from 10 different locations bought xyz product then in the result set for the search of product description there will be duplicate results. How to avoid such duplication? Please Help.
SELECT p.id, p.product_name, p.product_description, s.location FROM product p, shopping s WHERE p.description='xyz' OR s.location='New York' AND p.id=shopping.product_id ORDER BY p.product_name

Rails Select and Group by summation Custom SQL Calls

I have a table named sales, which has the following fields:
id (integer)
quantity (integer)
product_id (integer)
payment_method (string - bad I know)
I want to hook these records up to the google visualization api but first I need to actually get the data presentable in the way that I want. The google visualization integration is not the issue. What IS the issue is that I can't seem to get the group() function or select() function to do what I want.
I'm not sure that group is what I want, but basically I would like to do a sales totals per product by payment_method.
My original idea was that it would look like
.select("SUM(quantity) as total_sold", :product_id).group(:payment_method)
But that doesn't really help me sort them by product. What I'd like the data to look like would be:
CASH SALES:
Product A: 103 sales
Product B: 32 sales
Product C: 87 sales
CREDITCARD SALES:
Product A: 23 sales
Product B: 43 sales
Product C: 12 sales
DONATION SALES:
Product A: # sales
Product B: 43 sales
Product C: 12 sales
Any help would be appreciated!
.select("SUM(quantity) as total_sold", :product_id).group(:payment_method, :product_id)
Here first it will group the result set by payment method, then by product id.
Keep the select of all columns inside of one string. Generated SQL will use that "as is". No need to close the string and put 2nd parameter as symbol :product_id in select.
Important to add all columns from group by in select also.
Consider .order("payment_method, product_id") also.
.select("SUM(quantity) as total_sold, payment_method, product_id").group(:payment_method, :product_id)
Tested using rails console:
Product.select("product_line_id, public_product_id pp_id, count(product_id) num_products") .group("product_line_id, public_product_id").map{|p| "Public Prod Id: #{p.pp_id} has #{p.num_products} products " }
Product Load (0.2ms) SELECT public_product_id pp_id, count(product_id) num_products FROM products GROUP BY products.public_product_id
=> [
"Public Prod Id: 5 has 1 products ",
"Public Prod Id: 6 has 2 products ",
"Public Prod Id: 8 has 1 products ", ... ]

Receive product groups and products with one query

I have two tables:
Product
------------------------------------
id group_id name quick_select
------------------------------------
1 1 product1 1
2 3 product2 0
3 5 product3 1
Product_group
-----------------------
id name parent_id
-----------------------
1 group1 0
2 group2 0
3 group3 1
4 group4 1
5 group5 3
I making a navigation system for quick select products. I show categories to user and user can navigate in them by clicking category button, then goes level down to subcategory and goes down so many levels that finally it can't go deeper - then I show products. First I show root categories where there's products under them and under those root categories subcategories, subsubcategories and so on.
In my query I want select all root categories (where parent_id=0), if there's products in them and in their subcategories and subsubcategories and so on, where quick_select must be 1 in product table. And I don't know the deepness of categories - how many levels there are.
Is it possible with one query? Or do I need to do two queries?
I made so far, with this query:
SELECT pg.id, pg.name, pg.parent_id AS parent_id
FROM product_group AS pg
LEFT JOIN product AS p ON pg.id = p.group_id
WHERE pg.parent_id = 0 AND p.id IS NOT NULL AND p.quick_select = 1
GROUP BY pg.id
But I don't receive root categories which subcategory is empty, which subcategory is empty and under this is one more subcategory with products with quick_select=1.
Sorry for my bad english.
I want to receive all categories where products with quick_select=1 are, not products
-- Category
| |
| product
|
-- Category
|
Category
|
Category
|
multiple products
The bad news is that you can't do this in SQLite, at least with this data structure, since SQLite doesn't support recursive SQL or window functions.
If select performance is important, you can try to organize the data like this:
http://articles.sitepoint.com/article/hierarchical-data-database/2
Another option is to add the root id to each row at input time.
Basically, at some point you will have to use multiple selects and determine the root id at the application level.
Update:
Ok, this is very much pseudo-code, but it should get you there.
You need a language that has some sort of hashmap or named array datatype.
hashmap results, parent, nodes, nodes_new; # variables
foreach (res in sql_execute("SELECT id, parent_id FROM product_group;") ) {
parent[res.id] = res.parent_id;
}
# get groups with products
foreach (res in sql_execute("SELECT pg.id FROM product_group AS pg INNER JOIN
product AS p ON pg.id = p.group_id
WHERE p.quick_select = 1 GROUP BY pg.id ") ) {
nodes[res.id] = res.id;
}
while (length(nodes) > 0) {
foreach (i in nodes) {
if (i = 0) { results[i] = i; } # if its a root node, add to results
else { nodes_new[parent[i]] = parent[i]; } # otherwise, add parent to the next round
}
nodes = nodes_new; # prepare for next round
}
print results;