web-dev-qa-db-fra.com

Comment rejoindre la première ligne d'une sous-requête?

J'ai un tableau des factures et un tableau enfant des données associées liées par clé. En particulier, pour chaque facture, je ne m'intéresse qu'à la première ligne associée de la table enfant. Étant donné que je veux une ligne associée pour chaque clé de facture, comment puis-je accomplir cela?

Select i.[Invoice Number],
       c.[Carrier Name]
From Invoice i
    Left Join Carriers c on i.[InvoiceKey] = c.[InvoiceKey]
Where -- what?

Je suppose sémantiquement parlant que je cherche quelque chose qui ressemble au concept de Top 1 c.CarrierName Group by InvoiceKey (ou quel serait le concept de cela si cela était possible dans T-SQL).

J'ai pensé faire une jointure gauche sur une sous-requête, mais cela ne semble pas très efficace. Quelqu'un at-il des astuces T-SQL pour y parvenir efficacement?

Edit : Désolé les gars, j'ai oublié de mentionner qu'il s'agit de SQL Server 2000. Par conséquent, même si je vais donner des votes positifs pour les réponses actuelles de SQL Server 2005/2008 qui fonctionneront, je ne peux pas les accepter. peur.

23
BenAlabaster

Pourvu que Carriers ait un PRIMARY KEY appelé id:

SELECT  i.[Invoice Number],
        c.[Carrier Name]
FROM    Invoice i
JOIN    Carriers c
ON      c.id = 
        (
        SELECT  TOP 1 ID
        FROM    Carriers ci
        WHERE   ci.InvoiceKey = i.InvoiceKey
        ORDER BY
                id -- or whatever
        )
30
Quassnoi

Voici comment je le ferais, en utilisant une syntaxe légèrement différente de la vôtre (style MySQL), mais je suppose que vous pouvez également l'appliquer à votre solution:

SELECT i.invoiceNumber, c.carrierName
FROM Invoice as i
LEFT JOIN Carriers as c ON (c.id = (SELECT id FROM Carriers WHERE invoiceKey = i.invoiceKey ORDER BY id LIMIT 1))

Cela prendra tous les enregistrements de Invoice et les joindra à un (ou zéro) enregistrement des transporteurs, en particulier à l'enregistrement qui a la même factureKey et seulement le premier.

Tant que vous avez un index sur Carriers.invoiceKey, les performances de cette requête doivent être acceptables.

Sebastian

2
Sebastian Seilund
;with cteRowNumber as (
    select c.InvoiceKey, c.[Carrier Name], ROW_NUMBER() over (partition by c.InvoiceKey order by c.[Carrier Name]) as RowNum
        from Carriers c
)
select i.[Invoice Number],
       rn.[Carrier Name]
    from Invoice i
        left join cteRowNumber rn
            on i.InvoiceKey = rn.InvoiceKey
                and rn.RowNum = 1
1
Joe Stefanelli

Dans de tels cas, j’utilise souvent un dispositif que j’applique ici à votre exemple et que je décris ci-dessous:

SELECT
  i.[Invoice Number],
  c.[Carrier Name]
FROM Invoice i
  INNER JOIN Carriers c ON i.InvoiceKey = c.InvoiceKey
  INNER JOIN (
    SELECT MIN(ID) AS ID
    FROM Carriers
    GROUP BY InvoiceKey
  ) c_top ON c.ID = c_top.ID

Je pense que c'est à peu près ce que Quassnoi a posté, seulement j'essaie d'éviter d'utiliser SELECT TOPs comme ça.

Invoice est associé à Carriers en fonction de leur expression de liaison (InvoiceKey dans ce cas). Maintenant, Carriers peut avoir plusieurs lignes pour la même InvoiceKey, nous devons donc limiter la sortie. Et cela se fait en utilisant une table dérivée.

La table dérivée regroupe les lignes de Carrier en fonction de la même expression utilisée pour lier les deux tables (InvoiceKey).

Et il existe un autre moyen: au lieu de joindre la table dérivée, vous pouvez utiliser IN (subquery) avec le même effet. C'est-à-dire que la requête complète ressemblerait alors à ceci:

SELECT
  i.[Invoice Number],
  c.[Carrier Name]
FROM Invoice i
  INNER JOIN Carriers c ON i.InvoiceKey = c.InvoiceKey
    AND c.ID IN (SELECT MIN(ID) FROM Carriers GROUP BY InvoiceKey)
1
Andriy M

Cela fonctionne pour moi:

select ir.[Invoice Number], c.[Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number], i.InvoiceKey
    from Invoice i) AS ir
left join Carriers c
on ir.InvoiceKey = c.InvoiceKey
where RowNumber = 1
union all
select ir.[Invoice Number], NULL as [Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number]
    from Invoice i) AS ir
where RowNumber > 1

ou

select TOP 1 i.[Invoice Number], c.[Carrier Name]
from Invoice i
left join Carriers c
on i.InvoiceKey = c.InvoiceKey
union all
select ir.[Invoice Number], NULL as [Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number]
    from Invoice i) AS ir
where RowNumber > 1
1
abatishchev
group by carriername having max(invoicenumber)

pour obtenir le premier transporteur pour chaque facture:

group by invoicenumber having max(carriername)
-- substitute the column you want to order by for carrier name to change which is 'first'
0
Chris Shain