fetching single child row based on a max value using Django ORM - sql

I have a model, "Market" that has a one-to-many relation to another model, "Contract":
class Market(models.Model):
name = ...
...
class Contract(models.Model):
name= ...
market = models.ForeignKey(Market, ...)
current_price = ...
I'd like to fetch Market objects along with the contract with the maximum price of each. This is how I'd do it via raw SQL:
SELECT M.id as market_id, M.name as market_name, C.name as contract_name, C.price
as price from pm_core_market M INNER JOIN
(SELECT market_id, id, name, MAX(current_price) as price
FROM pm_core_contract GROUP BY market_id) AS C
ON M.id = C.market_id
Is there a way to implement this without using SQL? If there is, which one should be preferred in terms of performance?

Django 1.1 (currently beta) adds aggregation support to the database API. Your query can be done like this:
from django.db.models import Max, F
Contract.objects.annotate(max_price=Max('market__contract__current_price')).filter(current_price=F('max_price')).select_related()
This generates the following SQL query:
SELECT contract.id, contract.name, contract.market_id, contract.current_price, MAX(T3.current_price) AS max_price, market.id, market.name
FROM contract LEFT OUTER JOIN market ON (contract.market_id = market.id) LEFT OUTER JOIN contract T3 ON (market.id = T3.market_id)
GROUP BY contract.id, contract.name, contract.market_id, contract.current_price, market.id, market.name
HAVING contract.current_price = MAX(T3.current_price)
The API uses an extra join instead of a subquery (like your query does). It is difficult to tell which query is faster, especially without knowing the database system. I suggest that you do some benchmarks and decide.

Related

How to perform translation from RAW SQL to django queryset

I am struggling with conversion to django query having raw sql
I am new in django and any help will be appreciated
There are simple models:
Winemaker - target model
Wine
Post
Winemaker has 1+ Wines
Wine has 1+ Posts
I know that it should be done with annotations but have no idea how to implement it.
select w2.*,
(select count(wp.id)
from web_winemaker www
inner join web_wine ww on www.id = ww.winemaker_id
inner join web_post wp on ww.id = wp.wine_id
where
ww.status=20
and
wp.status=20
and
www.id = w2.id
) as wineposts_count,
(
select count(w.id)
from web_winemaker www1
inner join web_wine w on www1.id = w.winemaker_id
where
w.status=20
and www1.id = w2.id
) as wines_count
from web_winemaker w2;
You should be able to accomplish this with a Count aggregation expression in an annotate function. I took a guess at your related_name values on your relationship fields, so the following code may not plug in directly, but should give you an idea of how to do what you want.
from django.db.models import Count, Q
wine_makers = Winemaker.objects.annotate(
posts_count=Count(
'wine__post__id',
filter=Q(wines__status=20, wines__posts__status=20),
),
wines_count=Count(
'wines__id',
filter=Q(wines__status=20),
),
)
You may need to supply distinct=True depending on if you're crossing relationships.

Is there any way to use raw sql or i'm stuck with laravel-eloquent?

I'm trying to migrate an application developed in 2007 and add and api to it so I can easy develop and sync data with the new App I started a laravel project with the old database just to create the api the old database diagram look like this: Too many relationships and crazy polymorphic ones at that.
I want to migrate my query to laravel eloquent but I couldn't do it in time I tried to do it with eloquent but it took too much time is there any way to use raw sql like this or i'm stuck with eloquent this is my laravel code : here
the sql query looks like this
SELECT n.DOC_ID, n.TYP_ID, n.TYP_ID,
type_document.TYP_LIBELLE_AR, n.IND_ID,indication.IND_LIBELLE_AR,n.LAN_ID,
langue.LAN_LIBELLE_AR,
n.DOC_TITRE_PROPRE,
n.DOC_TITRE_COMPLEMENT,
n.DOC_TITRE_PARALLELE,
n.DOC_TITRE_ENSEMBLE,
n.DOC_NUMERO_PARTIE,
editeur.EDT_NOM_AR,
editeur.EDT_KEYWORDS,
n.DOC_LIEU_EDITION,
n.DOC_ANNEE,
n.DOC_EDITION,
periodicite.PER_LIBELLE_AR,
n.DIP_ID,
specialite.SPE_LIBELLE_AR,
n.COL_ID,
n.DOC_NUM,
n.COL_NUMERO,
n.COT_NOTICE,
n.SCL_ID,
n.SCL_NUMERO,
n.DOC_NBR_UNITE,
n.DOC_ILLUSTRATION,
n.DOC_FORMAT,
n.DOC_MATERIEL,
n.DOC_ISBN,
n.DOC_ISSN,
n.DOC_NBR_EXEMPLAIRE,
n.STA_ID,
n.REL_VOLUME,
n.PAY_ID,
n.DOC_AGENCE,
n.DOC_PRET_INTERNE,
n.DOC_PRET_EXTERNE,
n.DOC_KEYWORDS,
n.CREATE_DATE,
n.UPDATE_DATE,
statut_notice.STA_LIBELLE_AR,
notice_auteur.VED_ID,
notice_auteur.FON_ID,
notice_auteur.AUT_TYPE,
GROUP_CONCAT(DISTINCT vmath.VED_NOM) AS mats,
vmath.VED_KEYWORDS,
GROUP_CONCAT( DISTINCT vauth.VED_NOM) AS auths,
vauth.VED_KEYWORDS,
GROUP_CONCAT( DISTINCT notice_exemplaire.EXP_COTE) AS examp_cote,
GROUP_CONCAT( DISTINCT notice_exemplaire.LOC_ID) AS exmp_location
FROM notice n
INNER JOIN editeur ON editeur.EDT_ID =n.EDT_ID
INNER JOIN type_document ON type_document.TYP_ID = n.TYP_ID
INNER JOIN langue ON langue.LAN_ID = n.LAN_ID
INNER JOIN statut_notice ON statut_notice.STA_ID = n.STA_ID
INNER JOIN indication ON indication.IND_ID = n.IND_ID
INNER JOIN notice_exemplaire ON n.DOC_ID = notice_exemplaire.DOC_ID
LEFT JOIN specialite ON n.SPE_ID = specialite.SPE_ID
LEFT JOIN periodicite ON periodicite.PER_ID = n.PER_ID
INNER JOIN notice_auteur ON n.DOC_ID = notice_auteur.DOC_ID
INNER JOIN vedette vauth ON notice_auteur.VED_ID = vauth.VED_ID
LEFT JOIN notice_matiere ON n.DOC_ID = notice_matiere.DOC_ID
LEFT JOIN vedette vmath ON notice_matiere.VED_ID = vmath.VED_ID
GROUP BY n.DOC_ID
1:
For something like this you can use DB::select('YOUR-QUERY').
DB::select('...') calls select() on the underlying Connection class so it's not the same as using DB::table('...')->select('...') which is calling the select method on the Builder class.
For more information on running raw queries you can refer to the documentation.

How to filter all objects that contain multiple elements in a many-to-many relationship using Django's ORM?

I have two classes in Django linked through a ManyToManyField (the User class is the built-in User model):
from django.contrib.auth.models import User
class Activity():
participants = models.ManyToManyField(User, related_name='activity_participants')
I want to find all the activities in which two users are simultaneously participating.
I managed to solve my problem using a raw query (my app name is "core", therefore the "core" prefix in the table names):
SELECT ca.id FROM core_activity_participants AS cap, core_activity AS ca
INNER JOIN core_activity_participants AS cap2 ON cap.activity_id
WHERE cap.user_id == 1 AND cap2.user_id == 2
AND cap.activity_id == cap2.activity_id
AND ca.id == cap.activity_id
However, if possible, I'd like to avoid using raw queries, since it breaks uniformity from the rest of my app. How could I make this query, or one equivalent to it, using Django's ORM?
If you're using Django 1.11 or later the intersection queryset method will give you the records you want.
# u1 and u2 are User instances
u1_activities = Activity.objects.filter(participants=u1)
u2_activities = Activity.objects.filter(participants=u2)
common_activities = u1_activities.intersection(u2_activities)
Will produce a query something like this:
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 1
INTERSECT
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE "core_activity_participants"."user_id" = 2
You can also add extra querysets to the intersection if you want to check for activity overlap between more than 2 users.
Update:
Another approach, which works with older Django versions, would be
u1_activities = u1.activity_participants.values_list('pk', flat=True)
common_activities = u2.activity_participants.filter(pk__in=u1_activities)
Which produces a query like
SELECT "core_activity"."id"
FROM "core_activity"
INNER JOIN "core_activity_participants"
ON ("core_activity"."id" = "core_activity_participants"."activity_id")
WHERE (
"core_activity_participants"."user_id" = 2
AND "core_activity"."id" IN (
SELECT U0."id"
FROM "core_activity" U0
INNER JOIN "core_activity_participants" U1
ON (U0."id" = U1."activity_id")
WHERE U1."user_id" = 1
)
)

How to get Django QuerySet 'exclude' to work right?

I have a database that contains schemas for skus, kits, kit_contents, and checklists. Here is a query for "Give me all the SKUs defined for kitcontent records defined for kit records defined in checklist 1":
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1;
I'm using Django, and I mostly really like the ORM because I can express that query by:
skus = SKU.objects.filter(kitcontent__kit__checklist_id=1).distinct()
which is such a slick way to navigate all those foreign keys. Django's ORM produces basically the same as the SQL written above. The trouble is that it's not clear to me how to get all the SKUs not defined for checklist 1. In the SQL query above, I'd do this by replacing the "=" with "!=". But Django's models don't have a not equals operator. You're supposed to use the exclude() method, which one might guess would look like this:
skus = SKU.objects.filter().exclude(kitcontent__kit__checklist_id=1).distinct()
but Django produces this query, which isn't the same thing:
SELECT distinct s.* FROM skus s
WHERE NOT ((skus.id IN
(SELECT kc.sku_id FROM kit_contents kc
INNER JOIN kits k ON (kc.kit_id = k.id)
WHERE (k.checklist_id = 1 AND kc.sku_id IS NOT NULL))
AND skus.id IS NOT NULL))
(I've cleaned up the query for easier reading and comparison.)
I'm a beginner to the Django ORM, and I'd like to use it when possible. Is there a way to get what I want here?
EDIT:
karthikr gave an answer that doesn't work for the same reason the original ORM .exclude() solution doesn't work: a SKU can be in kit_contents in kits that exist on both checklist_id=1 and checklist_id=2. Using the by-hand query I opened my post with, using "checklist_id = 1" produces 34 results, using "checklist_id = 2" produces 53 results, and the following query produces 26 results:
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1
JOIN kit_contents kc2 ON kc2.sku_id = s.id
JOIN kits k2 ON k2.id = kc2.kit_id
JOIN checklists c2 ON k2.checklist_id = 2;
I think this is one reason why people don't seem to find the .exclude() solution a reasonable replacement for some kind of not_equals filter -- the latter allows you to say, succinctly, exactly what you mean. Presumably the former could also allow the query to be expressed, but I increasingly despair of such a solution being simple.
You could do this - get all the objects for checklist 1, and exclude it from the complete list.
sku_ids = skus.values_list('pk', flat=True)
non_checklist_1 = SKU.objects.exclude(pk__in=sku_ids).distinct()

Complicated Calculation Using Oracle SQL

I have created a database for an imaginary solicitors, my last query to complete is driving me insane. I need to work out the total a solicitor has made in their career with the company, I have time_spent and rate to multiply and special rate to add. (special rate is a one off charge for corporate contracts so not many cases have them). the best I could come up with is the code below. It does what I want but only displays the solicitors working on a case with a special rate applied to it.
I essentially want it to display the result of the query in a table even if the special rate is NULL.
I have ordered the table to show the highest amount first so i can use ROWNUM to only show the top 10% earners.
CREATE VIEW rich_solicitors AS
SELECT notes.time_spent * rate.rate_amnt + special_rate.s_rate_amnt AS solicitor_made,
notes.case_id
FROM notes,
rate,
solicitor_rate,
solicitor,
case,
contract,
special_rate
WHERE notes.solicitor_id = solicitor.solicitor_id
AND solicitor.solicitor_id = solicitor_rate.solicitor_id
AND solicitor_rate.rate_id = rate.rate_id
AND notes.case_id = case.case_id
AND case.contract_id = contract.contract_id
AND contract.contract_id = special_rate.contract_id
ORDER BY -solicitor_made;
Query:
SELECT *
FROM rich_solicitors
WHERE ROWNUM <= (SELECT COUNT(*)/10
FROM rich_solicitors)
I'm suspicious of your use of ROWNUM in your example query...
Oracle9i+ supports analytic functions, like ROW_NUMBER and NTILE, to make queries like your example easier. Analytics are also ANSI, so the syntax is consistent when implemented (IE: Not on MySQL or SQLite). I re-wrote your query as:
SELECT x.*
FROM (SELECT n.time_spent * r.rate_amnt + COALESCE(spr.s_rate_amnt, 0) AS solicitor_made,
n.case_id,
NTILE(10) OVER (ORDER BY solicitor_made) AS rank
FROM NOTES n
JOIN SOLICITOR s ON s.solicitor_id = n.solicitor_id
JOIN SOLICITOR_RATE sr ON sr.solicitor_id = s.solicitor_id
JOIN RATE r ON r.rate_id = sr.rate_id
JOIN CASE c ON c.case_id = n.case_id
JOIN CONTRACT cntrct ON cntrct.contract_id = c.contract_id
LEFT JOIN SPECIAL_RATE spr ON spr.contract_id = cntrct.contract_id) x
WHERE x.rank = 1
If you're new to SQL, I recommend using ANSI-92 syntax. Your example uses ANSI-89, which doesn't support OUTER JOINs and is considered deprecated. I used a LEFT OUTER JOIN against the SPECIAL_RATE table because not all jobs are likely to have a special rate attached to them.
It's also not recommended to include an ORDER BY in views, because views encapsulate the query -- no one will know what the default ordering is, and will likely include their own (waste of resources potentially).
you need to left join in the special rate.
If I recall the oracle syntax is like:
AND contract.contract_id = special_rate.contract_id (+)
but now special_rate.* can be null so:
+ special_rate.s_rate_amnt
will need to be:
+ coalesce(special_rate.s_rate_amnt,0)