How to use GroupBy properly in LINQ? - linq

I have 4 tables: Post, Category, Relation and Meta
A category can contains multiple posts, and the relation between them is stored in Relation table. A post then can has many extra info that are stored in Meta table. I want to list all post with categories and extra infos, then group them by post's ID.
I have the following query
select p.ID, p.Title, t.Name, m.Key, m.Value from Post p
left join Relation r on p.ID = r.Child
left join Category c on r.Parent = c.ID
left join Meta m on p.ID = m.Object
where m.Type = 'news'
order by p.ID
and with these sample data:
Post
ID Title
1 A
Category
ID Name
1 Tips
2 Tricks
Meta
ID Object Key Value
1 1 Key1 Value 1
2 1 Key2 Value 2
Relation
ID Child Parent
1 1 1
2 1 2
then the result will be
PostID Title Category Key Value
1 A Tips Key1 Value1
1 A Tips Key2 Value2
1 A Tricks Key1 Value1
1 A Tricks Key2 Value2
and I expected the result to be
PostID Title Categories Meta
1 A Tips, Tricks Key1=Value1, Key2=Value2
I wonder if we can convert the query from SQL to LINQ to Entities with EF v4 and the result is stored in a class like this
class Result
{
long ID,
string Title,
List<string> Categories,
Dictionary<string, string> Meta
}
Any helps would be appreciated.

What's the final result you expect from the query
I personally prefer to write the query like
var q = from r in Relation
join p in Post on r.Child equals p.ID
join t in Term on r.Parent equals t.ID
let x = new { p.ID, p.Title, t.Name }
group x by x.ID into g
select g;
this way I think (not sure) the sql generated will be simpler

Now that you're wanting to use EntityFramework, you would merely need to set up you database, edmx with a Result table with an ID and a Title, then Category and Meta tables. Then add one-to-many relationships from the Result table to each the Category and Meta tables.

I'm not 100% sure what you're trying to do, but obviously if you're grouping, the results have to be grouped by anything in the resultset, or be aggregated data. This query will retrieve your results and group by PostId, PostTitle, and CategoryName, generating a single SQL Statement:
var query = from p in Posts
from r in Relations
.Where(r => p.ID == r.Child)
.DefaultIfEmpty()
from c in Categories
.Where(c => r.Parent == c.ID)
.DefaultIfEmpty()
group p by new {ID = p.ID, Title = p.Title, Name = c.Name} into z
select new { ID = z.Key.ID, Title = z.Key.Title, Name = z.Key.Name };
Here is the SQL Generated by this statement:
SELECT [t3].[ID], [t3].[Title], [t3].[value] AS [Name]
FROM (
SELECT [t0].[ID], [t0].[Title], [t2].[Name] AS [value]
FROM [Post] AS [t0]
LEFT OUTER JOIN [Relation] AS [t1] ON [t0].[ID] = [t1].[Child]
LEFT OUTER JOIN [Category] AS [t2] ON [t1].[Parent] = [t2].[ID]
) AS [t3]
GROUP BY [t3].[ID], [t3].[Title], [t3].[value]
Here is the SQL Generated by your original statement:
SELECT [t0].[ID] AS [Key]
FROM [Post] AS [t0]
INNER JOIN [Relation] AS [t1] ON [t0].[ID] = [t1].[Child]
INNER JOIN [Category] AS [t2] ON [t1].[Parent] = [t2].[ID]
GROUP BY [t0].[ID]
GO
-- Region Parameters
DECLARE #x1 Int SET #x1 = 1
-- EndRegion
SELECT [t0].[ID], [t0].[Title], [t2].[Name]
FROM [Post] AS [t0]
INNER JOIN [Relation] AS [t1] ON [t0].[ID] = [t1].[Child]
INNER JOIN [Category] AS [t2] ON [t1].[Parent] = [t2].[ID]
WHERE ((#x1 IS NULL) AND ([t0].[ID] IS NULL)) OR ((#x1 IS NOT NULL) AND ([t0].[ID] IS NOT NULL) AND (#x1 = [t0].[ID]))
GO
-- Region Parameters
DECLARE #x1 Int SET #x1 = 2
-- EndRegion
SELECT [t0].[ID], [t0].[Title], [t2].[Name]
FROM [Post] AS [t0]
INNER JOIN [Relation] AS [t1] ON [t0].[ID] = [t1].[Child]
INNER JOIN [Category] AS [t2] ON [t1].[Parent] = [t2].[ID]
WHERE ((#x1 IS NULL) AND ([t0].[ID] IS NULL)) OR ((#x1 IS NOT NULL) AND ([t0].[ID] IS NOT NULL) AND (#x1 = [t0].[ID]))

Related

Can u help me convert this stored procedure to LINQ

ALTER Procedure [dbo].[GetItemsTypesByProduct]
(
#ProductID INT
)
As
Begin
Select distinct iMap.ProductId, iTyp.Id ItemTypeId, iTyp.ItemType, iTyp.SortOrder
from itemMapping iMap
inner join items itm on itm.itemid = imap.itemid
inner join itemType iTyp on iTyp.id = itm.ItemTypeId
Where ProductId = #ProductID
UNION
Select distinct rMap.ProductId, iTyp.Id, iTyp.ItemType, iTyp.SortOrder
from ReaderMapping rMap
inner join itemType iTyp on iTyp.id = 25
Where ProductId = #ProductID
UNION
Select distinct aMap.ProductId, iTyp.Id, iTyp.ItemType, iTyp.SortOrder
from AssessmentMapping aMap
inner join itemType iTyp on iTyp.id = 24
Where ProductId = #ProductID
order by productid, iTyp.SortOrder
Select distinct aTyp.AssessmentTypeId, AssessmentTypeName, MappingLevel
from eds_quiz..AssessmentType aTyp
inner join eds_quiz..Assessments asm on (asm.AssessmentTypeId = aTyp.AssessmentTypeId)
inner join AssessmentMapping aMap on (aMap.AssessmentId = asm.AssessmentId)
Where ProductId = #ProductID
Select Id, TypeName from eds_edusmart..lessontype
Where Id NOT IN (1, 2, 4)
End

how to write this statement in linq to sql

SELECT TOP (5)
Sales.Product, Sales.Product_Price, COUNT(*) AS CNT,
Products.Category, Products.IMG_URL, Products.Rate_Avg
FROM
Sales
INNER JOIN
Products ON Sales.Product = Products.Product
GROUP BY
Sales.Product, Sales.Product_Price,
Products.Category, Products.IMG_URL, Products.Rate_Avg
HAVING
(COUNT(*) > 1)
ORDER BY CNT DESC
Most of that query has a 1-to-1 correspondence to the equivalent linq-to-sql expression. Though the TOP (5) part needs to be added to the end.
(from s in db.Sales
join p in db.Products on s.Product equals p.Product
group s by new { s.Product, s.Product_Price, p.Category, p.IMG_URL, p.Rate_Avg } into g
where g.Count() > 1
orderby g.Count() descending
select new
{
g.Key.Product,
g.Key.Product_Price,
CNT = g.Count(),
g.Key.Category,
g.Key.IMG_URL,
g.Key.Rate_Avg,
}).Take(5)

Select query with join

I have tables:
How can I select NEWS with concrete TAGS, for example with two TAGS?
It's my wrong variant:
SELECT DISTINCT news.news_id, news.CREATION_DATE, news.MODIFICATION_DATE, news.FULL_TEXT, news.TITLE, news.SHORT_TEXT
FROM
news
INNER JOIN
news_tags
ON news.news_id = news_tags.news_id
WHERE news_tags.TAG_ID = 1 AND news_tags.TAG_ID = 3;
If you want to get news with tags 1 and 3 you should check that a record with tag_id 1 and a record with tag_id 3 exists in the news_tags table. Right now you're asking for news whose tag is 1 and at the same time 3, which doesn't really make sense.
Something like this:
SELECT DISTINCT news.news_id, news.CREATION_DATE, news.MODIFICATION_DATE, news.FULL_TEXT, news.TITLE, news.SHORT_TEXT
FROM news INNER JOIN news_tags ON news.news_id = news_tags.news_id
WHERE EXISTS (SELECT 1
FROM news_tags
WHERE news_id = news.news_id
AND tag_id = 1)
AND EXISTS (SELECT 1
FROM news_tags
WHERE news_id = news.news_id
AND tag_id = 3);
SELECT DISTINCT news.news_id, news.CREATION_DATE, news.MODIFICATION_DATE, news.FULL_TEXT, news.TITLE, news.SHORT_TEXT
FROM
news
INNER JOIN
news_tags
ON news.news_id = news_tags.news_id
inner join Tags on Tags.Tag_Id = news_tags.Tag_Id
WHERE news_tags.TAG_ID = 1 AND news_tags.TAG_ID = 3;

Distinct on one column in linq with joins

I know we can get distinct on one column using following query:
I know we can get distinct on one column using following query:
SELECT *
FROM (SELECT A, B, C,
ROW_NUMBER() OVER (PARTITION BY B ORDER BY A) AS RowNumber
FROM MyTable
WHERE B LIKE 'FOO%') AS a
WHERE a.RowNumber = 1
I have used similar sql query in my case where i am joining multiple tables but my project is in mvc4 and i need linq to entity equivalent of the same. Here is my code:
select * from
(
select fp.URN_No,
ROW_NUMBER() OVER
(PARTITION BY pdh.ChangedOn ORDER BY fp.CreatedOn)
as num,
fp.CreatedOn, pdh.FarmersName, pdh.ChangedOn, cdh.Address1, cdh.State, ich.TypeOfCertificate, ich.IdentityNumber, bdh.bankType, bdh.bankName,
pidh.DistrictId, pidh.PacsRegistrationNumber, idh.IncomeLevel, idh.GrossAnnualIncome
from MST_FarmerProfile as fp inner join PersonalDetailsHistories as pdh on fp.personDetails_Id = pdh.PersonalDetails_Id
inner join ContactDetailsHistories as cdh on fp.contactDetails_Id = cdh.ContactDetails_Id
inner join IdentityCertificateHistories as ich on fp.IdentityCertificate_Id = ich.IdentityCertificate_Id
inner join BankDetailsHistories as bdh on fp.BankDetails_Id = bdh.BankDetails_Id
left join PacInsuranceDataHistories as pidh on fp.PacsInsuranceData_Id = pidh.PacsInsuranceData_Id
left join IncomeDetailsHistories as idh on fp.IncomeDetails_Id = idh.IncomeDetails_Id
where URN_No in(
select distinct MST_FarmerProfile_URN_No from PersonalDetailsHistories where MST_FarmerProfile_URN_No in(
select URN_No from MST_FarmerProfile where (CreatedOn>=#fromDate and CreatedOn<= #toDate and Status='Active')))
)a where a.num=1
Use this linq query after getting result from sql. p.ID is be your desire distinct column name
List<Person> distinctRecords = YourResultList
.GroupBy(p => new { p.ID})
.Select(g => g.First())
.ToList();

GROUP BY and HAVING in linq

I want to convert this code to linq:
select t1.title, COUNT(*)as num
from t1 INNER join t2 on t2.gId = t1.Id
group by t1.title, t1.cId
having t1.cId = 2
I tried this below code:
from p in db.t1s join r in db.t2s on p.Id equals r.gId
where p.cId == 2
group p by p.title into g
select new{ name = from o in g select o.title, num = g.Count()}
But this doesn't return COUNT correctly.
please guide me how can I solve the problem
thanks
Without sample data its hard to get it right, but try this snippet
from p in db.t1s
join r in db.t2s on p.Id equals r.gId
where p.cId == 2
group p by new {p.title, p.cId} into grouped
select new{ name = grouped.Key.title, num = grouped.Count()}
Also, note that this sql:
select t1.title, COUNT(*)as num
from t1 INNER join t2 on t2.gId = t1.Id
group by t1.title, t1.cId
having t1.cId = 2
Will always return 1 as result of COUNT(*). The reason is that you have filtering t1.cId = 2 and grouping by t1.cId as second parameter.

Resources