Подтвердить что ты не робот

Множественные агрегированные функции 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, но я хотел бы думать, что мне не придется делать это, используя четыре круглых поездки БД.

4b9b3361

Ответ 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.