2010-12-13 11 views
22

In SQL è possibile esprimere molteplici aggregati in una singola query di database in questo modo:Molteplici funzioni di aggregazione SQL in un unico LINQ to Entities interrogare

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 

E 'possibile fare una cosa equivalente utilizzando LINQ-to -Entità? Ho trovato un similar question che suggerisce che non è possibile per Linq-to-SQL, ma mi piacerebbe pensare che non dovrò fare quanto sopra utilizzando quattro round trip al DB.

+0

+1 per domanda interessante, non l'ho guardato io stesso ma sarebbe strano se non funzionasse. Non puoi usare la sintassi 'let'? –

+0

@Thomas, per quanto posso vedere non è possibile assegnare un set usando 'let', ma piuttosto un singolo valore per un oggetto nel set. Ho usato EF per alcuni mesi e sono rimasto sorpreso di colpire questa barriera oggi. Davvero non penso sia possibile! –

+0

la risposta cancellata di @ hunter funziona. Hai provato? Inoltre, 'let' funziona davvero con gli insiemi. Hai provato? Ho un codice di produzione che fa entrambi. –

risposta

24

Supponiamo di avere la seguente istruzione SQL:

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

Prova questo in 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 in 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) 

L'ovvio, che in VB sarebbe:

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

se darà gli stessi risultati, ha una costo più elevato in quanto emette più istruzioni di selezione SQL, una per ogni funzione di aggregazione, ovvero verrà eseguita in più passaggi.La prima sintassi, fornisce una singola istruzione SQL (abbastanza complessa, ma efficiente) che esegue un singolo passaggio sul database.

PS. Se non si dispone di una chiave con la quale eseguire il raggruppamento (cioè è necessario una singola riga come risultato, che copre l'intero insieme di dati), utilizzare una costante come in:

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

Sembra funzionare molto bene. Grazie per aver risolto questo problema con una soluzione molto più elegante e robusta rispetto all'utilizzo di Entity SQL. Ho trovato che usare 'group t per 1 in g' ha funzionato bene per me. Grazie ancora! –

+0

La soluzione proposta utilizza il raggruppamento, sebbene nella domanda il raggruppamento non sia stato menzionato. . Cosa succede se non ci sono gruppi coinvolti, voglio solo eseguire questo sql: selezionare sum (A), max (B), avg (C) da TBL? – Goran

+0

@Goran Questo è coperto dal PS - si raggruppa per una costante. (Che sembra un po 'strano, ma sembra essere la risposta canonica. L'ho visto anche nelle query SQL raw). – starwed

0

Sfortunatamente la risposta sembra essere no.

Se qualcuno può dimostrare il contrario, sarò felice di concedere loro la risposta accettata.


EDIT

Ho trovato un modo per farlo utilizzando Entity SQL. Non sono sicuro che sia il modo più , ma come sembra essere l'unico modo allora potrebbe essere il più grande di default :)

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]; 
È possibile che questo non è esattamente il codice Sono

lavorando con. Per un caso più semplice, senza un join, ecco l'SQL generato, dimostrando che solo un viaggio al DB è fatto:

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] 

Se qualcuno trova una soluzione più elegante a questo problema, lo ammetto loro la risposta accettata .


EDIT 2

Grazie a Costas che ha trovato a great solution to this problem che utilizza pura LINQ.

2

Non ho vostro DB, ma questo (utilizzando un "default" modello EDMX di Northwind.mdb - nessuna modifica dopo l'esecuzione della nuova procedura guidata modello) viene eseguito come una query in 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(); 

aggiornato, secondo il vostro commento:

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

Ciao @Craig. Non ho Northwind sulla mia macchina, ma da bulbo oculare penso che questa query produrrà una voce per ogni cliente che ha quel codice postale. Quello che mi piacerebbe fare è l'equivalente di determinare il massimo OrderId per ogni cliente che ha quel codice postale. È possibile? Ho semplificato la mia domanda originale forse troppo. Lo aggiornerò per renderlo più simile al mio schema attuale. –

+0

OK, vedere la risposta aggiornata. Questo potrebbe probabilmente essere semplificato, ma ho scelto di seguire lo schema della prima query. –

+0

nel caso siate interessati, ho trovato una soluzione che funziona, anche se non è la più bella. –

Problemi correlati