Множественные агрегированные функции SQL в одном запросе Linq-to-Entities
В SQL вы можете выразить несколько агрегатов в одном запросе базы данных следующим образом:
SELECT MIN(p.x), MAX(p.x), MIN(p.y), MAX(p.y)
FROM Places p JOIN Logs l on p.Id = l.PlaceId
WHERE l.OwnerId = @OwnerId
Можно ли использовать эквивалентную вещь, используя Linq-to-Entities? Я нашел аналогичный вопрос, который предполагает, что это невозможно для Linq-to-SQL, но я хотел бы думать, что мне не придется делать это, используя четыре круглых поездки БД.
Ответы
Ответ 1
Предположим, что у вас есть следующий оператор SQL:
select sum(A), max(B), avg(C) from TBL group by D
Попробуйте это на С#:
from t in table
group t by D
into g
select new {
s = g.Sum(x => x.A),
m = g.Max(x => x.B),
a = g.Average(x => x.C)
}
- или в VB: -
from t in TBL
group t by key = D
into g = group
select s = g.Sum(function(x) x.A),
m = g.Max(function(x) x.B),
a = g.Average(function(x) x.C)
Очевидным, что в VB было бы:
aggregate t in TBL into s = Sum(t.A), m = Max(t.B), a = Average(t.C)
хотя он даст те же результаты, он имеет более высокую стоимость, поскольку он выдает несколько операторов выбора SQL, по одному для каждой агрегатной функции, т.е. будет выполняться с несколькими проходами. Первый синтаксис дает единый (довольно сложный, но эффективный) оператор SQL, который делает один проход против базы данных.
PS. Если у вас нет ключа для группировки (т.е. Вам нужна одна строка в качестве результата, охватывающая весь набор данных), используйте константу, как в:
from t in TBL
group t by key = 0
into g = group
select s = g.Sum(function(x) x.A),
m = g.Max(function(x) x.B),
a = g.Average(function(x) x.C)
Ответ 2
У меня нет вашей БД, но это (с использованием "стандартной" модели EDMX Northwind.mdb - без изменений после запуска мастера новой модели) выполняется как один запрос в LINQPad:
var one = from c in Customers
where c.PostalCode == "12209"
select new
{
Id = c.Orders.Max(o => o.OrderID),
Country = c.Orders.Min(o => o.ShipCountry)
};
one.Dump();
Обновлено, за ваш комментарий:
var two = from c in Customers
where c.PostalCode == "12209"
from o in c.Orders
group o by o.Customer.PostalCode into g
select new
{
PostalCode = g.Key,
Id = g.Max(o => o.OrderID),
Country = g.Min(o => o.ShipCountry)
};
two.Dump();
Ответ 3
К сожалению, ответ кажется нет.
Если кто-то может доказать обратное, я буду рад предоставить им принятый ответ.
EDIT
Я нашел способ сделать это, используя Entity SQL. Я не уверен, что это самый лучший способ, но, как кажется, это единственный способ, который по умолчанию может быть самым большим:)
var cmdText = "SELECT MIN(p.x), MAX(p.x), MIN(p.y), MAX(p.y) " +
"FROM Places AS p JOIN Logs AS l ON p.Id = l.PlaceId " +
"WHERE l.OwnerId==123";
var results = CreateQuery<DbDataRecord>(cmdText)
var row = results.First();
var minX = (double)row[0];
var maxX = (double)row[1];
var minY = (double)row[2];
var maxY = (double)row[3];
Вышеупомянутый не совсем тот код, с которым я работаю. Для более простого случая без объединения здесь создается SQL, показывающий, что сделано только одно прохождение в БД:
SELECT
1 AS [C1],
[GroupBy1].[A1] AS [C2],
[GroupBy1].[A2] AS [C3],
[GroupBy1].[A3] AS [C4],
[GroupBy1].[A4] AS [C5]
FROM ( SELECT
MIN([Extent1].[X1]) AS [A1],
MAX([Extent1].[X1]) AS [A2],
MAX([Extent1].[Y1]) AS [A3],
MIN([Extent1].[Y1]) AS [A4]
FROM [dbo].[Edges] AS [Extent1]
WHERE [Extent1].[PlaceId] = 123
) AS [GroupBy1]
Если кто-то найдет более элегантное решение этой проблемы, я дам им принятый ответ.
РЕДАКТИРОВАТЬ 2
Благодаря Costas, который нашел отличное решение этой проблемы, в котором используется чистый Linq.