I am using Xamarin c# linq with sqlite-net pcl (https://github.com/praeclarum/sqlite-net). I found that all my linq groupby cannot be properly translated into SQL. Linq translates the Where clause but not Group By.
In the following example, the fields used in Transaction:
AccountId: int
Amount: double
ICategoryId: int
All these query formats result in SQL without Group By:
select * from "Transaction" where ("DateWithoutTime" <= ?)
var accountBalances1 = _dataService.Connection.Table<Transaction>()
.Where(r => r.DateWithoutTime <= displayDuration.DurationEnd)
.GroupBy(r => r.AccountId)
.Select(g => new
{
Id = g.Key,
Balance = g.Sum(b => b.Amount)
})
.ToList();
var accountBalances2 = _dataService.Connection.Table<Transaction>()
.Where(r => r.DateWithoutTime <= displayDuration.DurationEnd)
.GroupBy(r => r.AccountId,
(key, g) => new
{
Id = key,
Balance = g.Sum(b => b.Amount),
})
.ToList();
var accountBalances3 = (from t in _dataService.Connection.Table<Transaction>()
where t.DateWithoutTime <= displayDuration.DurationEnd
group t by t.AccountId
into g
select new { g.Key, Balance = g.Sum(g => g.Amount) })
.ToList();
To clarify it has nothing to do with double data type, I tried another group by with int data type only:
var maxIECategoryId = _dataService.Connection.Table<Transaction>()
.Where(r => r.DateWithoutTime <= displayDuration.DurationEnd)
.GroupBy(r => r.AccountId,
(key, g) => new
{
Id = key,
IECategoryId = g.Max(b => b.IECategoryId)
})
.ToList();
Similarly, the generated sql does not have group by.
All group by are processed locally. Is there any trick to write a linq group by that can be processed on the server service? or it is a limitation of this implementation of sqlite orm?
Thanks,
Nick
Related
I have a following code:
var sql = db.Accounts.AsNoTracking()
.Join(db.Customers.AsNoTracking(),
d => d.AccountNr, c => c.CustNr,
(d, c) => new {Accounts = d, Customers = c })
.GroupBy(g => g.Accounts.AccountNr)
.Where(w => w.Accounts.Date == null)
.Select(s => new
{
Company = s.Customers.CompName,
TWQ = s.Customers.TWQ,
AccountNr = s.Accounts.AccountNr,
DocDate = s.Accounts.DocumentDate,
Income = s.Customers.Income
})
.OrderBy(o => o.DocDate);
The issue is that c# underlines the whole WHERE part with an alert saying that: Element IGrouping <string,> has no definition of Accounts and extension method of Accounts can not be found
I don't know where the problem lies. I also tried to use GroupBy in model (instead of using it in the code above) but got the some problem:
var model = (from ss in sql // here I refer to sql outcome I got from the code above
.GroupBy(g => g.AccountNr)
.Skip(page * 15 - 15)
.Take(15)
.AsEnumerable()
select new DocumentsModel
{
Company = s.Customers.CompName,
TWQ = s.Customers.TWQ,
AccountNr = s.Accounts.AccountNr,
DocDate = s.Accounts.DocumentDate,
Income = s.Customers.Income
}).ToList();
At first I would like to say that my English isn't that good.
If you execute an GroupBy, you'll get collection of elements where each element represents a projection over a group and its key.
That's why I execute SelectMany afterwards to work with the model in a normal way.
db.Accounts
.AsNoTracking()
.Join
(
inner: db.Customers.AsNoTracking(),
outerKeySelector: x => x.AccountNr,
innerKeySelector: x => x.CustNr,
resultSelector: (Accounts, Customers) => new
{
Accounts, Customers
}
)
.Where
(
predicate: x => x.Accounts.Date == null
)
.GroupBy
(
keySelector: x => x.Accounts.AccountNr
)
.SelectMany
(
selector: x => x
)
.Select
(
selector: x => new
{
Company = x.Customers.CompName,
TWQ = x.Customers.TWQ,
AccountNr = x.Accounts.AccountNr,
DocDate = x.Accounts.DocumentDate,
Income = x.Customers.Income
}
)
The two tables I'm working with here are built like this:
Users Table PurchaseLog Table
ID| LDAP| FNAME| LNAME| ID| UserID| PurchaseID | LOCATION |TIME
I'm trying to build a query that gets me back each user and the number of purchases they have. The model I'm trying to fill:
public class UserPurchaseCount{
public string LDAP {get; set;}
public int Count {get; set;}
}
This SQL query I wrote appears to be returning the correct results,
Select Users.LDAP, count(*) as Purchases
FROM Users
JOIN PurchaseLog ON PurchaseLog.UserId=Users.ID
WHERE Users.FName NOT LIKE '%name%'
AND PurchaseLog.CreatedDate <= 'some end date'
AND Purchase.CreatedDate >= 'some start date'
GROUP BY Users.LDAP
I suck at lambdas, but I like them and want to get a better understanding of them. Here's what I've got so far:
var items = db.PurchaseLogs
.Join(db.Users, usr => usr.UserId, x => x.ID, (usr, x) => new { usr, x })
.GroupBy(y => new { y.x.LDAP})
//.Where(i => i.CreatedDate >= startDate)
//.Where(i => i.CreatedDate <= endDate)
//.Where(i => i.FName.Contains("Guy1", StringComparison.CurrentCultureIgnoreCase) == false)
.Select(g => new
{
g.Key.LDAP,
Count = g.Count()
})
.ToList();
This lambda expression works. When I uncomment the WHERE clauses, the compiler throws up in my face.
Error 6 'System.Linq.IGrouping<AnonymousType#2,AnonymousType#3>' does not contain a definition for 'FName'...
Don't group before apply the conditions:
var items =db.PurchaseLogs
.Join(db.Users, usr => usr.UserId, x => x.ID, (usr, x) => new { usr, x })
.Where(i => i.user.CreatedDate >= startDate)
.Where(i => i.user.CreatedDate <= endDate)
.Where(i => !i.x.FName.Contains("Guy1"))
.GroupBy(y => new { y.x.LDAP})
.Select(g => new
{
g.Key.LDAP,
Count = g.Count()
})
.ToList();
Using the following linq code, how can I add dense_rank to my results? If that's too slow or complicated, how about just the rank window function?
var x = tableQueryable
.Where(where condition)
.GroupBy(cust=> new { fieldOne = cust.fieldOne ?? string.Empty, fieldTwo = cust.fieldTwo ?? string.Empty})
.Where(g=>g.Count()>1)
.ToList()
.SelectMany(g => g.Select(cust => new {
cust.fieldOne
, cust.fieldTwo
, cust.fieldThree
}));
This does a dense_rank(). Change the GroupBy and the Order according to your need :)
Basically, dense_rank is numbering the ordered groups of a query so:
var DenseRanked = data.Where(item => item.Field2 == 1)
//Grouping the data by the wanted key
.GroupBy(item => new { item.Field1, item.Field3, item.Field4 })
.Where(#group => #group.Any())
// Now that I have the groups I decide how to arrange the order of the groups
.OrderBy(#group => #group.Key.Field1 ?? string.Empty)
.ThenBy(#group => #group.Key.Field3 ?? string.Empty)
.ThenBy(#group => #group.Key.Field4 ?? string.Empty)
// Because linq to entities does not support the following select overloads I'll cast it to an IEnumerable - notice that any data that i don't want was already filtered out before
.AsEnumerable()
// Using this overload of the select I have an index input parameter. Because my scope of work is the groups then it is the ranking of the group. The index starts from 0 so I do the ++ first.
.Select((#group , i) => new
{
Items = #group,
Rank = ++i
})
// I'm seeking the individual items and not the groups so I use select many to retrieve them. This overload gives me both the item and the groups - so I can get the Rank field created above
.SelectMany(v => v.Items, (s, i) => new
{
Item = i,
DenseRank = s.Rank
}).ToList();
Another way is as specified by Manoj's answer in this question - But I prefer it less because of the selecting twice from the table.
So if I understand this correctly, the dense rank is the index of the group it would be when the groups are ordered.
var query = db.SomeTable
.GroupBy(x => new { x.Your, x.Key })
.OrderBy(g => g.Key.Your).ThenBy(g => g.Key.Key)
.AsEnumerable()
.Select((g, i) => new { g, i })
.SelectMany(x =>
x.g.Select(y => new
{
y.Your,
y.Columns,
y.And,
y.Key,
DenseRank = x.i,
}
);
var denseRanks = myDb.tblTestReaderCourseGrades
.GroupBy(x => new { x.Grade })
.OrderByDescending(g => g.Key.Grade)
.AsEnumerable()
.Select((g, i) => new { g, i })
.SelectMany(x =>
x.g.Select(y => new
{
y.Serial,
Rank = x.i + 1,
}
));
This is the working query i was using in my management studio.
SELECT TOP 5 productCode, SUM(productSales) AS sales
FROM sellingLog
WHERE (salesYear = '2014')
GROUP BY productCode
ORDER BY sales DESC
I want to convert the query above into lambda, but i can't seems to make it works. the lambda still lacks of order by and select the productCode
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(b => b.Sum(u => u.productSales)).Take(5)
.ToList();
foreach(var v in topProduct)
{
//reading 'productCode' and 'sales' from each row
}
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(g => new { productCode = g.Key, sales = g.Sum(u => u.productSales) })
.OrderByDescending(x => x.productCode)
.Take(5)
.ToList();
You can use the .Key with group by to get productCode
var topProducts = sellingLog
.Where(s => s.salesYear == 2014)
.GroupBy(u => u.productCode)
.Select(b => new {u.Key, b.Sum(u => u.productSales)}).Take(5)
.OrderByDescending(b=>b.Sales)
.ToList();
Following is a T_SQL query for AdventureWorks database:
SELECT Name
FROM Production.Product
WHERE ListPrice >= ANY
(SELECT MAX (ListPrice)
FROM Production.Product
GROUP BY ProductSubcategoryID)
I try writing a LINQ query for this:
var groupMaxPricesquery2 = from product in dc.Products
group product by product.ProductSubcategoryID into productGroup
select productGroup.Max(eachProductInGroup => eachProductInGroup.ListPrice);
var query = from product in dc.Products
where groupMaxPricesquery2.Any(listPrice => listPrice <= product.ListPrice)
select product.Name;
How can I make it more beautiful (i.e. combining those queries together, or a more efficient approach)?
Thank you very much
Give either of these a try:
var query = from product in dc.Products
let groupMaxPricesQuery = dc.Products.GroupBy(p => p.ProductSubcategoryID)
.Select(g => g.Max(item => item.ListPrice))
where groupMaxPricesQuery.Any(listPrice => listPrice <= product.ListPrice)
select product.Name;
// or
var query = dc.Products
.Select(product => new {
Product = product,
GroupedMaxPrices = dc.Products.GroupBy(p => p.ProductSubcategoryID)
.Select(g => g.Max(item => item.ListPrice))
})
.Where(item => item.GroupedMaxPrices.Any(listPrice => listPrice <= item.Product.ListPrice))
.Select(item => item.Product.Name);
Maybe I'm missing something with the grouping, but I don't see why it is necessary.
var maxListPrice = dc.Products.Max(p => p.ListPrice);
var query = dc.Products.Where(p => p.ListPrice >= maxListPrice).Select(n => n.Name);