2010-12-13 8 views
22

W SQL można wyrazić wiele agregatów w jednym zapytaniu bazy danych takie jak to:Wiele funkcji SQL kruszywa w jednym LINQ-podmioty kwerendy

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 

jest to możliwe do zrobienia równoważny rzeczą przy użyciu LINQ-do -Entities? Znalazłem similar question, który sugeruje, że nie jest możliwe dla Linq-SQL, ale chciałbym myśleć, że nie będę musiał tego robić, korzystając z czterech rundek podróży do DB.

+0

+1 ciekawych pytanie, Sam nie zaglądałem w to, ale byłoby dziwnie, gdyby to nie zadziałało. Nie możesz użyć składni "let"? –

+0

@Thomas, o ile widzę, nie można przypisać zestawu za pomocą 'let', ale raczej pojedynczej wartości dla elementu w zestawie. Korzystam z EF od kilku miesięcy i byłem zaskoczony, że uderzyłem dziś w tę barierę. Naprawdę nie sądzę, że to możliwe! –

+0

@ Hunter's deleted answer Działa. Spróbowałeś? Również 'let' rzeczywiście działa z zestawami. Czy próbowałeś tego? Mam kod produkcyjny, który wykonuje oba. –

Odpowiedz

24

Załóżmy, że mamy następującą instrukcję SQL:

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

spróbować tego w 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) 
    } 

- albo w 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) 

Oczywiste, który byłby w VB:

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

mimo to daje takie same wyniki, to ma wyższy koszt, ponieważ wystawia wiele instrukcji SQL Select, po jednym dla każdej funkcji agregującej, tzn. będzie wykonywany w wielu przejściach.Pierwsza składnia, daje pojedynczą (dość złożoną, ale wydajną) instrukcję SQL, która wykonuje pojedyncze przejście do bazy danych.

PS. Jeśli nie masz klucza, według której grupy przez (czyli trzeba pojedynczy wiersz w wyniku, obejmujący cały zestaw danych), należy użyć stałej, jak w:

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

To wydaje się działać naprawdę dobrze. Dzięki za rozwiązanie tego z dużo bardziej eleganckim i solidnym rozwiązaniem niż przy użyciu Entity SQL. Zauważyłem, że użycie 'group t przez 1 na g' działa dobrze dla mnie. Dzięki jeszcze raz! –

+0

Proponowane rozwiązanie korzysta z grupowania, jednak w pytaniu nie podano żadnej grupy. . Co jeśli nie ma w tym celu grupowania, po prostu chcę wykonać ten sql: select sum (A), max (B), avg (C) z TBL? – Goran

+0

@Goran To jest objęte PS - ty grupa przez stałą. (Co wydaje się trochę dziwne, ale wydaje się być odpowiedzią kanoniczną. Widziałem to również w surowych zapytaniach SQL.) – starwed

0

Niestety odpowiedź wydaje się być nr.

Jeśli ktoś może udowodnić, że jest inaczej, z przyjemnością udzielę im zaakceptowanej odpowiedzi.


EDIT

znalazłem sposób to zrobić przy użyciu Entity SQL. Nie jestem pewien, że to droga największym, ale wydaje się być tylko sposób to może być największy domyślnie :)

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

Powyższe nie jest dokładnie kod Jestem Praca z. Dla uproszczenia sprawy bez sprzężenia, tutaj jest generowane SQL, pokazując, że tylko jeden wyjazd do DB jest wykonana:

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] 

Jeśli ktoś znajdzie bardziej eleganckie rozwiązanie tego problemu, będę udzielać im zaakceptowane odpowiedź .


EDIT 2

Dzięki Costas który znalazł a great solution to this problem który używa czystego Linq.

2

nie mam swój DB, ale to (za pomocą „default” EDMX model Northwind.mdb - bez zmian po uruchomieniu kreatora nowego modelu) działa jako jednego zapytania w 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(); 

Zaktualizowany, za swój komentarz:

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

Hi @Craig. Nie mam Northwind na mojej maszynie, ale przez gałkę oczną myślę, że to zapytanie stworzy jeden wpis dla każdego klienta mającego ten kod pocztowy. To, co chciałbym zrobić, to odpowiednik określenia maksymalnego OrderId dla każdego klienta mającego ten kod pocztowy. Czy to jest możliwe? Uproszczę moje pierwotne pytanie może za dużo. Zaktualizuję go, aby był bardziej zgodny z moim rzeczywistym schematem. –

+0

OK, patrz zaktualizowana odpowiedź. Prawdopodobnie można to uprościć, ale zdecydowałem się podążać za wzorcem pierwszego zapytania. –

+0

Jeśli jesteś zainteresowany, znalazłem rozwiązanie, które działa, choć nie jest najładniejsze. –

Powiązane problemy