2010-12-13 19 views
22

En SQL se puede expresar múltiples agregados en una sola consulta de base de datos de la siguiente manera:funciones agregadas de SQL múltiples en una sola LINQ a Entidades consultar

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 

¿Es posible hacer algo equivalente utilizando LINQ to -Entidades? Encontré un similar question que sugiere que no es posible para Linq-to-SQL pero me gustaría pensar que no tendré que hacer lo anterior utilizando cuatro viajes de ida y vuelta al DB.

+0

+1 para la pregunta interesante, no lo he investigado, pero sería extraño si no funcionara. ¿No puedes usar la sintaxis 'let'? –

+0

@Thomas, hasta donde puedo ver, no es posible asignar un conjunto usando 'let', sino más bien un único valor para un elemento en el conjunto. He estado usando EF por unos meses y me sorprendí al golpear esta barrera hoy. ¡Realmente no creo que sea posible! –

+0

La respuesta eliminada de @ hunter funciona. ¿Lo intentaste? Además, 'let' sí funciona con conjuntos. ¿Has probado eso? Tengo un código de producción que hace ambas cosas. –

Respuesta

24

Suponga que tiene la siguiente instrucción SQL:

select sum(A), max(B), avg(C) from TBL group by D 

Prueba esto en C# :

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) 
    } 

- o en 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) 

Lo obvio, que en VB sería:

aggregate t in TBL into s = Sum(t.A), m = Max(t.B), a = Average(t.C) 

a pesar de que le dará los mismos resultados, tiene una mayor costo ya que emite múltiples sentencias SQL select, una para cada función agregada, es decir, se ejecutará en varias pasadas.La primera sintaxis proporciona una declaración SQL única (bastante compleja, pero eficiente) que hace una sola pasada contra la base de datos.

PS. Si usted no tiene una clave por el cual agrupar por (es decir, que necesitan una sola fila como resultado, que abarca todo el conjunto de datos), utilice una constante como en:

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) 
+0

Esto parece funcionar muy bien. Gracias por resolver esto con una solución mucho más elegante y sólida que con Entity SQL. Encontré que usar 'group t por 1 en g' funcionó bien para mí. ¡Gracias de nuevo! –

+0

La solución propuesta es usar agrupación, aunque en la pregunta no se mencionó la agrupación. . ¿Qué sucede si no hay una agrupación involucrada, solo quiero ejecutar este sql: seleccione sum (A), max (B), avg (C) de TBL? – Goran

+0

@Goran Eso está cubierto por el PS - usted agrupa por una constante. (Lo cual se siente un poco raro, pero parece ser la respuesta canónica. Lo he visto hecho en consultas SQL sin procesar también). – starwed

0

Lamentablemente la respuesta parece ser no.

Si alguien puede demostrar lo contrario, estaré encantado de darles la respuesta aceptada.


EDITAR

he encontrado una manera de hacer esto utilizando Entity SQL. No estoy seguro de que es la manera más , pero como parece ser la única manera, entonces puede ser el más grande de forma predeterminada :)

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]; 

lo anterior no es exactamente el código que estoy trabajando con. Para un caso más simple, sin una combinación, aquí está el SQL generado, lo que demuestra que sólo un viaje a la base de datos se hace:

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] 

Si alguien encuentra una solución más elegante a este problema, voy a otorgarles la respuesta aceptada .


EDITAR 2

Gracias a Costas que encontró a great solution to this problem que utiliza puro LINQ.

2

no tengo su base de datos, pero esto (utilizando un modelo EDMX "default" de NEPTUNO.MDB - no hay cambios después de ejecutar el asistente de nuevo modelo) se ejecuta como una consulta en 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(); 
Actualizado

, por su comentario:

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(); 
+0

Hola @Craig. No tengo Northwind en mi máquina, pero creo que esta consulta generará una entrada para cada cliente que tenga ese código postal. Lo que me gustaría hacer es el equivalente a determinar el OrderId máximo para cualquier cliente que tenga ese código postal. ¿Es eso posible? Yo simplifiqué mi pregunta original quizás demasiado. Lo actualizaré para que coincida más con mi esquema real. –

+0

OK, ver la respuesta actualizada. Esto probablemente podría simplificarse, pero elegí seguir el patrón de la primera consulta. –

+0

en caso de que esté interesado, encontré una solución que funciona, aunque no es la más bonita. –

Cuestiones relacionadas