Linq to SQL: левое соединение с агрегатом MAX - PullRequest
2 голосов
/ 24 февраля 2010

Я пытаюсь написать оператор Linq to SQL, который отображает все записи клиентов и только совпадающий максимум (InvoiceId) таблицы счетов-фактур; в основном новейший счет для клиента. Требуется левое объединение, поскольку у клиента могут отсутствовать счета, но он должен быть в наборе результатов.

Две основные таблицы с внешним ключом Customer.CustomerID = Invoice.CustomerId

CREATE TABLE [dbo].[Customer](
    [CusomerId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerName] [int] NOT NULL
    CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
    [CustomerId] ASC
)
) ON [PRIMARY]

CREATE TABLE [dbo].[Invoice](
    [InvoiceId] [int] IDENTITY(1,1) NOT NULL,
    [CustomerId] [int] NOT NULL,
    [InvoiceTotal] [float] NOT NULL
    CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED 
(
    [InvoiceId] ASC
)
) ON [PRIMARY]

SQL желаемого набора результатов выглядит следующим образом:

SELECT *
FROM Customers c
  LEFT JOIN 
    (Invoice i 
      INNER JOIN (SELECT CustomerId, MAX(InvoiceId) as InvId FROM Invoice GROUP BY CustomerId) as InvList 
      ON i.InvoiceNo = InvList.InvoiceNo) ON c.CustomerId = i.CustomerId

Из того, что я обнаружил, я не думаю, что это можно сделать одним утверждением; что продукт MAX (InvoiceId) необходимо сначала создать и использовать в основном операторе. Поскольку я не могу заставить его работать, возможно, я тоже ошибаюсь.

Ответы [ 2 ]

3 голосов
/ 24 февраля 2010

Вы можете написать этот конкретный запрос в LINQ следующим образом - хотя это приведет к коррелированному подзапросу:

var query = 
    from c in ctx.Customer
    select new
    {
        Customer = c,
        LatestInvoice = ctx.Invoice
            .Where(i => i.CustomerId == c.CustomerId)
            .OrderByDescending(i => i.InvoiceId)
            .FirstOrDefault();
    };

Если вы хотите сделать это по-другому, синтаксис LINQ менее читабелен, но вы можете немного разделить запрос благодаря отложенному выполнению:

var latestInvoicesPerCustomerQuery = 
    from inv in ctx.Invoice
    group inv by inv.CustomerId into g
    select new { CustomerId = g.Key, InvoiceId = g.Max(inv => inv.InvoiceId) };

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in latestInvoicesPerCustomer 
         on customer.CustomerId equals linv.CustomerId
         into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

Первый запрос (latestInvoicesPerCustomerQuery) не будет выполнен, пока вы не выполните перечисление по нему или по второму запросу, который ссылается на первый. Что касается времени выполнения, то конечный запрос представляет собой одно дерево выражений, так что вы можете думать о первом запросе как о поглощенном во втором.

Если вы действительно хотите все это в одном запросе, вы можете сделать это тоже:

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in (
            from inv in ctx.Invoice
            group inv by inv.CustomerId into g
            select new 
            { 
                CustomerId = g.Key, 
                InvoiceId = g.Max(inv => inv.InvoiceId) 
            }
        ) 
        on customer.CustomerId equals linv.CustomerId
        into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

Любой вариант customersAndLatestInvoicesQuery должен примерно переводиться в SQL, который вы перечислите в своем посте.

0 голосов
/ 25 февраля 2010

Мне не удалось заставить пример Бена М работать, но я смог обработать его следующим образом:

var latestInvoicesPerCustomerQuery = 
    from inv in ctx.Invoice
    group inv by inv.CustomerId into g
    join invj in ctx.Invoice on g.Max(inv => inv.InvoiceId) equals invj.InvoiceId
    select invj;

var customersAndLatestInvoicesQuery = 
    from customer in ctx.Customer
    join linv in latestInvoicesPerCustomer 
         on customer.CustomerId equals linv.CustomerId
         into latestInvoiceJoin
    from latestInvoice in latestInvoiceJoin.DefaultIfEmpty() // left join
    select new
    {
        Customer = customer,
        LatestInvoice = invoice
    };

В первом утверждении я снова включил таблицу счетов в результат и нарочно не использовал select new.

//If I had done:
select new {LatestInvoice=invj}
// then I would have included the name LatestInvoice in the second statement:
join linv in latestInvoicesPerCustomer 
             on customer.CustomerId equals linv.LatestInvoice.CustomerId
             into latestInvoiceJoin
// Not desirable to me, and it seems it may be troublesome when used.

Это на самом деле немного упрощает второе выражение, только делая простое левое соединение с объектом первого оператора. Теперь я получаю набор результатов всех клиентов и последние счета-фактуры для клиентов, у которых они есть.

Я не уверен, почему решение Бен М не работает, но я получаю продукт левого соединения только после удаления следующей строки:

join invoice in ctx.Invoice 
         on latestInvoice.InvoiceId equals invoice.InvoiceId

С этой линией продукт является внутренним соединением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...