Laravel sum nested relationship column - sql

I tried sum nested related column but get empty result.
Code:
$data = CropType::with(['categories' => function($cq) {
return $cq->with(['varieties' => function($vq) {
return $vq->with(['products' => function($pq) {
return $pq->sum('total');
}]);
}]);
}])->get();
As you can see, I have 4 tables that are interconnected with foreign keys
Table: crop_types
id
title
Table: categories
id
title
crop_type_id
Table: varieties
id
title
category_id
Table: products
id
title
total
variety_id
But when I run my query it's run as like this instead summing total products:
"select * from crop_types"
How I can correctly sum total products in each crop type?

Try this query to get total products in each crop type:
SQL
SELECT crop_types.id,
crop_types.title AS title,
sum(products.total) AS total
FROM `products`
INNER JOIN `varieties`
ON `varieties`.`id` = `products`.`variety_id`
INNER JOIN `categories`
ON `categories`.`id` = `varieties`.`category_id`
INNER JOIN `crop_types`
ON `crop_types`.`id` = `categories`.`crop_type_id`
GROUP BY `crop_types`.`id`
Laravel Eloquent
$result = Product::query()
->selectRaw('crop_types.id, crop_types.title as title, sum(products.total) as total')
->join(
'varieties',
'varieties.id',
'=',
'products.variety_id'
)
->join(
'categories',
'categories.id',
'=',
'varieties.category_id'
)
->join(
'crop_types',
'crop_types.id',
'=',
'categories.crop_type_id'
)
->groupBy('crop_types.id')
->get();

Related

Unable to join and fetch data from two tables that are referenced with foreign key

I need help with a simple SQL query. Suppose I have two tables, customers and orders. In customers table I have only name and id fields and in orders table I have name, id and customer_id fields. Here customer_id is the foreign key to customers table.
So now I want to fetch all customers and also join each of their orders. Suppose these are the two tables with dummy data:
customers table
id name
1 John Doe
2 Jane Doe
orders table
id product_name customer_id
250 Massage Gun 1
260 Mac Lipstick 2
270 Mac Eyeliner 2
280 Yoga Mat 1
290 Mac Eyeshadow 2
Here's the code:
const query = `
SELECT * FROM customers c WHERE EXISTS (SELECT * FROM orders o WHERE o.customer_id = c.id);
`;
const customers = await pool.query( query );
console.log( customers );
Your SQL statement performs a semi-join, which only checks the existence of orders, but does not include them in the result set. You want a join:
SELECT * FROM customers c JOIN orders o
ON o.customer_id = c.id
// construct your query 1st
const query = `SELECT c.*, o.id as orderId, o.product_name
FROM customers c
INNER JOIN orders o
ON(c.id = o.customer_id);`;
// try-catch pool.query since it can raise exception
try{
const customers = await pool.query(query);
let customers_dict = {};
for(var i = 0; i<customers.length; i++){
if(!(customers[i].id.toString() in customers_dict)){ // 1st order
customers_dict[customers[i].id.toString()] = {
"id": customers[i].id,
"name": customers[i].name,
"orders":[
{
"id": customers[i].orderId,
"product_name":customers[i].product_name,
}
]
};
}else{ // another order for the customer id
customers_dict[customers[i].id.toString()]["orders"].push({
"id": customers[i].orderId,
"product_name": customers[i].product_name,
});
}
}
//here is the expected final json result
const expected_json_customers = Object.values(customers_dict);
console.log(expected_json_customers);
}catch(err){
console.log(err); // your error mgmt function
}

Postgres query - return flattened JSON

I have the following working query:
const getPromos = async (limit = 10, site: string, branch: string) => {
const query = `SELECT
json_build_object(
'id', p.id,
'description', p.description,
'discounted_price', p.discounted_price,
'items', jsonb_agg((i.id, i.price, i.title))
)
FROM promotions p
INNER JOIN promotion_items pi ON p.id = pi.promotion_id
INNER JOIN items i ON pi.item_code = i.item_code WHERE site_id = ${site} and store_id = ${branch}
GROUP BY p.id LIMIT ${limit}`;
return await db.query(query);
};
The issue is simple - each item (in this example - promotion) is returned with an object that wraps it - named json_build_object. I don't want any object to wrap my promotions - just like this:
[{id:1, .... items: [...items here...]}, {id:2, .... items: [...items here...]}]
Any idea?
You can get the desired result directly from the query when you aggregate the resultset using jsonb_agg (like items further down the query).
SELECT
jsonb_agg(jsonb_build_object(
'id', p.id,
'description', p.description,
'discounted_price', p.discounted_price,
'items', jsonb_agg((i.id, i.price, i.title))
))
--- the rest of your query

How to write linq query for this SQL query?

How to write linq query for this SQL Query
select c.Name, Count(cb.Id) as Total1, Count(cf.Id) as Total2
from Company c
left join CompanyBDetails CB on C.Id = CB.CompanyId
left join CompanyFDetails CF on CF.BankId = CB.Id
group by C.Name
So you have a table of Companies, where every Company has zero or more BDetails, every BDetail belongs to exactly one Company, namely the Company that the foreign key CompanyId refers to: a straightforward one-to-many relation.
Similarly, every BDetail has zero or more FDetails, and every FDetail belongs to exactly one BDetail, namely the BDetail that the foreign key BankId refers to.
For every company you want the Name of the Company and the number of BDetails of this company. It seems to me that you also want the total number of FDetails of all BDetails of this Company.
var result = dbContext.Companies.Select(company => new
{
Name = company.Name,
BDetailIds = dbContext.BDetails
.Where(bDetail => bDetail.CompanyId == company.Id)
.Select(bDetail => bDetail.Id),
})
Intermediate result: for every Company, you have created one object, that holds the name of the Company, and the Ids of all its BDetails
Continuing the Linq: calculate the totals:
.Select(company => new
{
Name = company.Name,
// Total1 is the number of BDetails of this Company
Total1 = company.BDetailIds.Count(),
// Total2 is the number of FDetails of all BDetails of this Company
// whenever you need the sub-items of a sequence of items, use SelectMany
Total2 = company.BDetailIds
.SelectMany(bDetailId => dbContext.FDetails.Where(fDetail => fDetail.BankId == bDetailId))
.Count();
});
If you want more properties than just the totals:
var result = dbContext.Companies.Select(company => new
{
// Select the company properties that you plan to use:
Id = company.Id,
Name = company.Name,
...
BDetail = dbContext.BDetails
.Where(bDetail => bDetail.CompanyId == company.Id)
.Select(bDetail => new
{
// Select only the bDetail properties of the company that you plan to use
Id = bDetail.Id,
...
// not needed, you know the value:
// CompanyId = bDetail.CompanyId,
FDetails = dbContext.FDetails
.Where(fDetail => fDetail.BankId == bDetail.Id)
.Select(fDetail => new
{
// you know the drill by now: only the properties that you plan to use
Id = fDetail.Id,
...
})
.ToList(),
})
.ToList(),
});

How to get other column value in different table into the query?

I had searching application, finding personal information which had been filtered by some criteria (category, years of experience etc)
I had problem with the last filter, 'tempoh perkhidmatan by negeri'. I need to calculate the number of working experience by state(negeri). For example, when searching for people of 5 years in the state(negeri) 'x', the sql will sum years of experience of each person in the state selected.
This is the full code of SQL searching by criteria:
$query = DB::table('itemregistrations')
->join('sections', 'itemregistrations.SectionID', '=', 'sections.SectionID')
->join('categories', 'itemregistrations.CategoryID', '=', 'categories.CategoryID')
->join('operasi', 'itemregistrations.OperasiID', '=', 'operasi.OperasiID')
->join('negeri', 'itemregistrations.NegeriID', '=', 'negeri.NegeriID')
->join('gred', 'itemregistrations.GredID', '=', 'gred.GredID')
->where('itemregistrations.statusProID', '=', 1)
->select('itemregistrations.name','sections.sectionname', 'categories.categoryname', 'operasi.operasiname', 'itemregistrations.Nobadan', 'itemregistrations.lahir_yy', 'itemregistrations.pdrm_yy', 'gred.namagred', 'itemregistrations.itemRegistrationID', '');
if($request->input('negeri_lahir') != ''){
$query->where('itemregistrations.NegeriID', $request->input('negeri_lahir'));
}
if($request->input('kategori') != '') {
$query->where('itemregistrations.CategoryID', $request->input('kategori'));
}
if($request->input('pangkat') != '') {
$query->where('itemregistrations.OperasiID', $request->input('pangkat'));
}
if(request('umur')) {
$query->whereRaw('YEAR(CURDATE()) - lahir_yy >= ?', [request('umur')]);
}
if($request->input('gred') != '') {
$query->where('itemregistrations.GredID', $request->input('gred'));
}
if(request('tempoh')) {
$query->whereRaw('YEAR(CURDATE()) - pdrm_yy >= ?', [request('tempoh')]);
}
if($request->input('negeri_perkhidmatan') != '') {
$query->join('itemregistrationpangkat', 'itemregistrationpangkat.itemRegistrationID', '=', 'itemregistrations.itemRegistrationID')
->where('itemregistrationpangkat.NegeriID', $request->input('negeri_perkhidmatan'));
}
if(request('tempoh_negeri')) {
$query->select(DB::raw('m.itemRegistrationID, sum(m.duration)'))
->from(DB::raw('(SELECT itemRegistrationID, NegeriID, yeartamatkhidmat - yearmulakhidmat as duration FROM itemregistrationpangkat) AS m
RIGHT JOIN itemregistrations ON itemregistrations.itemRegistrationID=m.itemRegistrationID'))
->distinct()
->groupBy('m.itemRegistrationID');
}
$newitem = $query->get();
return response::json($newitem);
The code involve to be solve is this(the last filter):
if(request('tempoh_negeri')) {
$query->select(DB::raw('m.itemRegistrationID, m.NegeriID, sum(distinct m.duration)'))
->from(DB::raw('(SELECT itemRegistrationID, NegeriID, yeartamatkhidmat - yearmulakhidmat as duration FROM itemregistrationpangkat) AS m
RIGHT JOIN itemregistrations ON itemregistrations.itemRegistrationID=m.itemRegistrationID'))
->groupBy('m.itemRegistrationID', 'm.NegeriID');
}
The problem is I need to get name column, sectionID column, CategoryID, OperasiID, NegeriID, GredID, from itemregistrations table from the $query statement. How to combine the last query filter in 'tempoh_negeri' with the previous one?
I didn't know about Laravel in particular, so I had much trouble trying to understand how your query was built, but this syntax seems to enable people to write a request by adding chunks, but not necessarily in the right order. So here's what I believe your query is supposed to do, for SQL speakers:
SELECT itemregistrations .name,
sections .sectionname,
categories .categoryname,
operasi .operasiname,
itemregistrations .Nobadan,
itemregistrations .lahir_yy,
itemregistrations .pdrm_yy,
gred .namagred,
itemregistrations .itemRegistrationID
-- if($tempoh_negeri) (request)
,m .itemRegistrationID,
sum(m.duration)
FROM itemregistrations
-- if($tempoh_negeri) (request)
,(SELECT DISTINCT itemRegistrationID,
NegeriID,
yeartamatkhidmat - yearmulakhidmat as duration
FROM itemregistrationpangkat) AS m
RIGHT JOIN itemregistrations
ON itemregistrations.itemRegistrationID = m.itemRegistrationID
JOIN sections
ON itemregistrations.SectionID = sections.SectionID
JOIN categories
ON itemregistrations.CategoryID = categories.CategoryID
JOIN operasi
ON itemregistrations.OperasiID = operasi.OperasiID
JOIN negeri
ON itemregistrations.NegeriID = negeri.NegeriID
JOIN gred
ON itemregistrations.GredID = gred.GredID
-- if($negeri_perkhidmatan)
JOIN itemregistrationpangkat
ON itemregistrationpangkat.itemRegistrationID = itemregistrations.itemRegistrationID
WHERE itemregistrations.statusProID = 1
-- if($negeri_lahir) (WHERE)
AND itemregistrations.NegeriID = $negeri_lahir
-- if($kategori) (WHERE)
AND itemregistrations.CategoryID = $kategori
-- if($pangkat) (WHERE)
AND itemregistrations.OperasiID = $pangkat
-- if(umur) (WHERERAW) (request)
AND YEAR(CURDATE()) - lahir_yy >= umur
-- if($gred) (WHERE)
AND itemregistrations.GredID = $gred
-- if($tempoh) (WHERERAW) (request)
AND YEAR(CURDATE()) - pdrm_yy >= tempoh
-- if($negeri_perkhidmatan)
AND itemregistrationpangkat.NegeriID = $negeri_perkhidmatan
-- if($tempoh_negeri) (request)
GROUP BY m.itemRegistrationID
If it's so, you cannot do what you want following that way (including main columns into the subquery) because your subquery will be evaluated BEFORE the main one is.
Instead, you need to write a proper filter at main query level, that is : among the others "JOIN…ON" clauses already in place. Which would give:
LEFT JOIN itemregistrationpangkat
ON itemregistrationpangkat.itemRegistrationID = itemregistrations.itemRegistrationID
… then specify the substraction directly in your sum() function
sum(yeartamatkhidmat - yearmulakhidma)
As regards Lavarel, this probably would give something like:
if(request('tempoh_negeri')) {
$query->leftjoin('itemregistrationpangkat','itemRegistrationID','=','itemregistrations.itemRegistrationID');
$query->select(DB:raw('sum(yeartamatkhidmat - yearmulakhidmat'));
}

How to do a SQL JOIN where multiple joined rows need to contain things

When someone on my site search for an image that has multiple tags I need to query and find all images that have the searched tags, but can't seem to figure out this query.
I have an Images table.
The Images table has a relation to Posts_Images.
Posts_Images would have a relation to Posts table.
Posts has a relation to Posts_Tags table.
Posts_Tags table will have the relations to Tags table.
The query I have so far:
SELECT "images".* FROM "images"
INNER JOIN "posts_images" ON posts_images.image_id = images.id
INNER JOIN "posts" ON posts.id = posts_images.post_id AND posts.state IS NULL
INNER JOIN "posts_tags" ON posts_tags.post_id = posts.id
INNER JOIN "tags" ON posts_tags.tag_id = tags.id
WHERE (("images"."order"=0) AND ("images"."has_processed"=TRUE)) AND (LOWER(tags.tag)='comic') AND ("tags"."tag" ILIKE '%Fallout%') ORDER BY "date_uploaded" DESC LIMIT 30
It gets no results, it is checking if the tags equals both values, but I want to see if any of the tags that were joined have all the values I need.
The desired result would be any Post that has a tag matching Comic and ILIKE '%Fallout%'
You seem to want something like this:
SELECT i.*
FROM images JOIN
posts_images pi
ON pi.image_id = i.id JOIN
posts p
ON p.id = pi.post_id AND p.state IS NULL JOIN
posts_tags pt
ON pt.post_id = p.id JOIN
tags t
ON pt.tag_id = t.id
WHERE i."order" = 0 AND
i.has_processed AND
(LOWER(t.tag) = 'comic') OR
(t.tag ILIKE '%Fallout%')
GROUP BY i.id
HAVING COUNT(DISTINCT tag) >= 2
ORDER BY date_uploaded DESC
LIMIT 30;
The logic is in the HAVING clause. I'm not 100% sure that this is exactly what you want for multiple matches.
In addition to gordon-linoff’s response - query can be described using ActiveQuery:
Images::find()
->alias('i')
->joinWith([
'postsImages pi',
'postsImages.posts p',
'postsImages.posts.tags t'
])
->where([
'p.state' => null,
'i.order' => 0,
'i.has_processed' => true,
])
->andWhere(
'or'
'LOWER(t.tag) = "comic"',
['like', 't.tag', 'Fallout']
])
->groupBy('id')
->having('COUNT(DISTINCT tag) >= 2')
->orderBy('date_uploaded DESC')
->limit(30)
->all()
$images = Images::find()
->innerJoin('posts_images', 'posts_images.image_id = images.id')
->innerJoin('posts', 'posts.id = posts_images.post_id AND posts.state IS NULL')
->where(['images.order' => 0, 'images.has_processed' => true]);
if (!is_null($query)) {
$tags = explode(',', $query);
$images = $images
->innerJoin('posts_tags', 'posts_tags.post_id = posts.id')
->innerJoin('tags', 'posts_tags.tag_id = tags.id');
$tagsQuery = ['OR'];
foreach ($tags as $tag) {
$tag = trim(htmlentities($tag));
if (strtolower($tag) == 'comic') {
$tagsQuery[] = ['tags.tag' => $tag];
} else {
$tagsQuery[] = [
'ILIKE',
'tags.tag', $tag
];
}
}
if (!empty($tagsQuery)) {
$images = $images->andWhere($tagsQuery)
->having('COUNT(DISTINCT tags.tag) >= ' . sizeof($tags));
}
}
$images = $images
->groupBy('images.id')
->orderBy(['date_uploaded' => SORT_DESC])
->offset($offset)
->limit($count);
return $images->all();