Convert T-SQL(distinct, groupby, union) to LINQ - c#

I'm familiar with t-sql but not in LINQ, I had tried basic linq but not sure on this one.
Im not sure if there's an exact equivalent in linq on my written sql statement below.
Basically I want to transfer the linq result to my domain model.
The sql query are using the same view (1 sql view)
Domain Model
Public class Result
{
Public int Key{get;set;}
Public string Name{get;set;}
}
SQL Query
SELECT DISTINCT Name = Field1 ,ItemKey = Field2 FROM sql_view
UNION
SELECT DISTINCT Name = Field3 ,ItemKey = Field4 FROM sql_view
UNION
SELECT DISTINCT Name = Field5 ,ItemKey = Field6 FROM sql_view
UNION
SELECT Name = Field7 ,ItemKey = Field8 FROM sql_view
Sample Data of sql_vw
Field1 Field2 Field3 Field4 Field5 Field6 Field7 Field8
1 A1 23 FF23 322 ZZ322 10 A10
1 A1 23 FF23 322 ZZ322 21 R21
1 A1 23 FF23 322 ZZ322 31 E31
2 B2 22 PP22 331 WW331 3 A3
2 B2 22 PP22 331 WW331 7 R7
2 B2 22 PP22 331 WW331 9 E9
2 B2 22 PP22 331 WW331 12 E12
3 C3 26 HH26 340 NN340 43 H43
3 C3 26 HH26 340 NN340 39 J39

(from v in context.View
group v by new {v.Field1, v.Field2} into g
select new Result{Key = g.Key.Field1, Item = g.Key.Field2})
.Union()//more of this may be
Updated with grouping

To makes things easier to read and understand, I would do something like that
var part1 = context.View.Select(m => new {Name = m.Field1, ItemKey = m.Field2});
var part2 = context.View.Select(m => new {Name = m.Field3, ItemKey = m.Field4});
var part3 = context.View.Select(m => new {Name = m.Field5, ItemKey = m.Field6});
var part4 = context.View.Select(m => new {Name = m.Field7, ItemKey = m.Field8});
var result = (part1.Union(part2).Union(part3).Union(part4)).Distinct();

Related

Sort List<T> on a property in a related List<T>

Say we have two List<T>. The first is a list of sales totals:
class SalesTotals
{
public Guid EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
Then we have another list of sales by year:
class YearlySales
{
public Guid EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
These are used together to create a "cross tab" report which lists the total sales by each employee, followed by a column for each year with the related yearly sales. It would look something like this:
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Joe | 70 | 20 | | 50 |
| Sam | 60 | 30 | 20 | 10 |
| Fred | 50 | 30 | | 20 |
| Bob | 40 | 10 | 15 | 15 |
By default, the report is sorted by TotalSales (no problem). But if we want to sort by an individual year, things get trickier. Sorted by 2017 (then by total):
| Name | Total | 2018 | 2017 | 2016 |
+------+-------+------+------+------+
| Sam | 60 | 30 | 20 | 10 |
| Bob | 40 | 10 | 15 | 15 |
| Joe | 70 | 20 | | 50 |
| Fred | 50 | 30 | | 20 |
I assume we want to (Left) Join these two List<T>s on EmpID, where SalesYear == <year to sort by> then OrderBy YearlyTotals, TotalSales (since YearlyTotals might not exist for a given year, and we still want some type of order in that case). So we also have to consider that there might not be a record for that year to join with (so it needs to be a left join).
If I were writing SQL it would look something like this:
SELECT ST.EmpID, ST.EmpName, ST.TotalSales
FROM SalesTotals AS ST
LEFT JOIN YearlySales AS YS ON ST.EmpID=YS.EmpID
WHERE YS.SalesYear=#SortBySalesYear OR YS.SalesYear IS NULL
ORDER BY YS.YearlySales DESC, ST.TotalSales DESC
I'm not good enough with Linq (yet) to be able to figure this out. In fact, I was able to get virtually no where (maybe trying to do too much at once, perhaps I need to break it down in to individual steps, and not search for the one liner).
So, is there a way to do this with Linq? Or should I be attempting some other type of approach?
Note: All I need is an "in place" sort here. I don't need/want a different type of List<T> returned here, just a sorted List<SalesTotals>.
Edit: I prefer the Linq "Query Syntax" as it is more intuitive to me (strong SQL background). So I prefer an answer using Query Syntax as opposed to Method Syntax.
Edit: Here is a test case setup:
class SalesTotals
{
public int EmpID { get; set; }
public string EmpName { get; set; }
public decimal? TotalSales { get; set; }
}
class YearlySales
{
public int EmpID { get; set; }
public short SalesYear { get; set; }
public decimal? YearlyTotals { get; set; }
}
class TestSort
{
public TestSort()
{
var st = new List<SalesTotals>
{
new SalesTotals() { EmpID = 1, EmpName = "Joe", TotalSales = 70 },
new SalesTotals() { EmpID = 2, EmpName = "Sam", TotalSales = 60 },
new SalesTotals() { EmpID = 3, EmpName = "Fred", TotalSales = 50 },
new SalesTotals() { EmpID = 4, EmpName = "Bob", TotalSales = 40 }
};
var ys = new List<YearlySales>
{
new YearlySales() { EmpID = 1, SalesYear = 2018, YearlyTotals = 20 },
new YearlySales() { EmpID = 2, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 3, SalesYear = 2018, YearlyTotals = 30 },
new YearlySales() { EmpID = 4, SalesYear = 2018, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2017, YearlyTotals = 20 },
new YearlySales() { EmpID = 4, SalesYear = 2017, YearlyTotals = 15 },
new YearlySales() { EmpID = 1, SalesYear = 2016, YearlyTotals = 10 },
new YearlySales() { EmpID = 2, SalesYear = 2016, YearlyTotals = 15 },
new YearlySales() { EmpID = 3, SalesYear = 2016, YearlyTotals = 50 },
new YearlySales() { EmpID = 4, SalesYear = 2016, YearlyTotals = 20 }
};
st = SortByYear(st, ys, 2017);
}
private List<SalesTotals> SortByYear(List<SalesTotals> salesTotals, List<YearlySales> yearlySales, short sortYear)
{
// return sorted salesTotals by sortYear using both salesTotals and yearlySales joined on EmpID
}
}
Rather than convert the SQL directly, I think it is a little clearer to break the query into two parts.
First, find the YearlySales for the year to sort by:
var sortYearSales = from ys in yearlySales
where ys.SalesYear == SortBySalesYear
select ys;
Then you can left join on that and sort (since ys might by null, I used the null conditional member acecss operator):
var orderedSalesTotals = (from st in salesTotals
join ys in sortYearSales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();
Note: I changed the name of the YearlySales member to YearSales since the C# compiler complained about the member and class having the same name.
You can do it in a single query, but you must either nest the first query into the second, or use lambda syntax in the query:
var orderedSalesTotals = (from st in salesTotals
join ys in yearlySales on st.EmpID equals ys.EmpID into ysj
from ys in ysj.Where(y => y.SalesYear == SortBySalesYear).DefaultIfEmpty()
orderby ys?.YearSales descending, st.TotalSales descending
select st).ToList();
You can write it pretty much the same way you would in SQL!
var results = from t in totals
join y in years on t.EmpID equals y.EmpID into groupedTable
from p in groupedTable.DefaultIfEmpty()
where y == null || y.SalesYear == year
orderby y.SalesYear, t.TotalSales descending
select t;
Quick note: Joins in LINQ are by default inner joins. If you want an outer join, you have to use a DefaultIfEmpty() call.
Kind of works. Need to put in a null for sales
List<YearlySale> YearlySales = new List<YearlySale>() { new YearlySale() { EmpID = 1, Sales = 700, Year = 2018 },
new YearlySale() { EmpID = 1, Sales = 600, Year = 2017 },
new YearlySale() { EmpID = 1, Sales = 500, Year = 2016 },
new YearlySale() { EmpID = 2, Sales = 400, Year = 2018 },
new YearlySale() { EmpID = 2, Sales = null, Year = 2017 },
new YearlySale() { EmpID = 2, Sales = 300, Year = 2016 }
};
List<SalesTotal> SalesTotals = new List<SalesTotal>() { new SalesTotal() { EmpID = 1, EmpName = "stan", TotalSales = 1800 },
new SalesTotal() { EmpID = 2, EmpName = "sally", TotalSales = 700 }
};
var q = from s in SalesTotals
join y18 in YearlySales
on s.EmpID equals y18.EmpID
join y17 in YearlySales
on s.EmpID equals y17.EmpID
join y16 in YearlySales
on s.EmpID equals y16.EmpID
where y18.Year == 2018
where y17.Year == 2017
where y16.Year == 2016
select new { SalesTotal = s, Year18 = y18 == null ? 0 : y18.Year, YearS18 = y18 == null ? 0 : y18.Sales
, Year17 = y17 == null ? 0 : y17.Year, YearS17 = y17 == null ? 0 : y17.Sales
, Year16 = y16 == null ? 0 : y16.Year, YearS16 = y16 == null ? 0 : y16.Sales
};
foreach (var v in q.OrderBy(x => x.SalesTotal.EmpID))
{
Debug.WriteLine($"{v.SalesTotal.EmpID} {v.SalesTotal.EmpName} {v.SalesTotal.TotalSales} {v.YearS18} as y18 {v.YearS17} as y17 {v.YearS16} as y16" );
}

Linq for rank() equivalent in SQL Server -

How can order my list using Linq equals rank() over in SQL ?
For example rank is my List<Player>
class Player
{
public int Id;
public int RankNumber;
public int Points;
public int Name;
}
Original Rank list:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
3 80 Jess 22
4 50 Jack 11
5 50 Paul 03
6 10 Monik 13
I need this Rank:
RankNumber Points Name Id
1 100 James 01
2 80 Mike 50
2 80 Jess 22
4 50 Jack 11
4 50 Paul 03
6 10 Monik 13
I don't think there is a good way to convert this directly to Linq to SQL but you could do this:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
It gives you the correct output but will convert horribly and inefficiently to SQL. So I would suggest this modification which materialises the data to a list before creating the ranks:
var rankedPlayers = players
.OrderByDescending(p => p.Points)
.ToList() //<--- Add this
.Select((p, r) => new Player
{
Id = p.Id,
RankNumber = players.Where(pl => pl.Points > p.Points).Count() + 1,
Points = p.Points,
Name = p.Name
});
You can try below expression:
var newData = players
.OrderByDescending(x => x.Points)
.GroupBy(x => x.Points)
.SelectMany((x, index) => x.Select(y => new Player
{
Name = y.Name,
Points = y.Points,
RankNumber = index + 1,
Id = y.Id
}));
players contains IEnumerable of objects of type Player and newData contains ordered data with rank.

LINQ merge 2 query results

The datatable has 5 columns
Name Class Course Month Score
Alex C1 Math 12 90
Bob C1 Chem 11 91
Alex C2 Math 11 91
Alex C1 Math 11 89
Bob C1 Chem 12 97
Alex C1 Math 10 94
Alex C2 Chem 12 92
Bob C2 Math 12 94
And I wanna group (name, class) and fetch the max math score in just Nov and Dec, and the max chem score. Heres my query code
DataRow[] dr1 = dt.Select("Course = 'Math' AND Month > 10");
var result_one = dr1.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
DataRow[] dr2 = dt.Select("Course = 'Chem'");
var result_two = dr2.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
And I could output these 2 query results as this:
Name Class Math_Max_Month Math_Max
Alex C1 12 90
Alex C2 11 91
Bob C2 12 94
Name Class Chem_Max_Month Chem_Max
Bob C1 12 97
Alex C2 12 92
But how can I merge these 2 results into 1 output such as this:
Name Class Math_Max_Month Math_Max Chem_Max_Month Chem_Max
Alex C1 12 90 null null
Alex C2 11 91 12 92
Bob C1 null null 12 97
Bob C2 12 94 null null
I've tried to use result_one.Concat(result_two) and result_one.Union(result_two), but both are incorrect.
Alright, seems a bit complicated in your example. So i'll give you an answer on a int[] instead of DataRow[]
int[] first = new int[] { 3, 5, 6, 9, 12, 14, 18, 20, 25, 28 };
int[] second = new int[] { 30, 32, 34, 36, 38, 40, 42, 44, 46, 48 };
int[] result = first
.Concat(second)
.OrderBy(x => x)
.ToArray();
Output will be
// 3, 5, 6, 9, 12, 14, 18, 20, 25, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48
Console.Write(String.Join(", ", result));
theoretically this should work in your case, sense we're only dealing with arrays.
This works perfectly well for your code.,
DataRow[] dr1 = dtt.Select("Course = 'Math' AND Month > 10");
var result_one = dr1.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Max = g.Max(r => r.Field<int>("Score")),
Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
DataRow[] dr2 = dtt.Select("Course = 'Chem'");
var result_two = dr2.AsEnumerable()
.GroupBy(r => new { Name = r.Field<string>("Name"), Class = r.Field<string>("Class") })
.Select(g => new
{
Name = g.Key.Name,
Class = g.Key.Class,
Chem_Max = g.Max(r => r.Field<int>("Score")),
Chem_Max_Month = g.FirstOrDefault(gg => gg.Field<int>("Score") == g.Max(r => r.Field<int>("Score"))).Field<int>("Month"),
}
).Distinct().ToList();
Left Join...
var lstLeftJoin = (from a in result_one
join b in result_two
on new { a.Name, a.Class } equals new { b.Name, b.Class }
into gj
from subpet in gj.DefaultIfEmpty()
select new { a.Name, a.Class, Math_Max_Month = a.Max_Month, Math_Max = a.Max, Chem_Max_Month = (subpet == null ? 0 : subpet.Chem_Max_Month), Chem_Max = (subpet == null ? 0 : subpet.Chem_Max) }).ToList();
Right Join...
var lstRightJoin = (from a in result_two
join b in result_one
on new { a.Name, a.Class } equals new { b.Name, b.Class }
into gj
from subpet in gj.DefaultIfEmpty()
select new { a.Name, a.Class, Math_Max_Month = (subpet == null ? 0 : subpet.Max_Month), Math_Max = (subpet == null ? 0 : subpet.Max), a.Chem_Max_Month, a.Chem_Max }).ToList();
Finaly the Union...
var lstUnion = lstLeftJoin.Select(s => new { Name = s.Name, Class = s.Class, Math_Max_Month = s.Math_Max_Month, Math_Max = s.Math_Max, Chem_Max_Month = s.Chem_Max_Month, Chem_Max = s.Chem_Max }).Union(lstRightJoin.Select(s => new { Name = s.Name, Class = s.Class, Math_Max_Month = s.Math_Max_Month, Math_Max = s.Math_Max, Chem_Max_Month = s.Chem_Max_Month, Chem_Max = s.Chem_Max })).OrderBy(o => o.Name).ThenBy(c => c.Class).ToList();
RESULT
Name Class Math_Max_Month Math_Max Chem_Max_Month Chem_Max
Alex C1 12 90 null null
Alex C2 11 91 12 92
Bob C1 null null 12 97
Bob C2 12 94 null null

Linq involving groupby orderby and join

I have two tables
tblEquipment
Id1 Id2 Version1 Version2
1 1 - 0
2 1 A 1
3 1 B 1
4 1 B 2
5 2 - 0
6 2 A 0
and another table
tblHistory
IdParent Version1 Version2 Date
1 - 0 1/01/14
1 A 1 2/01/14
1 B 1 3/01/14
1 B 2 4/01/14
2 - 0 4/01/14
2 A 0 6/01/14
2 A 0 8/01/14
I am trying to write a query that fetches the record which has the maximum Version1 and Version2 corresponding to the maximum version1. For e.g. I want the following records from the above table -
Id2 = 1, Version1 = B, Version2 = 2 and Date = 4/01/14
Id2 = 2, Version1 = A, Version2 = 0 and Date = 8/01/14
Can anyone help me with the linq that gives me the above result.
Fetching data according the rule you described would be like this:
var result = history
.GroupBy( h => h.IdParent )
.Select( h => h.OrderBy( h1 => h1.Version1 )
.ThenBy( h2 => h2.Version2 )
.Last() )
.Select(h => new {
Id2 = h.IdParent,
Version1 = h.Version1,
Version2 = h.Version2,
Date = h.Date
}
);

Selecting Multiple Enum Values when comparing

I am trying to select different type of Enum Values when comparing, based on what user selected.
This is my code:
public enum CategoryType { E1, E2, E3, E4 }
List1.Add(new model{ Line = "Line 1", Category = model.CategoryType.E3| model.CategoryType.E1});
List1.Add(new model{ Line = "Line 2", Category = model.CategoryType.E2 | model.CategoryType.E1});
List1.Add(new model{ Line = "Line 3", Category = model.CategoryType.E4 | model.CategoryType.E3});
var modelEnum = CategoryType.E1 | CategoryType.E3
var ValidLines = List1.Where(P => P.Category == modeEnum ).ToList()
.Select(P => P.Line).ToList();
The above code does not work. Since I am looking for E1 or E3, it should return ANY items that contains E1 or E3. In this case, it should return all 3 items above because all of them contains either E1 or E3.
What am I doing wrong?
Thanks
You seem to be a bit confused. I believe what you want is to use the Flags attribute on your enum then assign unique values to them (it's difficult cause your code is invalid). For example:
[Flags]
public enum CategoryType { E1 = 1, E2 = 2, E3 = 4, E4 = 8 }
In this way your 'bit or' operator (|) will allow you to properly combine your values (up to 31 values, plus 0) into a unique value. To test it you want to do something like this:
if ((value & CategoryType.E2) == CategoryType.E2) { ...
They way you have it your bit or's will return non-unique ints. What you have is this:
public enum CategoryType { E1 = 0, E2 = 1, E3 = 2, E4 = 3 }
This is the default enum behavior. So E1 | E3 is 0 | 2 which is 2. E2 | E3 is 1 | 2 which is 3, which is the same as E4.

Categories

Resources