2010-02-24 4 views
2

J'essaie d'écrire une instruction Linq to SQL qui affiche tous les enregistrements clients et uniquement le max correspondant (InvoiceId) de la table de facture; essentiellement la plus récente facture pour le client. La jointure à gauche est requise car un client peut ne pas avoir de factures mais doit être dans le jeu de résultats.Linq to SQL: Left Joindre à l'agrégat MAX

Deux tables de base avec une clé étrangère de Client.IDClient = 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] 

Le SQL du jeu de résultat souhaité est la suivante:

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 

D'après ce que j'ai découvert, Je ne pense pas que cela puisse être fait dans une seule déclaration; que le produit MAX (InvoiceId) doit être créé en premier et utilisé dans l'instruction principale. Puisque je ne peux pas le faire fonctionner, peut-être que j'ai tort à ce sujet aussi.

Répondre

3

Vous pouvez écrire cette requête particulière dans LINQ comme suit - bien que cela se traduira par une sous-requête corrélative:

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

Si vous voulez le faire dans l'autre sens, la syntaxe LINQ est moins lisible, mais vous pouvez diviser la requête un peu grâce à l'exécution différée:

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

la première requête (latestInvoicesPerCustomerQuery) n'exécute pas jusqu'à ce que vous énumérez dessus, ou sur la deuxième requête, qui fait référence à la première. En ce qui concerne l'exécution, la requête finale est un arbre d'expression - ainsi vous pouvez penser que la première requête a été absorbée dans la seconde.

Si vous voulez vraiment tout dans une requête, vous pouvez le faire aussi:

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

une ou l'autre variante du customersAndLatestInvoicesQuery devrait se traduire à peu près dans le SQL que vous énumérez dans votre post.

+0

Cela ne semble pas fonctionner comme prévu. Même problème que je courais en moi-même. J'utilise votre exemple de bloc de code du milieu. La jointure à gauche fonctionne bien jusqu'à la ligne "joindre la facture dans ctx.Invoice sur latestInvoice.InvoiceId vaut facture.InvoiceId" Lorsque cette ligne est ajoutée dans je n'obtiens plus tous les clients, seulement les clients avec les factures max correspondantes. – Brettski

+0

Ben Je vous remercie sincèrement pour votre réponse; cela m'a permis de découvrir la solution dont j'avais besoin pour mon projet. Je voudrais également ajouter que n'importe qui là-bas apprend Linq, pour utiliser LinqPad pour comprendre vos questions. Je ne sais pas où je serais sans cet outil, j'ai même payé pour l'option itellisense. :) – Brettski

+0

Hmm. J'ai copié le motif à partir d'une requête existante qui fonctionne, mais je dois avoir manqué une différence clé - je ne sais toujours pas pourquoi cela n'a pas fonctionné pour vous. Quoi qu'il en soit, je suis heureux de pouvoir vous aider à le résoudre! –

0

je n'étais pas en mesure d'obtenir l'exemple de Ben M à travailler, mais j'ai pu travailler dans les éléments suivants:

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

Dans la première déclaration, je rejoins la table de la facture de nouveau dans le résultat et volontairement n'a pas utilisé sélectionner nouveau. Ceci simplifie en fait la deuxième instruction d'un bit qui n'a qu'à faire une simple jointure à gauche vers le premier objet d'instructions. Je reçois maintenant un ensemble de résultats de tous les clients et les dernières factures pour les clients qui les ont.

Je ne sais pas pourquoi la solution de Ben M ne fonctionne pas, mais je ne reçois une jointure gauche produit lorsque la ligne suivante est supprimée:

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

Avec cette ligne inclus le produit est une jointure interne.