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

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'));
}

Related

Can Laravel automatically switch between column = ? and column IS NULL depending on value?

When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();

How to write join query with multiple column - LINQ

I have a situation where two tables should be joined with multiple columns with or condition. Here, I have a sample of sql query but i was not able to convert it into linq query.
select cm.* from Customer cm
inner join #temp tmp
on cm.CustomerCode = tmp.NewNLKNo or cm.OldAcNo = tmp.OldNLKNo
This is how i have write linq query
await (from cm in Context.CustomerMaster
join li in list.PortalCustomerDetailViewModel
on new { OldNLKNo = cm.OldAcNo, NewNLKNo = cm.CustomerCode } equals new { OldNLKNo = li.OldNLKNo, NewNLKNo = li.NewNLKNo }
select new CustomerInfoViewModel
{
CustomerId = cm.Id,
CustomerCode = cm.CustomerCode,
CustomerFullName = cm.CustomerFullName,
OldCustomerCode = cm.OldCustomerCode,
IsCorporateCustomer = cm.IsCorporateCustomer
}).ToListAsync();
But this query doesn't returns as expected. How do I convert this sql query into linq.
Thank you
You didn't tell if list.PortalCustomerDetailViewModel is some information in the database, or in your local process. It seems that this is in your local process, your query will have to transfer it to the database (maybe that is why it is Tmp in your SQL?)
Requirement: give me all properties of a CustomerMaster for all CustomerMasters where exists at least one PortalCustomerDetailViewModel where
customerMaster.CustomerCode == portalCustomerDetailViewModel.NewNLKNo
|| customerMaster.OldAcNo == portalCustomerDetailViewModel.OldNLKNo
You can't use a normal Join, because a Join works with an AND, you want to work with OR
What you could do, is Select all CustomerMasters where there is any PortalCustomerDetailViewModel that fulfills the provided OR:
I only transfer those properties of list.PortalCustomerDetailViewModel to the database that I need to use in the OR expression:
var checkProperties = list.PortalCustomerDetailViewModel
.Select(portalCustomerDetail => new
{
NewNlkNo = portalCustomerDetail.NewNlkNo,
OldNLKNo = portalCustomerDetail.OldNLKNo,
});
var result = dbContext.CustomerMasters.Where(customerMaster =>
checkProperties.Where(checkProperty =>
customerMaster.CustomerCode == checkProperty.NewNLKNo
|| customerMaster.OldAcNo == checkProperty.OldNLKNo)).Any()))
.Select(customerMaster => new CustomerInfoViewModel
{
Id = customerMaster.Id,
Name = customerMaster.Name,
...
});
In words: from each portalCustomerDetail in list.PortalCustomerDetailViewModel, extract the properties NewNKLNo and OldNLKNo.
Then from the table of CustomerMasters, keep only those customerMasters that have at least one portalCustomerDetail with the properties as described in the OR statement.
From every remaining CustomerMasters, create one new CustomerInfoViewModel containing properties ...
select cm.* from Customer cm
inner join #temp tmp
on cm.CustomerCode = tmp.NewNLKNo or cm.OldAcNo = tmp.OldNLKNo
You don't have to use the join syntax. Adding the predicates in a where clause could get the same result. Try to use the following code:
await (from cm in Context.CustomerMaster
from li in list.PortalCustomerDetailViewModel
where cm.CustomerCode == li.NewNLKNo || cm.OldAcNo = li.OldNLKNo
select new CustomerInfoViewModel
{
CustomerId = cm.Id,
CustomerCode = cm.CustomerCode,
CustomerFullName = cm.CustomerFullName,
OldCustomerCode = cm.OldCustomerCode,
IsCorporateCustomer = cm.IsCorporateCustomer
}).ToListAsync();
var result=_db.Customer
.groupjoin(_db.#temp ,jc=>jc.CustomerCode,c=> c.NewNLKNo,(jc,c)=>{jc,c=c.firstordefault()})
.groupjoin(_db.#temp ,jc2=>jc2.OldAcNo,c2=> c2.OldNLKNo,(jc2,c2)=>{jc2,c2=c2.firstordefault()})
.select(x=> new{
//as you want
}).distinct().tolist();

Fetching data from the database based on different categories

I am setting up the query that fetch data from 3 tables based on filters that user select. filters can be multiple or can be none.
I made stored procedures with different combination. But I know that was the worst thing that I did.
var result = (from product in context.Products
from img in context.ProductImage
from saved in context.SavedProduct
where (cat.Color.Contains(product.Color)
& cat.BrandName.Contains(product.Brand_Name)
& cat.Fabric.Contains(product.Fabric)
& cat.Design.Contains(product.Design))
select new
{
product.ProductID,
product.Price,
product.Brand_Name,
product.Title,
product.Color,
product.Fabric,
product.Design,
img.Image,
saved.ProductSavedCounter,
}).ToList();
Product related details in a Product Table. Product images in a ProductImage table. And How many people saved this product are in SavedProduct table.
It returns the products only if user select all filters means when user select red color, Nike brand, cotton fabric etc. If one is missed than this query returns nothing. I want when 1 or 2 are missed than it should return data according to other selected fitters.
Pardon me if there is any mistake I am new bee.
And I missed the joins.
This may help:-
Note: I assumed the ProductID is in the other tables to make the joins, in general if your model have navigation properties for the Product image and save product you would not need the joins.
var result = from product in context.Products
join img in context.ProductImage on product.ProductID equals img.ProductID
join saved in context.SavedProduct on product.ProductID equals saved.ProductID
where (
(string.IsNullOrEmpty(cat.Color) || cat.Color.Equals(product.Color))
& (string.IsNullOrEmpty(cat.BrandName) || cat.Brand_Name.Equals(product.Brand_Name))
& (string.IsNullOrEmpty(cat.Fabric) || cat.Fabric.Equals(product.Fabric))
& (string.IsNullOrEmpty(cat.Design) || cat.Design.Equals(product.Design))
& (
((string.IsNullOrEmpty(cat.Color) ? 1 : 0) +
(string.IsNullOrEmpty(cat.BrandName) ? 1 : 0) +
(string.IsNullOrEmpty(cat.Fabric) ? 1 : 0) +
(string.IsNullOrEmpty(cat.Design) ? 1 : 0)) >= 2) //at least two conditions
)
select new {
product.ProductID,
product.Price,
product.Brand_Name,
product.Title,
product.Color,
product.Fabric,
product.Design,
img.Image,
saved.ProductSavedCounter,
};
I think a better solution would to build it gradually, as below:-
//build the joins
var result = (from product in context.Products
join img in context.ProductImage on product.ProductID equals img.ProductID
join saved in context.SavedProduct on product.ProductID equals saved.ProductID
select new { product, img, saved }).AsQueryable();
//get number of conditions avaliable
var conditionCount = (string.IsNullOrEmpty(cat.Color) ? 1 : 0) +
(string.IsNullOrEmpty(cat.Brand_Name) ? 1 : 0) +
(string.IsNullOrEmpty(cat.Fabric) ? 1 : 0) +
(string.IsNullOrEmpty(cat.Design) ? 1 : 0);
if (conditionCount >= 2)
{
//add the condition if they exists
if (!string.IsNullOrEmpty(cat.Color))
result = result.Where(x => cat.Color.Equals(x.product.Color));
if (!string.IsNullOrEmpty(cat.Brand_Name))
result = result.Where(x => cat.Brand_Name.Equals(x.product.Brand_Name));
if (!string.IsNullOrEmpty(cat.Fabric))
result = result.Where(x => cat.Fabric.Equals(x.product.Fabric));
if (!string.IsNullOrEmpty(cat.Design))
result = result.Where(x => cat.Design.Equals(x.product.Design));
//make the final select
var finalResult = result.Select(x => new
{
x.product.ProductID,
x.product.Price,
x.product.Brand_Name,
x.product.Title,
x.product.Color,
x.product.Fabric,
x.product.Design,
x.img.Image,
x.saved.ProductSavedCounter,
}).ToList();
}
And if you meant that whenever any filter is missing the query should ignore it remove the condition count and the if statement for it.

Linq to SQL include related table

Been trying for a while now, but just can't get it to work the way I want. Referring to the code snippet below, I want to include a related table to Bricks (BrickColors). As of now BrickColors are not included and is lazy loaded.
var query = (from ul in DbContext.UserLocs
join l in DbContext.Locs on ul.LocId equals l.Id
join lb in DbContext.LocBricks on l.Id equals lb.LocId
join b in DbContext.Bricks on lb.BrickId equals b.Id
join bc in DbContext.BrickColors on b.ColorId equals bc.Id
where ul.UserId == userId
group new { LocQty = ul.Quantity, LocBrickQty = lb.Quantity, Brick = b } by new { b.BrickId, b.ColorId }
into data
orderby data.Key
select new
{
Brick = data.FirstOrDefault().Brick,
Quantity = data.Sum(d => d.LocBrickQty * d.LocQty)
})
.AsNoTracking();
If I remove .AsNoTracking() the performance is quite good, because it keeps the BrickColors table in memory, but I want it to be included in the query from the start.
I have tried DbContext.Bricks.Include(b => b.BrickColorAccessor) in the query, but that doesn't work. I think my group new { } is messing something up since I don't include BrickColors there...

IQueryable LINQ ordering and grouping

I have the following (incorrect) LINQ function (updated):
public IQueryable<ClassUsageReport> GetClassUsage()
{
//Class Code, Title, Usage, FiscalYear
var queryable = (from agencyplan in _agencyPlansList
join classSchedule2012 in _classSchedule2012List
on agencyplan.Id equals classSchedule2012.AgencyPlanId
join classes in _classesList
on classSchedule2012.Class.Id equals classes.Id
orderby agencyplan.PlanYear.FiscalYear descending, classes.ClassCode.Count() descending
group agencyplan by new
{
agencyplan.PlanYear.FiscalYear,
classes.ClassCode,
classes.Title
} into gcs
select new ClassUsageReport
{
ClassCode = gcs.Key.ClassCode,
Title = gcs.Key.Title,
Usage = gcs.Key.ClassCode.Count(),
FiscalYear = gcs.Key.FiscalYear
}
);
return queryable.AsQueryable();
}
I am having trouble with the Group By and Order By clauses. Also with the COUNT().
I have written the correct SQL statement, that produces the results as needed (and expected):
select py.fiscalyear, c.classcode, c.title, count(c.classcode) as usage from classschedule2012 cs
inner join classes c on cs.class_id = c.id
inner join agencyplans ap on cs.agencyplanid = ap.Id
inner join planyears py on ap.planyear_id = py.id
group by py.fiscalyear, c.classcode, c.title
order by py.fiscalyear desc, usage desc
What am I doing wrong with the grouping and ordering in my LINQ statement? I would like it to include "usage" like my SQL has. How can I get count to properly reflect the true count? As the query is at the moment, it only returns "9" in every row. This does not match my SQL, as the real results should be "55, 44, 14, 13" etc....
EDIT: 11/14/2013
Here is the final result:
public IQueryable<ClassUsageReport> GetClassUsage()
{
//Class Code, Title, Usage, FiscalYear
var queryable = (from agencyplan in _agencyPlansList
join classSchedule2012 in _classSchedule2012List
on agencyplan.Id equals classSchedule2012.AgencyPlanId
join classes in _classesList
on classSchedule2012.Class.Id equals classes.Id
where classes.Active = true
orderby agencyplan.PlanYear.FiscalYear descending
group agencyplan by new
{
agencyplan.PlanYear.FiscalYear,
classes.ClassCode,
classes.Title
} into gcs
select new ClassUsageReport
{
ClassCode = gcs.Key.ClassCode,
Title = gcs.Key.Title,
Usage = gcs.Count(),
FiscalYear = gcs.Key.FiscalYear
}
);
return queryable.AsQueryable().OrderByDescending(x => x.FiscalYear).ThenByDescending(x => x.Usage);
}
Once you group, you only have access to the columns you've grouped by and aggregate data of the other columns (like SUM or COUNT). In your query's select portion, you need to use g. instead of classes..
Try using g.Key in the select statement.