C# Linq using distinct with union - c#

I have a table in db as :
Id Name Stream Version UId Tab Key Value CreatedOn CreatedBy
1 Name1 GOP 1 U1 Tab1 co 1 07/01/2018 S, Adam
2 Name1 GOP 1 U2 Tab1 co 1 07/03/2018 S, Adam
3 Name1 GOP 1 U3 Tab2 st 2 07/03/2018 S, Adam
4 Name1 GOP 2 OR Tab1 co 1 07/02/2018 P, Silver
5 Name2 GOP 1 OR Tab1 co 1 07/02/2018 P, Silver
6 Name3 GOP 0 OR1 Tab0 coe 1 07/02/2018 S, Adam
7 Name3 GOP 0 OR2 Tab1 coe 1 07/02/2018 S, Adam
8 Name2 LNT 3 NE Tab1 st 4 07/01/2018 P, Silver
9 Name2 LNT 3 NE1 Tab1 co 2 07/01/2018 P, Silver
10 Name2 LNT 2 NE2 Tab1 st 3 07/01/2018 P, Silver
11 Name2 LNT 0 NE Tab9 co 5 07/01/2018 R, Henry
12 Name3 TTE 0 TT Tab1 ee 2 07/02/2018 R. Henry
13 Name3 TTE 0 T1 Tab1 ee 2 07/02/2018 R. Henry
I want to write a query that would give me the highest version of set together with distinct version 0.
For this I wrote the query as but this does not get my desired output:
var data = response.GroupBy(x => new { x.Name, x.Stream })
.SelectMany(g => g.OrderByDescending(row => row.Version).Take(1)) //This gives highest version
.Union(response.Where(x => int.Parse(x.Version) == 0)) // This gives version 0
.OrderByDescending(o => o.CreatedOn).ToList();
Desired Output On UI
Id Name Stream Version CreatedOn CreatedBy
4 Name1 GOP 2 07/02/2018 P, Silver //This is shown as 2 is the highest version for Name1 & GOP combination
5 Name2 GOP 1 07/02/2018 P, Silver //This is shown as Name & Stream combination is different
6 Name1 GOP 0 07/02/2018 S, Adam //Version 0 is always shown - Combination of Name & Stream may or may not have more than one 0 version
8 Name2 LNT 3 07/01/2018 P, Silver //This is shown as 3 is the highest version for Name2 & LNT combination
11 Name2 LNT 0 07/01/2018 R, Henry //Version 0 is always shown
12 Name3 TTE 0 07/02/2018 R, Henry //Version 0 is always shown
On the UI I am trying to just show the trimmed down version of a set. When the user clicks on that set I would then show details for all the individual sets within the set.
Right now I only struck with how to update my query so I can get my desired result.
----Updated---
Right now what I got working is individual lists:
var data1 = response.GroupBy(x => new { x.Name, x.Stream})
.SelectMany(g => g.OrderByDescending(row => row.Version).Take(1))
.Where(x => int.Parse(x.Version) != 0)
.OrderByDescending(o => o.CreatedOn).ToList();
The above gives me all latest versions for a given name and stream.
var data2 = response.GroupBy(x => new { x.Name, x.Stream})
.Select(g => g.First())
.Where(x => int.Parse(x.Version) == 0)
.OrderByDescending(o => o.CreatedOn).ToList();
The above gives me all 0 versions for a given name and stream.
I think these individual lists works fine at the moment, but how to merge them.
Is there a way to join/merge these lists together so as to return just a single set. Or if there is a way to merge these 2 linq queries together.
---Updated-----
var set1 = response.GroupBy(x => new { x.Name, x.Stream})
.SelectMany(g => g.OrderByDescending(row => row.Version).Take(1))
.Where(x => int.Parse(x.Version) != 0).ToList();
var set2 = response.GroupBy(x => new { x.Name, x.Stream, x.Version})
.Select(g => g.First())
.Where(x => int.Parse(x.Version) == 0).ToList();
var setmerged = set1.Union(set2).OrderByDescending(o => o.CreatedOn).ToList();
Got it working by above not sure if this is an clean solution.

Wrong use of DISTINCT
This doesn't make sense. DISTINCT is used to filter duplicate rows, which they aren't as only the version number is the same. Where should the DBS know from, which CreatedOn value it should use?
Better solution
What you want is to use GROUP BY in order to group everything with the same version value. Be aware that you'll need to use an aggregate function like MAX()on the other columns in order to use it correctly.

So from the looks of your updated question, you want the top one of each distinct version, name and stream.
According to the dataset you've provided, and the result sets you want, there is no need to do anything special for the 0 version because the conditions for grabbing the distinct version should include the 0 version as well.
You haven't really specified what the order of the version should be so for now I'll assume that the one that was most recently updated is the one you want
response.GroupBy(x => new { x.Name, x.Stream, x.Version })
.SelectMany(g => g.OrderByDescending(row => row.CreatedOn).Take(1))
.ToList();

Related

How to do select multiple records based on max version using Entity Framework

I have records such like this
id name number version
---------------------------
1 NewYork 1 1
2 LosAngeles 1 2
3 Seatle 1 3
4 Toronto 2 1
5 Ottawa 2 2
I want to select only the records with highest version within the same number
So I wrote query like this
SELECT *
FROM city c
WHERE c.[version] = (SELECT Max([version])
FROM [city] c2
WHERE c2.number = c.number)
and it would return
id name number version
---------------------------
3 Seatle 1 3
5 Ottawa 2 2
How do I write that in linq with Entity Framework?
db.cities.where(c => c.version == (????))
I don't know how Entity Framework would work for this.
Use GroupBy to group by the number and then OrderBy the record with the highest version:
var result = db.cities.GroupBy(item => item.number)
.Select(grouping => grouping.OrderByDescending(item => item.version)
.First());
var maxVersionCities = db.cities
.GroupBy(c => c.number)
.Select(grp => grp
.OrderByDescending(c => c.version)
.First())
.SelectMany(grp => grp);

Need linq query to obtain specific result set

If any one can help here to prepare this linq query.
Below is the table data:
category question answer user
a1 q1 3 u1
a1 q1 5 u2
a1 q2 3 u2
a2 q3 3 u3
a2 q3 2 u4
a3 q4 9 u3
a3 q5 8 u7
a3 q5 2 u9
I need result as below:
group by Category, Question, Result (group with three condition as below).
so, for each category, each question, need to group the result in three form(group by answer (<=3) and one group, answer = 5 and third group answer=8 OR 7 OR 9),
category name Question TotalCount Ans(<=3) Ans(5) Ans(7 or 8 or 9)
a1 q1 2 1 1 0
a1 q2 1 1 0 0
a2 q3 1 1 0 0
.....
I tried by using below query in SQL (it is not perfect query as i need column for result group instead of record).
SELECT
Category, Question,
Count(Ans),
CASE
WHEN Ans = 9 OR Ans = 8 OR ans=7 THEN '9 or 8 or 7'
WHEN Ans = 5 THEN '5'
WHEN Ans <= 3 THEN '<=3'
END as 'ResultGroup'
FROM
SurveyQuestions
GROUP BY
Category, Question, Ans
ORDER BY
Question
Please guide me how to get the result as shown above. I need LINQ query to obtain this result.
thanks
You don't need to group it by answer. Instead filter and count on the answer something like below:
from t in db.Table
group t by new {t.Category, t.Question} into g
order by g.Category
select new
{
CategoryName = t.FirstOrDefault().Category, //might be required to handle null here
Question = t.FirstOrDefault().Question, //might be required to handle null here
TotalCount = t.Count(),
AnsLessEqual3 = t.Where(d => d.Answer<=3).Count(),
Ans5 = t.Where(d => d.Answer = 5).Count(),
Ans789 = t.Where(d => d.Answer = 7 || d.Answer = 8 || d.Answer = 9).Count()
}
Although above query is not tested but I believe it would give you quick start for moving forward.

SELECT UNTIL in SQL with Entity Framework

I would like to select rows until I found a certain Id which is numeric. If my data is ordered by Id the problem can be solved quite easy.
Id Name
-----------
1 Bob
2 Eve
3 Alice
4 Michael
5 Anne
6 Mike
To get all items until Id 4 is found the following SQL statement is sufficient:
SELECT * FROM Users WHERE Id <= 4
If the data is ordered by Name and I still would like to get the items until Id 4 is found I couldn't come up with a good solution.
Id Name
-----------
3 Alice
5 Anne
1 Bob
2 Eve
4 Michael
6 Mike
The output of the statement to be defined should be:
Id Name
-----------
3 Alice
5 Anne
1 Bob
2 Eve
4 Michael
EDIT 1:
With the following statement the output is almost what I need but missing the item with Id 4.
queryable.OrderBy(o => o.Name).TakeWhile(o => o.Id != 4);
Id Name
-----------
3 Alice
5 Anne
1 Bob
2 Eve
Is it possible to include the item with Id 4 too?
EDIT 2:
For now I'm going with this approach:
queryable.OrderBy(o => o.Name).TakeWhile(o => o.Id != 4).Union(queryable.Where(o => o.Id == 4))
As the Id is unique there should be no problem with the UNION statement. But I'm not sure if this statement is the most effective one.
Context.Table.OrderBy(p => p.Name).TakeWhile(p => p.id != 4);
var result = Users.TakeWhile((u,i)=> i == 0 || Users.ElementAt(i-1).Id != 4);
Another approach:
var result = Users.TakeWhile(u=>u.Id!=4)
.Union(Users.SkipWhile(u=>u.Id!=4).Take(1));
var results = from n in names.TakeWhile( n => n.Id != 4)
.Union(names.Where(n => n.Id == 4)) select n;

Datatable group by sum

in a Queue i have datatables in the following format
some table in the Queue
Name Rank
AAA 9
BBB 5
CCC 1
DDD 5
some other table in the Queue
Name Rank
AAA 1
SSS 5
MMM 1
DDD 8
using LINQ need to process those tables table by table continously and add the results to a global DataTable in the following format:
Name Rank1 Rank2 Rank3 Rank>3
AAA 1 0 0 1
BBB 0 0 0 1
CCC 1 0 0 0
DDD 0 0 0 2
SSS 0 0 0 1
MMM 0 0 0 0
in the global table 4 columns state how many times a name was ranked in ranks 1,2,3 or >3.
now if the name already exists in global table i will not add it but only increment the rank count columns, and if does not exist then add it.
i've done this with nested looping but i wonder if anyone can help me with the LINQ syntax to do such thing,also will using LINQ make the process faster than with nested looping?
note that new tables are added to the Queue every second and i will be getting sometable from the Queue and process it to the global datatable
table1.AsEnumerable().Concat(table2.AsEnumerable())
.GroupBy(r => r.Field<string>("Name"))
.Select(g => new {
Name = g.Key,
Rank1 = g.Count(x => x.Field<int>("Rank") == 1),
Rank2 = g.Count(x => x.Field<int>("Rank") == 2),
Rank3 = g.Count(x => x.Field<int>("Rank") == 3),
OtherRank = g.Count(x => x.Field<int>("Rank") > 3)
}).CopyToDataTable();
You will need implementation of CopyToDataTable method where Generic Type T Is Not a DataRow.
A little optimized solution (single parsing and single loop over grouped ranks):
(from row in table1.AsEnumerable().Concat(table2.AsEnumerable())
group row by row.Field<string>("Name") into g
let ranks = g.Select(x => x.Field<int>("Rank")).ToList()
select new {
Name = g.Key,
Rank1 = ranks.Count(r => r == 1),
Rank2 = ranks.Count(r => r == 2),
Rank3 = ranks.Count(r => r == 3),
OtherRank = ranks.Count(r => r > 3)
}).CopyToDataTable();

LINQ join and group

I'm new to LINQ, and I'm trying to convert this SQL query into its LINQ equivalent:
select S.*
from Singles S
join (
select max(SingleId) as SingleId
from Single
group by ArtistId) S2 on S2.SingleId = S.SingleId
order by Released desc
The table looks like this:
-----------
| Singles |
|-----------|
| SingleID |
| ArtistId |
| Released |
| Title |
| ..... |
-----------
and so on...
And contains for example these items:
SingleID ArtistID Released Title
1 1 2011-05-10 Title1
2 1 2011-05-10 Title2
3 2 2011-05-10 Title3
4 3 2011-05-10 Title4
5 4 2011-05-10 Title5
6 2 2011-05-10 Title6
7 3 2011-05-10 Title7
8 5 2011-05-10 Title8
9 6 2011-05-10 Title9
So I'm trying to get the latest singles, but only one per artist. Could anyone help me? :)
Maybe there's even a better way to write the query?
Update:
To answer the questions posted in the comments:
We're using Microsoft SQL Server, and LINQ to NHibernate.
Here's a sample that we're using right now, that returns the latest singles, without grouping by artistid:
public Single[] GetLatest()
{
IQueryable<Single> q;
q = from s in _sess.Query<Single>()
where s.State == State.Released
orderby s.Released descending
select s;
return q.Take(20).ToArray();
}
How about this:
var firstSingles = Singles.GroupBy(x => x.ArtistId)
.Select(g => g.OrderByDescending(x => x.Released).First())
.ToList();
Something like this should work.
var query = from s in db.Singles
group s by s.ArtistID into sg
let firstSingle = sg.OrderByDescending(r => r.SingleID).FirstOrDefault()
select new
{
ArtistID = sg.Key,
SingleID = firstSingle.SingleID,
Released = firstSingle.Released,
Title = firstSingle.Title,
}
singles
.OrderByDescending(s => s.SingleID)
.GroupBy(s => s.SingerID, (id, s) => new
{
SingleID = id,
Title = s.First().Title
});

Categories