Select best score in course with lambda - linq

This table stores student course name and score. I want to create a lambda expression to choose the best student in each course
ID | name | courseName | score
------------------------------
1 | Alex | Math | 18
2 | John | physics | 19
3 | Sam | Math | 17
4 | Sarah| physics | 14
The desired result
ID | name | courseName | score
------------------------------
1 | Alex | Math |18
2 | John | Physics |19

You can use GroupBy:
var query = students
.GroupBy(s => s.CourseName )
.Select(cg => cg.OrderByDescending(s => s.Score).First());
That picks the best student of each course. If there are multiple with the same max-score you get an arbitrary student. You could use this to select all max-students:
IEnumerable<Student> query = students
.GroupBy(s => s.CourseName)
.SelectMany(cg => cg.GroupBy(s => s.Score).OrderByDescending(g => g.Key).First());

Related

How to make Eloquent query for Group by, with HAVING and MAX

I have SQL query which I need to convert to Eloquent and no idea where to write MAX conditions. How it is posible to convert it without much Raw sql? Thank you!
SELECT bike_id
FROM bike_filters
GROUP BY bike_id
HAVING
MAX(bike_category_id in (416,11111)) = 1
AND MAX(bike_category_id in (5555,779)) = 1
AND MAX(bike_category_id in (5555,772)) = 1
Table Structure:
| id | bike_id | bike_category_id |
| 1 | 3 | 416 |
| 2 | 3 | 779 |
| 3 | 3 | 344 |
| 4 | 3 | 332 |
| 5 | 4 | 444 |
| 5 | 5 | 555 |
The purpose of this query is to get bike_ids, which has all parameters by the query - bike can have 20 filters, but if user searches by 5 and bike matches them, we get bike_id by this query.
For this you could use havingRaw() which would look something like:
$query = DB::table('bike_filters')
->select('bike_id')
->groupBy('bike_id')
->havingRaw('MAX(bike_category_id in (416,11111)) = 1')
->havingRaw('MAX(bike_category_id in (5555,779)) = 1')
->havingRaw('MAX(bike_category_id in (5555,772)) = 1')
->get();

Convert raw query into laravel eloquent

I have this written and working as a raw SQL query, but I am trying to convert it to a more Laravel eloquent / query builder design instead of just a raw query.
My table structure like this:
Table One (Name model)
______________
| id | name |
|------------|
| 1 | bob |
| 2 | jane |
--------------
Table Two (Date Model)
_________________________________
| id | table_1_id | date |
|-------------------------------|
| 1 | 1 | 2000-01-01 |
| 2 | 1 | 2000-01-31 |
| 4 | 1 | 2000-02-28 |
| 5 | 1 | 2000-03-03 |
| 6 | 2 | 2000-01-03 |
| 7 | 2 | 2000-01-05 |
---------------------------------
I am returning only the the highest (most recent) dates from table 2 (Dates model) that match the user bob from table 1 (Name model).
For instance, in the example above, I return this from my query
2000-01-31
2000-02-28
2000-03-03
Here is what I am doing now (which works), but i'm just not sure how to use YEAR, MONTH and MAX with laravel.
DB::select(
DB::raw("
SELECT MAX(date) as max_date
FROM table_2
INNER JOIN table_1 ON table_1.id = table_2.table_1_id
WHERE table_1.name = 'bob'
GROUP BY YEAR(date), MONTH(date)
ORDER BY max_date DESC
")
);
Try this code if any problem then,
DB::table('table_1')->join('table_2', 'table_1.id','=','table_2.table_1_id')
->select(DB::raw('MAX(date) as max_date'),DB::raw('YEAR(date) year, MONTH(date) month'),'table_1.name')
->where('name','bob')
->groupBy('year','month')
->orderBy('max_date')
->get();
If any problem with above code then feel free to ask.

how to use order by multiple times on the results set along with rownum (oracle)

I have the below tables:
person table
personid | firstname | lastname
------------------------------
P1 | Jim | John
P2 | Kori | Test
P3 | Adam | Blair
P4 | Kim | sand
P5 | julia | Dan
order table
orderno |ordername | price | personid
---------------------------------
1 |shoes | 100 | P1
2 |books | 50 |P2
3 | pen | 10 |P3
4 |laptop | 80 |P4
5 |notebook | 40 |P5
Email address table
clientid emailid
---------------------
P1 | jom.John#test.com
P3 | adam.blair#test.com
P4 | kim.sand#test.com
I have to get the top 3 person names ordered by 'price' and then get rid of the persons who dont have an email address and this final list should be ordered by firstname (desc)
My results should look like:
Firstname lastname price
------------------------------
Kim | sand | 80
Jim | john | 100
I tried minus but not able to figure out how to use order by (first by price) and then for the final results by firstname. oracle is not liking the combination of multiple order by and rownum together.
Any pointers would be helpful.
Try this:-
SELECT P.FIRSTNAME,P.LASTNAME,O.PRICE FROM PERSON_TABLE P
INNER JOIN ORDER_TABLE O
ON P.PERSONID = O.PERSONID
AND P.PERSONID NOT IN
(SELECT E.CLIENTID FROM EMAIL_ADDRESS_TABLE E)
ORDER BY P.FIRSTNAME desc;
output:
Firstname lastname price
------------------------
kim | sand | 80
julia | dan | 40
My result is not look like as your but it may be help you. I wrote above script as per your above mentioned requirement.

Laravel/Eloquent: Constrain nested eager load so as not to include empty parents

I have a relatively simple DB structure including countries, regions and depots. Each depot is assigned to an operator and a region:
operators
+----+------------+
| ID | name |
+----+------------+
| 1 | Operator 1 |
| 2 | Operator 2 |
+----+------------+
countries
+----+----------------+------+
| ID | country_id | code |
+----+----------------+------+
| 1 | United Kingdom | gb |
| 2 | France | fr |
+----+----------------+------+
regions
+----+-----------------+-------+
| ID | country_id (FK) | name |
+----+-----------------+-------+
| 1 | 1 | North |
| 2 | 1 | South |
| 3 | 1 | East |
| 4 | 1 | West |
| 5 | 2 | North |
| 6 | 2 | South |
| 7 | 2 | East |
| 8 | 2 | West |
+----+-----------------+-------+
depots
+----+----------------+------------------+-----------+
| ID | region_id (FK) | operator_id (FK) | name |
+----+----------------+------------------+-----------+
| 1 | 1 | 1 | Newcastle |
| 2 | 8 | 2 | Nantes |
+----+----------------+------------------+-----------+
I have set up their eloquent relationships successfully in the respective models.
I want to load each depot grouped into their respective regions and countries, and filtered by a specific operator.
$depots = Country::with('regions.depots')->whereHas('regions.depots', function($query) use ($opID) {
$query->where('operator_id',$opID);
})->get();
This does the trick, however as well as eager loading the depots, it's also eager loading all regions, including those without depots assigned to them. E.G. when the above is performed when $opID = 1, you get this result:
name: United Kingdom,
regions: [
{
name: North,
depots: [{
name: Newcastle
}]
}, {
name: South,
depots: []
}, {
name: East,
depots: []
}, {
name: West,
depots: []
}
]
What I would like is the above returned, but without the regions where there are no depots.
I have played around a lot with the constraints of both with and whereHas but cannot get the desired data structure. Why doesn't the below code have the desired effect?
$depots = Country::with(['regions.depots' => function($query) use ($opID) {
$query->where('depots.operator_id',$opID);
}])->get();
Is there any way at all of not eagerly loading the parent if the child doesn't exist? Or is it a case of performing the above query as I have it and then looping through the result manually?
EDIT
So after a few more hours I finally found a way to get my desired outcome. But it seems really dirty. Is this really the best way?
$depots = Country::whereHas('regions.depots', function($q) use ($opID) {
$q->where('operator_id',$opID);
})->with(['regions' => function($q) use ($opID) {
$q->with('depots')->whereHas('depots', function($q2) use ($opID) {
$q2->where('operator_id',$opID);
});
}])->get();
EDIT 2
So it turns out the first edit was actually querying the operator_id on everything but the depots table, which meant as soon as I added another depot owned by another operator in the same region, that showed up when I didn't want it to. The below seems even messier but does work. It's fun having conversations with myself ;) Hopefully it helps someone one day...
$depots = Country::has('regions.depots')
->with(['regions' => function($q) use ($opID) {
$q->with(['depots' => function($q2) use ($opID) {
$q2->where('operator_id',$opID);
}])->has('depots');
}])->get();
You can always use lazy loading:
$depots = Country::whereHas('regions.depots', function($q) use ($opID) {
$q->where('operator_id',$opID);
})->get()->load('regions.depots');

Sum of the grouped distinct values

This is a bit hard to explain in words ... I'm trying to calculate a sum of grouped distinct values in a matrix. Let's say I have the following data returned by a SQL query:
------------------------------------------------
| Group | ParentID | ChildID | ParentProdCount |
| A | 1 | 1 | 2 |
| A | 1 | 2 | 2 |
| A | 1 | 3 | 2 |
| A | 1 | 4 | 2 |
| A | 2 | 5 | 3 |
| A | 2 | 6 | 3 |
| A | 2 | 7 | 3 |
| A | 2 | 8 | 3 |
| B | 3 | 9 | 1 |
| B | 3 | 10 | 1 |
| B | 3 | 11 | 1 |
------------------------------------------------
There's some other data in the query, but it's irrelevant. ParentProdCount is specific to the ParentID.
Now, I have a matrix in the MS Report Designer in which I'm trying to calculate a sum for ParentProdCount (grouped by "Group"). If I just add the expression
=Sum(Fields!ParentProdCount.Value)
I get a result 20 for Group A and 3 for Group B, which is incorrect. The correct values should be 5 for group A and 1 for group B. This wouldn't happen if there wasn't ChildID involved, but I have to use some other child-specific data in the same matrix.
I tried to nest FIRST() and SUM() aggregate functions but apparently it's not possible to have nested aggregation functions, even when they have scopes defined.
I'm pretty sure there is some way to calculate the grouped distinct sum without needing to create another SQL query. Anyone got an idea how to do that?
Ok I got this sorted out by adding a ROW_NUMBER() function my SQL query:
SELECT Group, ParentID, ROW_NUMBER() OVER (PARTITION BY ParentID ORDER BY ChildID ASC) AS Position, ChildID, ParentProdCount FROM Table
and then I replaced the SSRS SUM function with
=SUM(IIF(Position = 1, ParentProdCount.Value, 0))
Put a grouping over the ParentID and use a summation over that group,
eg:
if group over ParentID = "ParentIDGroup"
then
column sum of ParentPrdCount = SUM(Fields!ParentProdCount.Value,"ParentIDGroup")

Resources