In SQL, when you do a bunch of joins, it treats all of the joined objects as one "super-object" to be selected from. This remains the case when you group by a particular column, as long as you include anything you select in the grouping (unless it is produced by the grouping, such as summing a bunch of int columns).
In LINQ, you can similarly do a bunch of joins in a row, and select from them. However, when you perform a grouping, it behaves differently. The syntax in query-style LINQ only allows for grouping a single table (i.e., one of your joins), discarding the others.
For an example case suppose we have a few tables:
Request
-------
int ID (PK)
datetime Created
int StatusID (FK)
Item
----
int ID (PK)
string Name
RequestItem
-----------
int ID (PK)
int ItemID (FK)
int RequestID (FK)
int Quantity
Inventory
---------
int ID (PK)
int ItemID (FK)
int Quantity
LU_Status
---------
int ID (PK)
string Description
In our example, LU_Status has three values in the database:
1 - New
2 - Approved
3 - Completed
This is a simplified version of the actual situation that lead me to this question. Given this schema, the need is to produce a report that shows the number of requested items (status not "Completed"), approved items (status "Approved"), distributed items (status "Completed"), and the number of items in stock (from Inventory), all grouped by the item. If this is a bit vague take a look at the SQL or let me know and I'll try to make it clearer.
In SQL I might do this:
select i.Name,
Requested = sum(ri.Quantity),
Approved = sum(case when r.StatusID = 2 then ri.Quantity else 0 end)
Distributed = sum(case when r.StatusID = 3 then ri.Quantity else 0 end)
Storage = sum(Storage)
from RequestItem as ri
inner join Request as r on r.ID = ri.RequestID
inner join Item as i on i.ID = ri.ItemID
inner join (select ItemID, Storage = sum(Quantity)
from Inventory
group by ItemID)
as inv on inv.ItemID = ri.ItemID
group by i.Name
This produces the desired result.
I began to rewrite this in LINQ, and got so far as:
var result = from ri in RequestItem
join r in Request on ri.RequestID equals r.ID
join i in Item on ri.ItemID equals i.ID
join x in (from inv in Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) })
on ri.ItemID equals x.ItemID
group...????
At this point everything had been going smoothly, but I realized that I couldn't simply group by i.Name like I did in SQL. In fact, there seemed to be no way to group all of the joined things together so that I could select the necessary things from them, so I was forced to stop there.. I understand how to use the group syntax in simpler situations (see the subquery), but if there's a way to do this sort of grouping in LINQ I'm not seeing it, and searching around here and elsewhere has not illuminated me.
Is this a shortcoming of LINQ, or am I missing something?
You can create an anonymous type in a grouping that contains all data you need:
var result = from ri in RequestItem
join r in Request on ri.RequestID equals r.ID
join i in Item on ri.ItemID equals i.ID
join x in (from inv in Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) })
on ri.ItemID equals x.ItemID
group new
{
i.Name,
r.StatusId,
ri.Quantity,
x.Storage,
}
by i.Name into grp
select new
{
grp.Key,
Requested = grp.Where(x => x.StatusID == 2).Sum(x => x.Quantity),
Distributed = grp.Where(x => x.StatusID == 3).Sum(x => x.Quantity),
Storage = grp.Sum(x => x.Storage)
}
(not tested, obviously, but it should be close).
The easiest way is to use group new { ... } by ... construct and include all the items from the joins that you need later inside the { ... }, like this
var query =
from ri in db.RequestItem
join r in db.Request on ri.RequestID equals r.ID
join i in db.Item on ri.ItemID equals i.ID
join x in (from inv in db.Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) }
) on ri.ItemID equals x.ItemID
group new { ri, r, i, x } by i.Name into g
select new
{
Name = g.Key,
Requested = g.Sum(e => e.ri.Quantity),
Approved = g.Sum(e => e.r.StatusID == 2 ? e.ri.Quantity : 0),
Distributed = g.Sum(e => e.r.StatusID == 3 ? e.ri.Quantity : 0),
Storage = g.Sum(e => e.x.Storage)
};
Related
I need to translate SQL query to LINQ and have no idea how.
I have two tables: Bins and DataFromBins.
DataFromBins contains column BinId which refers to Bins.Id
What my query does is selecting most recent row for each BinId from DataFromBins and joining some data from Bins for these BinIds.
Please help :(
SELECT BinId, Address, Lon, Lat, MaxFillLevel, Distance
FROM (
SELECT DataFromBins.*
FROM (
SELECT DataFromBins.BinId, MAX(DataFromBins.Date) AS Date
FROM DataFromBins
GROUP BY DataFromBins.BinId
) AS latest_records
INNER JOIN DataFromBins ON DataFromBins.BinId = latest_records.BinId
AND DataFromBins.Date = latest_records.Date
) AS most_recent
INNER JOIN Bins ON most_recent.BinId = Bins.Id
I guess you are seeking the code below. The trick is to split your subqueries ;)
suppose _db is your context.
var latestRecords = from t in _db.DataFromBins
group t by t.BinId into g
select new
{
BinId = g.Key,
Date = (from t2 in g select t2.Date).Max()
};
var mostRecents = from itm in latestRecords
join bin in _db.DataFromBins on new {BinId = itm.BinId, Date =itm.Date } equals new {BinId = bin.BinId , Date =bin.Date}
select bin;
var finalQuery = from recent in mostRecents
join bin _db.Bins on recent.BinId equals bin.Id
select new {
bin.BinId,bin.Address, bin.Lon, bin.Lat, bin.MaxFillLevel, bin.Distance
}
how to create this query using model in yii2
select *,p1.plan_id from product p1
where id in (select max(p2.id) from product p2 where p2.plan_id = p1.plan_id)
form the following table Product
id product_name plan_id
-------------------------------
1 bottle 1
2 book 2
3 book 2
4 bottle 1
5 notbook 3
You could refactor you query using an inner join eg:
$sql = "select *
from product p1
inner join (
select plan_id, max(id) max_id
from product
group by plain_id
) t on t.plan_id = p1.plan_id and p1.id = t.max_id";
and in some case could be useful findBySql, assuming your model is name Product
$models = Product::findBySql($sql)->all();
This should be the exact query you would do, but it won't work for MySQL:
// select *, p1.plan_id from product p1
// where id in (select max(p2.id) from product p2 where p2.plan_id = p1.plan_id)
$subquery = (new \yii\db\Query)->select('[[p2]].[[id]]')
->from(['p2' => 'product'])
->where('[[p2]].[[plan_id]] = [[p1]].[[plan_id]]')
->orderBy('[[p2]].[[id]] DESC')->limit(1); // equiv to max(p2.id)
$query = (new \yii\db\Query)->from(['p1' => 'product'])
->where(['p1.id' => $subquery])
->one();
// PS: Yii will quote fields wrapped in `[[]]` double square-brackets.
Using joins
So, you should use innerJoin (or other variants) to achieve the same result working for any database:
$query = (new \yii\db\Query)->from(['p1' => 'product'])
->innerJoin(['product p2'], '[[p2]].[[plan_id]] = [[p1]].[[plan_id]]')
->orderBy('[[p2]].[[id]] DESC')->limit(1); // equiv to max(p2.id)
->one();
We had an issue where our workflows have not been creating activities.
I now need to report which accounts have not had their workflows invoked.
I've tried advanced find and then moved to sql.
My question is can someone provide a simple starter query to pull which 'entity' has NOT had a specific activity associated with it?
Please let me know if the question is not clear enough or more info, is needed.
Below is a solution using SQL where I step through my thought process, and below that is a solution that gets started with the C# API (edit: just realized this is for a report, so that part can be disregarded). I've commented in most places, so I hope my methods are fairly straightforward.
SQL
1.
--get all the entities that aren't activities and aren't intersect entities (N:N tables)
--Put in your own where conditions to further filter this list,
--which is still probably far too expansive
SELECT
A.name EntityName
FROM MetadataSchema.Entity A
WHERE
A.IsActivity = 0
AND A.IsIntersect = 0
2.
--CROSS JOIN the non-activity entities with the activity entities
--to get a list of all possible entity/activity pairings
SELECT DISTINCT
A.name EntityName
, B.Name ActivityName
FROM MetadataSchema.Entity A
CROSS JOIN MetadataSchema.Entity B
WHERE
A.IsActivity = 0
AND A.IsIntersect = 0
AND B.IsActivity = 1
3.
--LEFT JOIN the partial cartesian join above against the Activity table,
--making a note of which entities actually have activity records.
--This will provide a complete list of which entity/activity pairings
--exist and don't exist
SELECT
A.name EntityName
, B.Name ActivityName
--if there is a matching activity, the unique key,
--ActivityTypeCode (int), will be positive.
--So, if there is a positive sum for an entity/activity
--pairing, you know there is a valid pair; otherwise
--no pair
, CAST(CASE WHEN sum(coalesce(C.ActivityTypeCode, 0)) > 0
THEN 1
ELSE 0
END AS BIT) EntityOwnsActivity
FROM MetadataSchema.Entity A
CROSS JOIN MetadataSchema.Entity B
LEFT JOIN dbo.ActivityPointer C ON
--ObjectTypeCode is a unique identifier for Entities;
--RegardingObjectTypeCode is the code for the entity type
--associated with a particular activity
A.ObjectTypeCode = C.RegardingObjectTypeCode
--ActivityTypeCode is the code for the particular activity
AND B.ObjectTypeCode = C.ActivityTypeCode
WHERE
A.IsActivity = 0
AND A.IsIntersect = 0
AND B.IsActivity = 1
GROUP BY
A.name
, B.Name
4.
--Putting it all together, using the above master table,
--filter out the entities/activities you're interested in
--(in this case, all entities that aren't associated with
--any emails)
SELECT
EntityName
FROM
(
SELECT
A.name EntityName
, B.Name ActivityName
, CAST(CASE WHEN sum(coalesce(C.ActivityTypeCode, 0)) > 0
THEN 1
ELSE 0
END AS BIT) EntityOwnsActivity
FROM MetadataSchema.Entity A
CROSS JOIN MetadataSchema.Entity B
LEFT JOIN dbo.ActivityPointer C ON
A.ObjectTypeCode = C.RegardingObjectTypeCode
AND B.ObjectTypeCode = C.ActivityTypeCode
WHERE
A.IsActivity = 0
AND A.IsIntersect = 0
AND B.IsActivity = 1
GROUP BY
A.name
, B.Name
) EntityActivities
WHERE ActivityName = 'Email'
AND EntityOwnsActivity = 0
ORDER BY
EntityName
C#.NET API
using (OrganizationServiceProxy _serviceProxy =
new OrganizationServiceProxy(
new Uri(".../XRMServices/2011/Organization.svc"), null, null, null))
{
_serviceProxy.EnableProxyTypes();
RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest()
{
EntityFilters = EntityFilters.Entity,
RetrieveAsIfPublished = true
};
// Retrieve the MetaData.
EntityMetadata[] entities =
((RetrieveAllEntitiesResponse)_serviceProxy.Execute(request)).EntityMetadata;
var ents = from e1 in entities.Where(x => x.IsActivity != true)
.Where(x => x.IsIntersect != true)
from e2 in entities.Where(x => x.IsActivity == true)
select new
{
entityName = e1.SchemaName
,
activityName = e2.SchemaName
};
//at this point, because of the limited nature of the Linq provider for left joins
//and sums, probably the best approach is to do a fetch query on each entity/activity
//combo, do some sort of sum and find out which combos have matches
// in the activity pointer table
//API = very inefficient; maybe improved in next CRM release? Let's hope so!
}
I want to receive property name and units count and specails count. I have this query:
SELECT
`property`.`property_name`,
COUNT(unit_id) AS `units_count`,
COUNT(special_id) AS `specials_count`
FROM `property`
LEFT JOIN `property_unit` ON unit_property_id = property_id
LEFT JOIN `property_special` ON special_property_id = property_id
WHERE (property_id = '1')
GROUP BY `property_id`
ORDER BY `property_name` ASC
But it is not working properly. If I have one of these left joins - it's ok, but if I have two, I get this result:
["property_name"] => string(11) "Rivers Edge"
["units_count"] => string(1) "2"
["specials_count"] => string(1) "2"
Specials count is 2 and units_count is 2, but units count is really '1'. How can I get correct counts for it?
P.S: for those who know Zend Framework:
$select->setIntegrityCheck(FALSE)
->from(
'property',
array(
'property_name',
)
)
->joinLeft(
'property_unit',
'unit_property_id = property_id',
array(
'units_count' => 'COUNT(unit_id)'
)
)
->joinLeft(
'property_special',
'special_property_id = property_id',
array(
'specials_count' => 'COUNT(special_id)'
)
)
->group('property_id')
->order('property_name');
Try this:
SELECT
`property`.`property_name`,
COUNT(distinct unit_id) AS `units_count`,
COUNT(distinct special_id) AS `specials_count`
FROM `property`
LEFT JOIN `property_unit` ON unit_property_id = property_id
LEFT JOIN `property_special` ON special_property_id = property_id
WHERE (property_id = '1')
GROUP BY `property_id`
ORDER BY `property_name` ASC
EDIT:
You shouldn't always use distinct - it happens to be the right option in this case.
select count(fieldname) returns the number of times that fieldname is not null; select count(distinct fieldname) returns the number of distinct values of fieldname.
In the original query, property_unit and property_special aren't joined to each other, only to property - so for a single property that had 5 units and 7 specials, 35 rows would be returned; therefore count(unit_id) and count(special_id) would both return 35. Since there would be 5 distinct values of unit_id and 7 distinct values of special_id (because these fields uniquely identify their records), count(distinct ...) returns the correct values in these circumstances.
Your SQL should be something like this:
SELECT
`property`.`property_name`,
COUNT(property_unit.unit_id) AS `units_count`,
COUNT(property_special.special_id) AS `specials_count`
FROM `property`
LEFT JOIN `property_unit` ON (property_unit.unit_property_id = property.property_id)
LEFT JOIN `property_special` ON (property_special.special_property_id = property.property_id)
WHERE (property.property_id = '1')
GROUP BY `property.property_id`
ORDER BY `property.property_name` ASC
var grid = new System.Web.UI.WebControls.GridView();
grid.DataSource = from booking in bookings
join f in getallAttendees on booking.UserID equals f.UserID into fg
from fgi in fg.DefaultIfEmpty() //Where(f => f.EventID == booking.EventID)
where
booking.EventID == id
select new
{
EventID = booking.EventID,
UserID = booking.UserID,
TrackName = booking.Name,
BookingStatus = booking.StatusID,
AttendeeName = booking.FirstName,
// name = account.FirstName,
AmountPaid = booking.Cost,
AttendeeAddress = booking.DeliveryAdd1,
City = booking.DeliveryCity,
Postcode = booking.Postcode,
Date = booking.DateAdded,
product = fgi == null ? null : fgi.productPurchased
};
Booking table design:
ID, EventID, UserID, BookingStatus, AmountPaid, AttendeeAddress, City, Date
Product table design:
ID, EventID, SecondDriver, Apples, Bananas, SecondItem
The association between booking and product table is many to many and the foreign key is UserID
The above Linq-to-SQL query returns the data of only users whose booking.UserID equals att.UserID, what I want is to return all the users of that particular event also with apples and banana fields if they have bought else fill null in that columns.
Any possible solutions or examples will be highly appreciated.
I think you are looking for an equivalent to a Left Join. You can accomplish this by using the DefaultIfEmpty() extension method. Modify your join statement and add another from in like so:
join at in getallAttendees on booking.UserID equals at.UserID into attg
from att in attg
In your select statement you can use the ternary operator ? : like so:
SecondItem = att == null ? null : att.SecondItem,
Edit
Your bookings and getallAttendees IQueryable need to come from the same context if you want to join them together.
Otherwise you will need to use getallAttendees.ToList() and bookings.ToList()