In my LINQ query below I want to select the 'product' rows and add the 'sale' rows data into it but the opposite is happening, it's selecting the 'sale' rows and adding the 'product' rows
var query = (from product in SampleProductTable
from sale in SampleSalesTable
where (sale.ProductId == product.Id)
select new
{
Id = product.Id,
TotalSales = product.TotalSales + ((product.Id == sale.ProductId) ? sale.Amount : 0)
})
Sample Product Table
+-------+------------+---------+-----------------+-------+------------+
| Id | CategoryId | BrandId | Name | Price | TotalSales |
+-------+------------+---------+-----------------+-------+------------+
| mlk3 | MLK | BRND1 | Creamy Milk | 5 | 10 |
| snck2 | SNCK | BRND2 | Chocolate Snack | 2 | 24 |
+-------+------------+---------+-----------------+-------+------------+
Sample Sales Table
+-----+-----------+--------+
| Id | ProductId | Amount |
+-----+-----------+--------+
| 120 | mlk3 | 55 |
| 121 | mlk3 | 15 |
| 122 | snck2 | 12 |
| 123 | mlk3 | 5 |
| 124 | mlk3 | 10 |
| 125 | snck2 | 2 |
| 126 | mlk3 | 115 |
| 127 | snck2 | 6 |
| 128 | snck2 | 34 |
+-----+-----------+--------+
Desired Output
+-------+------------+
| Id | TotalSales |
+-------+------------+
| mlk3 | 210 |
| snck2 | 78 |
+-------+------------+
var answer = (from product in SampleProductTable
join sale in SampleSalesTable on product.Id == sale.ProductId into subSales
from subSale in subSales.DefaultIfEmpty()
group subSale by new { product.Id, product.TotalSales } into gr
select new
{
gr.Key.Id,
TotalSales = gr.Sum(x => x == null ? 0 : x.Amount) + gr.Key.TotalSales
}).ToList();
Approximate T-SQL:
select p.Id, p.TotalSales + sum(coalesce(s.Amount, 0)) TotalSales
from SampleProductTable p
left outer join SampleSalesTable s on p.Id = s.ProductId
group by p.Id, p.TotalSales
In your example you join two collections the way that the result will have as many rows as there are distinct child records (sales in this case) and create a new object for each record (similar to INNER JOIN). That's why the result is "sales-based".
If I understand your intent correctly, I would approach it like:
SampleProductTable.Select(p => new
{
Id = p.Id,
TotalSales = p.Sales.Sum(s => s.Amount)
}
please note that for this approach you will need to map "Sales" collection on a product.
First of all it would be better to use the join statement instead, and then, it seems you need to group your Sales tables based on the ProductId:
var query = (from product in SampleProductTable
join sale in SampleSalesTable.GroupBy(c => c.ProductId)
on product.Id equals sale.Key
select new
{
Id = product.Id,
TotalSales = product.TotalSales + sale.Sum(c=>c.Amount)
}).ToList();
Also please note: since you used a where statement in your code, you don't need to use this condition (product.Id == sale.ProductId) ? in your select anymore. Same as mine, because I used the join statement with on keyword, there is no need to use the condition in the select area.
You can see your desired result in the following link:
https://dotnetfiddle.net/RFTtrv
In general LINQ terms the query shape you are looking for is called grouped join:
The group join is useful for producing hierarchical data structures. It pairs each element from the first collection with a set of correlated elements from the second collection.
In your case, it will produce a collection of correlated Sales for each Product. Then all you need is to apply aggregate (Sum) inside the final projection (select):
var query =
from product in SampleProductTable
join sale in SampleSalesTable on product.Id equals sale.ProductId into productSales
select new
{
Id = product.Id,
TotalSales = product.TotalSales + productSales.Sum(sale => sale.Amount)
};
But since in some of the comments you mentioned converting to SQL, most likely you are using some ORM like LinqToSQL, EF or EF Core. In such case the things are even simpler. These ORMs support a so called navigation properties which represent the relationships, and when used inside queries are translated to SQL with all the necessary joins, so you don't need to be bothered with such details and can concentrate on the logic needed to produce the desired result.
If that's the case, the Product class would normally have something like
public ICollection<Sale> Sales { get; set; }
and the query in question would be simple Select like this:
var query = db.Products
.Select(product => new
{
Id = product.Id,
TotalSales = product.TotalSales + product.Sales.Sum(sale => sale.Amount)
});
LEFT JOIN with grouping looks like
var query =
from product in SampleProductTable
join sale in SampleSalesTable.GroupBy(c => c.ProductId)
on product.Id equals sale.Key into join1
from lj in join1.DefaultIfEmpty() // left join
select new
{
Id = product.Id,
TotalSales = product.TotalSales + (lj == null ? 0 : lj.Sum(c => c.Amount))
};
Left join may return null, so check the potential group, lj before trying to sum it. For later c# versions null check could be abbreviated to
TotalSales = product.TotalSales + (lj?.Sum(c => c.Amount) ?? 0)
Fiddle
In query syntax, Slava's solution should return with the result you're looking for i.e.
var querySyntax = (from product in SampleProductTable
join sale in SampleSalesTable on product.Id equals sale.ProductId into sales
from subSales in sales.DefaultIfEmpty()
group subSales by new { product.Id, product.TotalSales }
into grp
select new
{
grp.Key.Id,
TotalSales = grp.Sum(s => s.Amount) + grp.Key.TotalSales
}).ToList();
If you have a burning desire to use method syntax for whatever reason, this equivalent LINQ query will also work:
var methodSyntax = (SampleProductTable
.GroupJoin(SampleSalesTable, product => product.Id, sale => sale.ProductId,
(product, sales) => new {product, sales})
.SelectMany(s => s.sales.DefaultIfEmpty(), (s, subSales) => new {s, subSales})
.GroupBy(ss => new {ss.s.product.Id, ss.s.product.TotalSales}, ss => ss.subSales)
.Select(grp => new {grp.Key.Id, TotalSales = grp.Sum(s => s.Amount) + grp.Key.TotalSales})).ToList();
Related
Database
Cars | CarDetails | Owners
----------------------------------
Id | CarDetailId | Id
Name | CarId | CarId
Type | CarId | OwnerName
| | PhoneNumber
LINQ Code
var intiQuery = from c in Cars
join cd in CarDetails
join o in Owners
select new { c,cd,o}
var results = from qry in intiQuery
group new { qry.c, qry.cd, qry.o} by qry.c.Id into g
select new
select new { CarId= g.Key,
Name = g.Select(g=>g.c.Name).FirstOrDefault(),
Type = g.Select(g=>g.c.Type).FirstOrDefault(),
Price= g.Select(g=>g.cd.Price).FirstOrDefault(),
OwnerName= g.Select(g=>g.o.OwnerName).FirstOrDefault(),
PhoneNumber= g.Select(g=>g.o.PhoneNumber).FirstOrDefault(),
}
My question is simply how to increase the performance when calling this query as you can see for each field, I need to.Select().FirstOrDefault() to get the corresponding data. If let's says I got 100 data I will need to get the data one by one 500 times it will take ages to display the data.
Extra Info in case someone not clear.
Cars
Id |Name |Type
-----------------------------------
1 |Toyota |Family
CarDetails
CarDetailId | CarId | Price
-----------------------------------
1 | 1 | 200000
Owners
Id| CarId | OwnerName | PhoneNumber
-----------------------------------
1 | 1 | Mitch | 48774800
2 | 1 | Camilo | 87404078
The result I wanted to get is something like this hope some of you can have a clearer picture
CarId| Name | Type | Price |OwnerName |PhoneNumber
----------------------------------------------------------------------
1 | Toyota | Family | 200000 | Mitch,Camilo | 48774800,87404078
This is absolutely not the best solution, but it will certainly be easy to understand and if you are not filtering the data then it may be good enough.
As you state, the performance problem is because your hitting your database hundreds of time, and so we can easily avoid that by simply pulling the information from the database in three simple queries.
var allCars = Cars.ToList();
var allCarDetails = CarDetails.ToList();
var allOwners= Owners.ToList();
Once you have all this information in memory, you can manipulate the in-memory objects to produce the results you need.
var results = (from car in allCars
let owners = allOwners.Where(a => a.CarID == car.Id)
select new
{
CarID = car.Id,
car.Name,
car.Type,
Price = allCarDetails.Where(a => a.CarID == car.Id).Select(a => a.Price).SingleOrDefault(),
OwnerName = String.Join(',', owners.Select(a => a.Name)),
PhoneNumber = String.Join(',', owners.Select(a => a.PhoneNumber))
}
);
If your tables have a lot of extra fields which you have not mentioned, then we may want to change the initial three queries to just pull the information required.
I have following table:
ID | Type | Desc | Amount
---------------------------------------
Stationery| Pen | Red Pen | 1.00
Stationery| Pen | Blue Pen | 1.10
Stationery| Ruler | A Ruler | 1.50
Stationery| Ruler | B Ruler | 1.40
I want get the Sum Amount for different type. The sum for Pen is 2.10, and for ruler is 2.90. How to I achieve this? I have try using "Group By". Here is my query:
From c In DB.Shop
Group By c.Type
Into TotalAmount=Sum(c.Amount) Select Type,TotalAmount
Then I will select the value and bind to gridview. Binding data to gridview is not the issue for me. The expected result for gridview as follow:
ID | PenAmount | RulerAmount
-------------------------------------
Stationery| 2.10 | 2.90
Please help. Thanks
Here is an example :
var query =
(from c in DB.Shop
group c by c.Type
into grp
select new
{
type = grp.Key,
value = grp.Sum(g => g.Amount)
});
grp.Key will contains c.Type because the grouping constructed by Type property. You may need to make some adjustment to get the exact working code, because I didn't test the linq above and I don't know exactly about your object model.
try this
var query =
(from c in DB.Shop
group c by c.Type
into grp
select new
{
Type= g.Key,
PenAmount = g.Where(c => c.Type=="Pen").Sum(c => c.Amount),
RulerAmount= g.Where(c => c.Type=="Ruler").Sum(c => c.Amount),
});
My database has a sales table with entries like so:
_____________________________________
| id | title_id | qty |
-------------------------------------
| 0 | 6 | 10 |
-------------------------------------
| 1 | 5 | 5 |
-------------------------------------
| 2 | 6 | 2 |
-------------------------------------
Title_id is Foreign key pointing to Titles table which is as follows:
_____________________________________
| id | title_id | title |
-------------------------------------
| 0 | 5 | Soda |
-------------------------------------
| 1 | 6 | Coffee |
-------------------------------------
I want to find top 5 sold products wich means i need to calculate the qty value for each product for all it's entried in sales table then order the result by qty in descending order and limit the select to 5.
However I'm new to C# ASP.NET and somewhat new to SQL. I dont know how to do this with LINQ.
This is my code so far:
var getIds = (from sale in db.sales
join tit in db.titles on sale.title_id equals tit.title_id
group sale by sale.qty into result
orderby result.Sum(i => i.qty) descending
select new Publication
{
PubID = sales.title_id, Title = tit.title
}
).Take(5);
Assuming you have a navigation property Sale.Title, something like this should do:
var tops =
db.Sales
.GroupBy( o => o.Title )
.Select( o => new { Title = o.Key, Sum = o.Sum( x => x.Quantity ) } )
.OrderByDescending( o => o.Sum )
.Take( 5 )
.ToList();
tops is then a list of an anonymous type with two properties: the Title object and the sum of the quantities.
You can then get the values like this:
foreach( var top in tops )
{
int titleId = top.Title.title_id;
string title = top.Title.title;
int sumOfQuantities = top.Sum;
...
If you just want the top Title objects, can can select them like this:
List<Title> topTitles = tops.Select( o => o.Title ).ToList();
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
orderby Name descending
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).Take(5);
If the entries in the Sales table are more than one per item (ie: in your example you have 'Soda' 10 + 'Soda' 2, then you need to GroupBy(), using the name as the key (or it's related id if it's in another table), but not the qty.
var topSales = db.sales.GroupBy(x => x.title)
.Select(g => new
{
Title = g.Key,
Qty = g.Sum(x => x.qty)
})
.OrderByDescending(x => x.Qty)
.Select(x => new Publication
{
PubID = x.Title.title_id,
Title = x.Title.title1
})
.Take(5)
.ToList();
Note that I've omitted the join statement assuming that you have a foreign key between sales.title_id -> title.id, and you are using LINQ to SQL. Also note that I've avoided using the query syntax in favor of the chained method syntax, I think it's much clear in this use case (although not always true, ie: cross-joins).
Also, SQL and LINQ have some similarities but don't let the names of clauses/methods fool you, LINQ is not SQL, IMHO, Microsoft just tried to make people comfortable by making it look similar ;)
EDIT: fixed GroupBy()
var result= (from p in sales
let k = new
{
Name = p.Name
}
group p by k into t
select new
{
Name = t.Name,
Qty = t.Sum(p => p.Qty)
}).OrderByDescending(i => i.Qty).Take(5);
You need to look at GroupBy; this will give you what you need
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
I've currently got this sample table of data:
ID | Policy ID | History ID | Policy name
1 | 1 | 0 | Test
2 | 1 | 1 | Test
3 | 2 | 0 | Test1
4 | 2 | 1 | Test1
Out of this, I want to group by the Policy ID and History ID (MAX), so the records I want to be kept are ID's 2 and 4:
ID | Policy ID | History ID | Policy name
2 | 1 | 1 | Test
4 | 2 | 1 | Test1
I've tried to do this in LINQ and stumbling on the same issue every time. I can group my entities, but always into a group where I have to re-define the properties, rather than have them kept from my Policy objects. Such as:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
This simply just brings out a list of objects which have "Policy ID" and "History ID" within them. I want all the properties returned from the Policies object, without having to redefine them all, as there are around 50+ properties in this object.
I tried:
var policies = _context.Policies.GroupBy(a => a.intPolicyId)
.Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
PolicyObject = group;
});
But this errors out.
Any ideas?
Group by composite key
_context.Policies.GroupBy(a => new {a.intPolicyId, *other fields*}).Select(
group=> new {
PolicyId = group.Key.intPolicyId,
HistoryId = group.Max(intHistoryId),
*other fields*
}
);
Another way - grab histories, than join back with the rest of the data, something like this (won't work out of the box, will require some refining)
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.intPolicyId equals p.intPolicyId
select new {h.HistoryId, *all other policy fields*}
And yet another way, even simpler and not require a lot of typing :):
var historyIDs = _context.Policies.GroupBy(a=>a.intPolicyId).Select(group => new {
PolicyID = group.Key,
HistoryID = group.Max(a => a.intHistoryID)
});
var finalData = from h in historyIDs
join p in _context.Policies on h.PolicyId equals p.intPolicyId && h.HistoryId equals p.HistoryId
select p
Basically it's somewhat equivalent to the following SQL query:
select p.*
from Policy p
inner join (
select pi.policyId, max(pi.historyId)
from Policy pi
group by pi.policyId
) pp on pp.policyId = p.policyId and pp.historyId = p.historyId
In LINQ to Objects, I'd do this as
var policies = _context.Policies
.GroupBy(a => a.intPolicyId)
.Select(g => g.OrderByDescending(p => p.intHistoryID).First());
but your _context impleis there might be a database involved and I'm not 100% sure this will translate.
Basically it groups by the policy ID as you'd expect, then within each group orders by history ID and from each group selects the row with the highest history ID. It returns exactly the same type as is found in Policies.
I have 5 tables:
course_id | course_name (course)
------------------------------
1 | Basic1
2 | Basic2
3 | Basic3
4 | Basic4
5 | Basic5
course_id | trainer_id (course_trainer)
-----------------------------
1 | 1
1 | 2
2 | 2
3 | 2
4 | 3
4 | 2
5 | 3
course_id | topic_id (course_topic)
-----------------------------
1 | 1
1 | 2
2 | 2
3 | 2
4 | 3
4 | 2
5 | 3
trainer_id| trainer_name (trainer)
-----------------------------
1 | Tom
2 | Thomas
3 | Sue
tropic_id | topic_name (topic)
-----------------------------
1 | Skill 1
2 | Skill 2
3 | Skill 3
How can I use LINQ to select with result as below
Course_name | Trainer_name | Topic_name
----------------------------------------------
Basic 1 | Tom, Thomas | Skill 1, Skill 2
Basic 2 | Thomas | Skill 2
Basic 3 | Thomas | Skill 2
Basic 4 | Sue, Thomas | Skill 3, Skill 2
Basic 5 | Sue | Skill 3
That is my code in C#, but the result isn't correct. Please help me, many thanks !
public class course_datatable
{
public string course_name {get; set;}
public string trainer_name {get; set;}
public string topic_name {get; set;}
}
IQueryable<course_datatable> coursequery =
from c in db.course
join ct in db.course_trainer on c.course_id equals ct.course_id
join t in db.trainers on ct.trainer_id equals t.trainer_id
join ctopic in db.course_topic on c.course_id equals ctopic.course_id
join topic in db.topic on ctopic.topic_id equals topic.topic_id
select new course_datatable()
{
course_name = c.course_name,
trainer = t.trainer_name,
topic = topic.topic_name
};
Get your data from your database:
var result = context.Courses.Select(c =>
new { Course = c, Trainers = c.Trainers, Skills = c.Skills }).ToList();
and then flatten the Trainers and Skills objects using String.Join:
result.Select(r => new
{
Course = r.Course.Course_Name,
Trainer = String.Join(",", r.Trainers.Select(t => t.TrainerName).ToArray()),
Skill = String.Join(",", r.Skills.Select(S => S.SkillName).ToArray())
});
edit
Using your schema, I'll rename so that it should work.
var result = db.course.Select(c => new
{
Course = c,
Trainers = c.course_trainer.trainers,
Skills = c.course_topic.topic
}).ToList();
result.Select(r => new
{
Course = r.Course.course_Name,
Trainer = String.Join(",", r.Trainers.Select(t => t.trainer_name).ToArray()),
Skill = String.Join(",", r.Skills.Select(S => S.topic_name).ToArray())
});
You can do this all in one statement but I've structured it this way so that it's hopefully clearer for you.
Because you seem unable to use my initial answer (which is preferred because doesn't require redundant join conditions), I'll work with your existing code and show you how to group and project.
Starting with this:
var coursequery =
from c in db.course
join ct in db.course_trainer on c.course_id equals ct.course_id
join t in db.trainers on ct.trainer_id equals t.trainer_id
join ctopic in db.course_topic on c.course_id equals ctopic.course_id
join topic in db.topic on ctopic.topic_id equals topic.topic_id
select new course_datatable()
{
course_name = c.course_name,
trainer = t.trainer_name,
topic = topic.topic_name
};
You then want to GroupBy the course_name
var groups = coursequery.GroupBy(item => item.course_name);
and then each group needs to project into your new result type
var result = groups.Select(group =>
new course_datatable
{
course_name = group.Key,
trainer_name = String.Join(",", group.Select(i=> i.trainer_name).ToArray()),
topic_name = String.Join(",", group.Select(i => i.topic_name).ToArray()),
}).ToList();
or if you want to try something else (for fun) use LINQs Aggregate method, rarely used:
var result = groups.Select(group =>
group.Aggregate((initial, next) =>
{
initial.topic_name += String.Format(", {0}", next.topic_name);
initial.trainer_name += String.Format(", {0}", next.trainer_name);
return initial;
})).ToList();