Group By and Sum clauses in LINQ - linq-to-sql

I've written a simple linq query as follows:
var query = from c in context.ViewDeliveryClientActualStatus
join b in context.Booking on c.Booking equals b.Id
join bg in context.BookingGoods on c.Booking equals bg.BookingId
select new { c, b, bg };
I have filtered the previous query with a number of premises and then needed to group by a set of fields and get the sum of some of them, as so:
var rows = from a in query
group a by new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode} into g
select new
{
Booking_refex = g.Key.h,
Client_refex = g.Key.b,
//Local = g.
Sum_Quan = g.Sum(p => p.bg.Quantity),
};
I'd like to get a few values from a which I haven't included in the group by clause. How can I get those values? They're not accessible through g.

The g in your LINQ expression is an IEnumerable containing a's with an extra property Key. If you want to access fields of a that are not part of Key you will have to perform some sort of aggregation or selection. If you know that a particular field is the same for all elements in the group you can pick the value of the field from the first element in the group. In this example I assume that c has a field named Value:
var rows = from a in query
group a by new {
h = a.c.BookingRefex,
b = a.c.ClientRefex,
c = a.b.PickupCity,
d = a.b.PickupPostalCode
} into g
select new {
BookingRefex = g.Key.h,
ClientRefex = g.Key.b,
SumQuantity = g.Sum(p => p.bg.Quantity),
Value = g.First().c.Value
};
However, if c.Value is the same within a group you might as well include it in the grouping and access it using g.Key.cValue.

Just add those field in the
new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode}
they will be accessible in g then.

Related

Linq to Sql - Count, sub-query and subtraction

I am trying to convert the following T-SQL statement into Linq to Sql but am having trouble with the subtraction from the count. The final select will be a single row and single column (int)
I have done the SQL in two ways (sub-query and by JOIN/GROUP) which both return the same result, although I think the former might be the 'easier' option...
SQL 1 using a sub-query...
SELECT e.Places - ( SELECT COUNT(*) FROM [Event Participants] ep WHERE ep.E__ID = x AND ep.EP_STAT IN ('B','C')) AS AvailablePlaces
From Events e
WHERE e.E__ID = x
SQL 2 using GROUP BY and JOIN...
SELECT e.Places - COUNT(ep.E__ID) AS AvailablePlaces
FROM Events e
JOIN [Event Participants] ep ON e.E__ID = ep.E__ID
WHERE e.E__ID = x AND ep.EP_STAT IN ('B','C')
GROUP BY e.Places
Something like
var array = new string[] { "B", "C" };
var result = (from e in Event where e.E__ID == x
let count = (from ep in Event_Participants
where ep.E__ID == e.E__ID &&
array.Contains(ep.EP_Stat)
select ep).Count()
select e.Places - count
)
.Single();
Depending on your model, it might be possible to use navigation properties in the subquery.

select by columns multiple criteria

I have this db structure
and this is my joined tables (sample data)
i want to filter when (key = 'price' and value > 4000) and (key = 'top-speed' and value > 200)
thanks for help)
Try this:
SELECT
car.id,
car.name,
key,
value
FROM
car c
LEFT JOIN car_specification_value csv ON c.id = csv.car_id
LEFT JOIN specification s ON csv.specification_id = s.id
WHERE
s.key = 'price'
AND csv.value > 4000
AND s.key = 'top-speed'
AND csv.value > 200;
A common practice is giving aliases to your tables so the join conditions can be determined in a shorter way.
One method uses aggregation:
select csv.car_id
from car_specification_value csv
where (csv.key = 'price' and (csv.value + 0) > 4000) and
(csv.key = 'top-speed' and (csv.value + 0) > 200)
group by csv.car_id
having count(distinct csv.key) = 2; -- both match
Note the + 0. This uses implicit conversion to change the value to a number, so it can be properly compared to a number. One challenge of key/value data structures is that all the values are strings, and that is tricky for other data types.

combine fields and importrange in same

I have a Form which splits into 3 sections
As a result of this. I have some fields which are duplicated.
I am trying to pull rows from the 'FormData' spreadsheet page. Then display them in the 'Report' spreadsheet page. Easy enough.
I then want to merge the duplicated fields together in the report. Fields will only be merged where all but one field is empty.
i.e "Select Col2 where Col1 =''"
FormData
A = Timestamp
B = Name
C = Account Name
D = Contact
E = Notes
F = Task
G = Location
H = Freight to Store
I = When Goods Arrive
J = Freight to Customer
K = Priority
L = Assign To
M = Status
N = Supplier
O = Freight to Store
P = When Goods Arrive
Q = Freight to Customer
R = Priority
S = Assign To
T = Status
U = Priority
V = Assign To
W = Status
Report
FormData column to merge
B & C (Name:),(Account Name: Optional)
K, R, U (Priority:)
L, S, V (Assign To:)
G & N: (Supplier:),(Location:)
H, O (Freight to Store:)
I, P (When Goods Arrive:)
J, Q (Freight to Customer: Optional)
M, T, W (Status:)
I have worked out how to merge cells. Using the below formula.
Example for Merging Column B (Name) and Column C (Account Name)
=iferror({query({IMPORTRANGE("URL","FormData!B2:B20000")},"Select Col1 where Col1 <>''");query({IMPORTRANGE("URL","FormData!B2:B20000"),IMPORTRANGE("URL","FormData!C2:C20000")},"Select Col2 where Col1 =''")})
I am not sure if this goes beyond using a formula or if a script will need to be created.
Either way I will require assistance.
I greatly appreciate any help offered. Am working on this until I have figured it out.
Many Thanks.

LINQ select after grouping

I'm trying to return a result set from a grouped query and I can't get the select right. In LinqPad the cursor jumps to "ItemID" in Grouped.Key.ItemID with the error:
'int' does not contain a definition for 'ItemID' and no extension method 'ItemID' accepting a first argument of type 'int' could be found
Here's the query:
from B in Bids
join I in Items on B.ItemID equals I.ItemID
group new {B, I} by I.ItemID into Grouped
select new {
ItemID = Grouped.Key.ItemID,
ItemName = Grouped.Key.ItemName,
Bids = Grouped.Key.B
}
I would like the return set to have records comprised of the ItemID, ItemName and all of the associated Bid records.
Thanks very much,
BK
The Grouped.Key refers to the field(s) that you specied in the grouped by x clause. As a result in your query, the Key = I.ItemID.
In your example, instead of thinking from the SQL perspective where you have to flatten out heirarchies, embrace the OO nature of LINQ and object graphs. Adapting your example a bit and setting LINQPad to use C# Statements, I think you will end up with more of what you are looking for. Note: The Dump() extension method is specific to LINQPad to output the results and shows the resulting heirarchy.
var bids = new [] { new { ItemID = 1, BidValue = 30 } , new {ItemID=1, BidValue=45}};
var items = new [] { new { ItemID = 1, ItemName = "x" }, new {ItemID = 2, ItemName="y"} };
var query = from i in items
select new
{
i.ItemID,
i.ItemName,
Bids = from b in bids
where b.ItemID == i.ItemID
select b
};
query.Dump();
That being said, your categories indicate LINQ to SQL. If your model is in LINQ to SQL or EF, you may be able to do this even easier by using the mapped associations:
var query = from i in dc.Items
select new
{
i.ItemID,
i.ItemName,
i.Bids
};
query.Dump();
That says exactly what is written. Groupped.Key will contain I.ItemID, but not the whole I. So you can't write Groupped.Key.ItemID.
Consider:
from B in new [] { new { ItemID = 1, BidValue = 30 } }
join I in new [] { new { ItemID = 1, ItemName = "x" } } on B.ItemID equals I.ItemID
group new { B, I } by I into Groupped
select new {
ItemID = Groupped.Key.ItemID,
ItemName = Groupped.Key.ItemName,
Bids = (from g in Groupped select g.B).ToList()
}
Well, assuming you have foreign keys setup in the database from bid -> item there is no need for all this joining an grouping.
Your items will already have a collection of bids in them.
So you can do things like:
var x = db.Items.Single(i=>ItemId == 1); // get one item
foreach (bid b in x.Bids) // iterate through all the bids
{}
If you really want to have them in an anonymous type, this will do:
from i in db.items
select new { i.ItemID, i.ItemName, i.Bids }
That is the beauty of Linq2Sql. Try to let go of writing SQL in Linq but instead use the more object oriented approach.
In this groupby, ItemID is the Key. ItemID does not have a B property.
group new {B, I} by I.ItemID into Grouped
Here's an improved version of your query which accesses the group properly.
from b in Bids
join i in Items on b.ItemID equals i.ItemID
group b by i into groupedBids
select new
{
Item = i,
Bids = groupedBids
};
Here's a version that uses GroupJoin to do the same thing.
from i in Items
join b in Bids on i.ItemID equals b.ItemID into groupedBids
select new
{
Item = i,
Bids = groupedBids
};
Here's a version that does the join in the database and the group locally. You might do something like this since LinqToSql must re-query by the key of a group to get each group's elements (known as the n+1 problem with groupby).
from x in
(
from i in Items
join b in Bids on i.ItemID equals b.ItemID
select new {Item = i, Bid = b}
).ToList()
group x.b by x.i into groupedBids
select new
{
Item = groupedBids.Key,
Bids = groupedBids
};

Linq to SQl - single result

I have been playing with the Linq to Sql and I was wondering if it was possible to get a single result out? For example, I have the following:
using(DataClassContext context = new DataClassContext())
{
var customer = from c in context.table
where c.ID = textboxvalue
select c;
}
And with this I need to do a foreach around the var customer but i know that this will be a single value! Anyone know how I could do a textbox.text = c.name; or something along that line?
Yes, it's possible.
using(DataClassContext context = new DataClassContext())
{
var customer = (from c in context.table
where c.ID = textboxvalue
select c).SingleOrDefault();
}
This way you get 1 result or null if there isn't any result.
You can also use Single(), which throws an exception when there isn't a result.
First() will give you only the first result found, where Last() will give you only the last result, if any.
Here's an Overview of all Enumerable methods.
var customer = context.table.SingleOrDefault(c => c.ID == textboxvalue);