I'm trying to figure out how I can convert this same SQL query into a Linq query, but I'm not seeing a way to do NOT IN with Linq like you can with SQL.
SELECT COUNT(DISTINCT ID)
FROM References
WHERE ID NOT IN (
SELECT DISTINCT ID
FROM References
WHERE STATUS = 'COMPLETED')
AND STATUS = 'FAILED'
I need to know how many distinct [ID] values exist that contain a [Status] value of "FAILED" that do not also have a [Status] of "COMPLETED". Basically, if there is a failed without a completed, i need the distinct amount for that.
var query_5 = from r in Records where r.ID NOT IN(from r in Records where
r.Record_Status == "COMPLETED" ) && (r.Record_Status == "FAILED")
select r.ID;
var rec_5 = query_5;
Console.WriteLine(rec_5.Distinct());
This was my attempt to do it, but I'm receiving numerous errors as it is not the right way to code it. Any examples on how to accomplish this would be much appreciated!
This is how the rest of my setup is looking.
public class References
{
public string ID;
public string Record_Status;
}
public static List<References> Records = new List<References>
{
};
The rough equivalent of a (not) in is using Contains(). Since the inner subquery doesn't reference the outer, you could write it like this:
var completedIds =
(from r in ctx.References
where r.Status == "COMPLETED"
select r.Id).Distinct();
var count =
(from r in ctx.References
where !completedIds.Contains(r.ID)
where r.Status == "FAILED"
select r.Id).Distinct().Count();
You could use the Except method:
var completed =
(from r in References
where r.Record_Status == "COMPLETED"
select r.Id).Distinct();
var failed =
(from r in References
where r.Record_Status == "FAILED"
select r.Id).Distinct();
var countFailedNotCompleted = failed.Except(completed).Count();
Note that this does not require using Contains during iteration. The sequences will be compared all at once within the Except method. You could also tack ToArray() on to each of the distinct sequences to ensure minimal iteration in the case where you want to use those sequences more than once.
Related
I was thinking that maybe, once the grouped data are retrieved in the C# part, I would be able loop through the list of items that were grouped.
var res = db.Commandes.Where(t => t.idMatiere == mod.idMatiereChoisie).GroupBy(t => t.UA_idCa);
foreach(var group in res)
{
foreach(var groupedLines in group)
{
// Always a single line, this loop is useless
}
}
It seems the logic applied here is more like SQL than C#: the grouping result in a single line and you won't see all the grouped items.
It's not a problem that I can't overcome
Tactic I will use: instead of grouping, I'll just query all the lines, and then, while looping, I will verify if UA_idCa is different form the previous data and that will means the next "group" has been reached.
But I wonder... How does someone normally do this cleanly, if it's possible?
Do you have to query again to retrieve a group's content?
Or is the "Tactic I will use" closer to what's best?
This problem is a matter of the combination of SQL server AND Entity Framework.
Seems like one of the value in the grouped part (a value that is different for all the line inside the group) must be marked as not null.
Because when looking for what could be a key, entity doesn't give a damn about nullable values : they could be unique, they could be never null, EF won't even check that.
Once it is marked as NOT NULL in the sql part, EF suddenly understand that there could multiple different unique values in the grouped part...
So basically This :
ALTER view [dbo].[Commandes] as
SELECT top(50000000)
isnull(ex.unitAdm, '000') UnitAdm
,c.id as idCahier
,isnull(ex.unitAdm, '000') + cast(c.id as nvarchar(6)) as UA_idCa
,c.NomCahier
,[Qte]
,c.prix as PrixCahier
,sc.id, 0 as idSousCahier /* THIS IS WHAT I COULD NOT COMPLETELY RETRIEVE
because it could be null ? */
,sc.NomCahier as sousCahier
,sc.prix as PrixSC
,m.id as idMatiere
,m.Code
,m.NomMatiere
,ep.id as idEpreuve
,ep.Titre
FROM [CahierExamen] cex
join Cahier c on c.id = cex.Fk_Cahier
join Examen ex on cex.FK_Examen = ex.id
join epreuve ep on ex.FK_Epreuve = ep.id
join Matiere m on ep.FK_Matiere = m.id
left join SousCahier sc on c.id = sc.FK_Cahier
order by code, unitAdm, idCahier
GO
As been changed to this:
ALTER view [dbo].[Commandes] as
SELECT top(50000000)
isnull(ex.unitAdm, '000') UnitAdm
,c.id as idCahier
,isnull(ex.unitAdm, '000') + cast(c.id as nvarchar(6)) as UA_idCa
,c.NomCahier
,[Qte]
,c.prix as PrixCahier
,isnull(sc.id, 0) as idSousCahier /* WOW, NOW EF UNDERSTAND
THERE COULD BE MULTIPLE DIFFERENTS VALUES ONCE DATA ARE GROUPED*/
,sc.NomCahier as sousCahier
,sc.prix as PrixSC
,m.id as idMatiere
,m.Code
,m.NomMatiere
,ep.id as idEpreuve
,ep.Titre
FROM [CahierExamen] cex
join Cahier c on c.id = cex.Fk_Cahier
join Examen ex on cex.FK_Examen = ex.id
join epreuve ep on ex.FK_Epreuve = ep.id
join Matiere m on ep.FK_Matiere = m.id
left join SousCahier sc on c.id = sc.FK_Cahier
order by code, unitAdm, idCahier
GO
I have linq query which has multiple records & I am filtering these records using a where clause.
Now, if the filtered records return nothing then I need to make it default to take default any single record from list.
var resultStaffGua = (from s in _db.Students
join sg in _db.StudentStaffGuardians on s.StudentID equals sg.StudentId
join g in _db.Staffs on sg.StaffId equals g.StaffID
join lr in _db.luRelationTypes on sg.RelationTypeId equals lr.RelationTypeID
join ga in _db.StaffAddresses on g.StaffID equals ga.StaffID
join ad in _db.Addresses on ga.AddressID equals ad.AddressID
where
lse.StatusID == (int?)Extension.StatusType.Active
&& lse.TenantID == tenantid
select new
{
g.FirstName,
g.LastName,
IsPrimary = sg.IsPrimaryGuardian,
se.Email,
Phone = sphon.PhoneNumber,
lr.RelationCD,
gdnr.GenderCD,
ad.Zipcode
}).Where(i=>i.IsPrimary==true);
if resultStaffGua count 0, I need one record from resultStaffGua. Thank you
if result count 0, I need one record from parentList.
Sometimes the obvious solution is the best. Why not add this after your code?
if (resultStaffGua.Count() == 0)
{
resultStaffGua = parentList.First();
}
If you want to be "clever" and do it all in one line (and I guess it would save a DB transaction too possibly) you could exchange your Where for an OrderBy and a Take.
So instead of:
).Where(i=>i.IsPrimary==true);
You could do:
).OrderBy( i => i.IsPrimary ? 0 : 1 ).Take(1);
This will prioritize any record that has an IsPrimary set to true, but it'll get one record regardless of whether any match.
Assuming that your intent is to retrieve one single record (there's at most one record with IsPrimary==true):
var query = (from s in...); //The whole query except the "where"
var resultStaffGua = query.SingleOrDefault(i=>i.IsPrimary==true) ?? query.First();
Otherwise, if the query could actually return more than one result:
var query = (from s in...);
var resultStaffGua = query.Where(i=>i.IsPrimary==true);
if(resultStaffGua.Count() == 0) resultStaffGua = new[] { query.First(); }
I am trying to return all distinct rows from Staging below where Staging.CenterCode does not exist in Centers.CenterCode.
At the moment Stagings has around 850 distinct CenterCodes and Centers is empty so I should be getting all of the distinct rows, but count begs to differ :)
Any ideas?
var query =
(from s in db.Stagings
join t in db.Centers on s.CenterCode equals t.CenterCode into tj
from t in tj.DefaultIfEmpty()
where s.CenterCode != t.CenterCode
select s.CenterCode).Distinct();
var c = query.Count();
I only need the unique columns from staging so not sure if I actually need a join with the above as I am not ever using data returned from Centers - I have however tried both and get the same 0 value for count.
Any ideas?
I would not use a join, but use a Contains.
var centerCodesQuery = db.Centers.CenterCode
.Select(x => x.CenterCode);
var query = db.Staging
.Where(x => !centerCodesQuery.Contains(x.CenterCode))
.Select(x => x.CenterCode)
.Distinct();
var c = query.Count();
the join is an inner join. So, if none of the rows in 1 table match the other table on the specified identifier then it will return 0. In yours you are trying to join 1 table with 850 distinct rows with an empty table. This will return 0.
If you actually want to return only those rows in 1 table that aren't in another you can use Except:
var query = (from s in db.Stagings
select s.CenterCode)
.Except(from t in db.Centers
select t.CenterCode);
var c = query.Count();
Looks like you are trying to implement antijoin via left outer join, which is one of the possible ways, but in order to make it work, you need to change
where s.CenterCode != t.CenterCode
to
where t == null
My ASP.Net application has the following Linq to SQL function to get a distinct list of height values from the product table.
public static List<string> getHeightList(string catID)
{
using (CategoriesClassesDataContext db = new CategoriesClassesDataContext())
{
var heightTable = (from p in db.Products
join cp in db.CatProducts on p.ProductID equals cp.ProductID
where p.Enabled == true && (p.CaseOnly == null || p.CaseOnly == false) && cp.CatID == catID
select new { Height = p.Height, sort = Convert.ToDecimal(p.Height.Replace("\"", "")) }).Distinct().OrderBy(s => s.sort);
List<string> heightList = new List<string>();
foreach (var s in heightTable)
{
heightList.Add(s.Height.ToString());
}
return heightList;
}
}
I ran Redgate SQL Monitor which shows that this query is using a lot of resources.
Redgate is also showing that I am running the following query:
select count(distinct [height]) from product p
join catproduct cp on p.productid = cp.productid
join cat c on cp.catid = c.catid
where p.enabled=1 and p.displayfilter = 1 and c.catid = 'C2-14'
My questions are:
A suggestion to change the function so that it uses less resources?
Also, how does linq to sql generate the above query from my function? (I did not write select count(distinct [height]) from product anywhere in the code)
There are 90,000 records in the products. This category which I am trying to get the distinct list of heights has 50,000 product records
Thank you in advance,
Nick
First of all your posted sql query and linq query doesn't match at all. it's not the LINQ query rather the underlying SQL query itself performing slow. Make sure, all the columns involved in JOIN ON clause and WHERE clause and ORDER BY clause are indexed properly in order to have a better execution plan; else you will end up getting a FULL Table Scan and a File Sort and query will deemed to perform slow.
The join multiplies the number of Products the query returns. To undo that, you apply Distinct at the end. It will certainly reduce db resources if you return unique Products right away:
var heightTable = (from p in db.Products
where p.CatProducts.Any(cp => cp.CatID == catID)
&& p.Enabled && (p.CaseOnly == null || !p.CaseOnly)
select new
{
Height = p.Height,
sort = Convert.ToDecimal(p.Height.Replace("\"", ""))
}).OrderBy(s => s.sort);
This changes the join into a where clause. It saves the db engine the trouble of deduplicating the result.
If that still performs poorly, you should try to do the conversion and ordering in memory, i.e. after receiving the raw results from the database.
As for the count. I don't know where it comes from. Such queries typically get generated by paging libraries such as PagedList, but I see no trace of that in your code.
Side note: you can return ...
heightList.Select(x => x.Height.ToString()).ToList()
... instead of creating the list yourself.
I need help converting some an SQL select into LINQ
Here is the original SQL:
Select Top 1 IsNull(ct.Template, t.Template) as Template
From Template t
Left Outer Join ClientTemplate ct On t.TemplateTypeId = ct.TemplateTypeId And ct.ClientId = 149
Where t.TemplateTypeId = (Select TemplateTypeId From QuoteType Where QuoteTypeId = 7)
Order By t.Version DESC, ct.Version DESC
I'm using Entity Framework and have entities for QuoteTypes, ClientTemplates and Templates.
The above SQL gets the Template from the ClientTemplate table for client 149 (uses a variable in the real code) for a particular QuoteType. If there is no entry in the CLientTemplate table then it returns the Template from the main Template table for the same QuoteType!
My idea was to query the QuoteType first and then query the ClientTemplate table to see if one exists and if not query the Template table instead. The problem is this will result in three queries but I'm sure it can be done in one swoop!?
Can anyone have a go at writing the LINQ for me?
Here's my mess so far:
QuoteType quoteType = (from qt in this.entities.QuoteTypes where qt.QuoteTypeID == this.SelectedNewQuoteTypeID select qt).First();
if (quoteType != null && quoteType.TemplateTypeID.HasValue)
{
int quoteTypeTemplateTypeID = (int)quoteType.TemplateTypeID;
var query = (from t in this.entities.Templates
join ct in this.entities.ClientTemplates on t.TemplateTypeID equals ct.TemplateTypeID
into a
from b in a.DefaultIfEmpty(new ClientTemplate())
where t.TemplateTypeID == quoteTypeTemplateTypeID
orderby t.Version descending
select new
{
T1 = t.Template1,
T2 = b.Template
}).First();
// Check the query to see if T1 and T2 are null and use whichever one isn't!
// TODO !!!
}
else
{
return string.Empty;
}
I kind of gave up once I got that far and posted this! My example still does two queries and does not select based on the client ID. It also does not have the second order by on the client template table.
I've inherited the original SQL statement so maybe the problem is with that being badly written in the first place!?
Over to you...
I haven't tested it but maybe you could try something like this
from t in this.entities.Template
from ct in this.entities.ClientTemplate.Where(x => t.TemplateTypeId == ct.TemplateTypeId && ct.ClientId == 149).DefaultIfEmpty()
where t.TemplateTypeId == (from x in this.entities.QuoteType where x.QuoteTypeId == 7 select x.TemplateTypeId).FirstOrDefault()
orderby t.Version descending, ct.Version descending
select new { ct.Template == null ? t.Template : ct.Template }
I think this might work, although this is untested, from reading it appears you can't add multiple join conditions to a LinQ statement so you can just specify it in the where clause
So here ya go, I tried to convert the SQL word for word so here's my attempt. If it does everything I think it will do it will work.
int templateTypeId;
templateTypeId= (context.QuoteType.Where(x => x.QuoteTypeId == 7)).FirstOrDefault().TemplateTypeId
var qry =(
from t in context.Template
join ct in context.ClientTemplate on
t.TemplateTypeId equals ct.TemplateTypeId into cts
from ct in cts.DefaultIfEmpty() //left join
where t.TemplateTypeId == templateTypeId
&& ct.ClientId == 149
order by t.Version descending, ct.Version descending
select new
{
Template = (ct.Template != null) ? ct.Template : t.Template //ternary operator
}).FirstOrDefault();