İlk satıra katılma


773

Somut ama varsayımsal bir örnek kullanacağım.

Her Sipariş'in normalde yalnızca bir satır öğesi vardır :

Emirler:

OrderGUID   OrderNumber
=========   ============
{FFB2...}   STL-7442-1      
{3EC6...}   MPT-9931-8A

Satır öğesi:

LineItemGUID   Order ID Quantity   Description
============   ======== ========   =================================
{098FBE3...}   1        7          prefabulated amulite
{1609B09...}   2        32         spurving bearing

Ancak bazen iki satır öğesi içeren bir sipariş olacaktır:

LineItemID   Order ID    Quantity   Description
==========   ========    ========   =================================
{A58A1...}   6,784,329   5          pentametric fan
{0E9BC...}   6,784,329   5          differential girdlespring 

Normalde siparişleri kullanıcıya gösterirken:

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID

Siparişte tek bir öğeyi göstermek istiyorum. Ama bu arada sipariş öğeleri ikisini içeren (veya daha fazla) ile, emir olurdu görünür olmak çoğaltılamaz :

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         spurving bearing
KSG-0619-81   5          panametric fan
KSG-0619-81   5          differential girdlespring

Gerçekten istediğim, yeterince iyi olacağı için SQL Server sadece birini seçmektir :

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan

Maceracı olursam, kullanıcıya gösterebilirim, birden fazla olduğunu gösteren bir üç nokta:

OrderNumber   Quantity   Description
===========   ========   ====================
STL-7442-1    7          prefabulated amulite
MPT-9931-8A   32         differential girdlespring
KSG-0619-81   5          panametric fan, ...

Soru şu; ya

  • "yinelenen" satırları yok et
  • çoğaltmayı önlemek için yalnızca satırlardan birine katılın

İlk girişim

İlk naif girişimim sadece " TOP 1 " satır öğelerine katılmaktı :

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    INNER JOIN (
       SELECT TOP 1 LineItems.Quantity, LineItems.Description
       FROM LineItems
       WHERE LineItems.OrderID = Orders.OrderID) LineItems2
    ON 1=1

Ama bu hatayı veriyor:

'Siparişler' sütunu veya öneki , sorguda kullanılan
tablo adıyla veya takma adla eşleşmiyor
.

Muhtemelen iç seçim dış tabloyu görmediği için.


3
Kullanamaz mısın group by?
Dariush Jafari

2
Bence (ve eğer yanılıyorsam beni düzeltin) group bykopyaları istemediğiniz hariç, diğer tüm sütunları listelemek gerekir. Kaynak
Joshua Nelson

Yanıtlar:


1213
SELECT   Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM     Orders
JOIN     LineItems
ON       LineItems.LineItemGUID =
         (
         SELECT  TOP 1 LineItemGUID 
         FROM    LineItems
         WHERE   OrderID = Orders.OrderID
         )

Yukarıdaki SQL Server 2005 ile de, sadece yerini alabilir INNER JOINile CROSS APPLY:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
CROSS APPLY
        (
        SELECT  TOP 1 LineItems.Quantity, LineItems.Description
        FROM    LineItems
        WHERE   LineItems.OrderID = Orders.OrderID
        ) LineItems2

Unutmayın ki TOP 1olmadan ORDER BYdeterministik değildir: bu sorgu size sipariş başına bir satır öğesi alır, ancak hangisinin olacağını tanımlamaz.

Sorgunun birden çok çağrılması, temel alınan değişmemiş olsa bile aynı sipariş için size farklı satır öğeleri verebilir.

Deterministik düzen istiyorsanız ORDER BY, en içteki sorguya bir cümle eklemelisiniz .


3
Mükemmel, işe yarıyor; TOP 1 türetilmiş tablo yan tümcesinden yan tümce tümce taşımak.
Ian Boyd

107
ve "OUTER JOIN" eşdeğeri "OUTER APPY" olacaktır
Alex

9
SOL DIŞ KATILMAYA ne dersiniz?
Alex Nolasco

8
Birleştirme bir bileşik anahtar aracılığıylaysa / birden çok sütunu varsa bunu nasıl yaparsınız?
Brett Ryan

7
CROSS APPLYyerine INNER JOINve OUTER APPLYyerine LEFT JOIN(ile aynı LEFT OUTER JOIN).
18'de

117

Bu sorunun bir süre önce yanıtlandığını biliyorum, ancak büyük veri kümeleriyle uğraşırken iç içe sorgular maliyetli olabilir. Burada, iç içe sorgunun döndürülen her satır yerine yalnızca bir kez çalıştırılacağı farklı bir çözüm bulunmaktadır.

SELECT 
  Orders.OrderNumber,
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders
  INNER JOIN (
    SELECT
      Orders.OrderNumber,
      Max(LineItem.LineItemID) AS LineItemID
    FROM
      Orders INNER JOIN LineItems
      ON Orders.OrderNumber = LineItems.OrderNumber
    GROUP BY Orders.OrderNumber
  ) AS Items ON Orders.OrderNumber = Items.OrderNumber
  INNER JOIN LineItems 
  ON Items.LineItemID = LineItems.LineItemID

2
Bu çok daha hızlı da sizin 'LineItemId' sütunu düzgün endeksli değildir. Kabul edilen cevapla karşılaştırıldığında.
GER

3
Ancak, Max'i kullanmak istemiyorsanız, geri dönmek istediğinizden farklı bir sütunla sipariş vermeniz gerektiğinde bunu nasıl yaparsınız?
NickG

2
türetilmiş tabloyu istediğiniz şekilde sipariş edebilir ve SQL Server'da TOP 1 veya
MySQL'de

28

Şunları yapabilirsiniz:

SELECT 
  Orders.OrderNumber, 
  LineItems.Quantity, 
  LineItems.Description
FROM 
  Orders INNER JOIN LineItems 
  ON Orders.OrderID = LineItems.OrderID
WHERE
  LineItems.LineItemID = (
    SELECT MIN(LineItemID) 
    FROM   LineItems
    WHERE  OrderID = Orders.OrderID
  )

Bunun için bir dizin (veya birincil anahtar) LineItems.LineItemIDve bir dizin açık LineItems.OrderIDolması gerekir, aksi takdirde yavaş olur.


2
Siparişlerde LineItems yoksa bu çalışmaz. Daha sonra alt ifade LineItems.LineItemID = null, sol varlık siparişlerini değerlendirir ve sonuçtan tamamen kaldırır.
leo

6
Bu aynı zamanda iç birleşimin de etkisi, yani ... evet.
Tomalak

1
SOL DIŞ BİRLEŞTİRME için uyarlanabilen çözüm: stackoverflow.com/a/20576200/510583
leo

3
@leo Evet, ama OP kendi içinde bir iç birleşim kullandı, bu yüzden itirazınızı anlamıyorum.
Tomalak

27

@Quassnoi yanıtı iyidir, bazı durumlarda (özellikle dış tablo büyükse), pencereli işlevlerin kullanılmasıyla daha verimli bir sorgu olabilir, örneğin:

SELECT  Orders.OrderNumber, LineItems2.Quantity, LineItems2.Description
FROM    Orders
LEFT JOIN 
        (
        SELECT  LineItems.Quantity, LineItems.Description, OrderId, ROW_NUMBER()
                OVER (PARTITION BY OrderId ORDER BY (SELECT NULL)) AS RowNum
        FROM    LineItems

        ) LineItems2 ON LineItems2.OrderId = Orders.OrderID And RowNum = 1

Bazen hangi sorgunun daha iyi performans verdiğini test etmeniz gerekir .


3
Bu, gerçek bir "Sol" birleştirme yapan bulduğum tek cevap, yani daha fazla satır eklemediğinden "Sol" tablodadır. Sadece alt sorgu koymak ve "nerede RowNum boş değil" eklemek gerekir
user890332

1
Bunun en iyi çözüm olduğunu kabul etti. Bu çözüm ayrıca, katıldığınız tabloda benzersiz bir kimliğiniz olmasını gerektirmez ve en çok oy alan yanıttan çok daha hızlıdır. Ayrıca, alt sorguda ORDER BY deyimini kullanarak, rastgele bir satır almak yerine, hangi satırı döndürmeyi tercih ettiğinize ilişkin ölçütler ekleyebilirsiniz.
Geoff Griswald

Bu iyi bir çözüm. Lütfen dikkat: kendi durumunuz için kullanırken, PARTION BY (genellikle orada bazı kimlik sütunları olmasını istiyorsunuz) ve ORDER BY (hangi satırı tutmak istediğinize bağlı olarak, çoğu şey tarafından yapılabilir, örn. DateCreated desc bazı tablolar için bir seçenek olurdu, ama bir çok şeye bağlı olacaktır)
JosephDoggie

14

, Ortak tablo ifadesini kullanan başka bir yaklaşım:

with firstOnly as (
    select Orders.OrderNumber, LineItems.Quantity, LineItems.Description, ROW_NUMBER() over (partiton by Orders.OrderID order by Orders.OrderID) lp
    FROM Orders
        join LineItems on Orders.OrderID = LineItems.OrderID
) select *
  from firstOnly
  where lp = 1

ya da sonunda belki bütün satırları birleştirmek istersiniz?

virgülle ayrılmış sürüm burada:

  select *
  from Orders o
    cross apply (
        select CAST((select l.Description + ','
        from LineItems l
        where l.OrderID = s.OrderID
        for xml path('')) as nvarchar(max)) l
    ) lines

13

SQL Server 2012 ve sonrası için bu hile yapacağını düşünüyorum:

SELECT DISTINCT
    o.OrderNumber ,
    FIRST_VALUE(li.Quantity) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Quantity ,
    FIRST_VALUE(li.Description) OVER ( PARTITION BY o.OrderNumber ORDER BY li.Description ) AS Description
FROM    Orders AS o
    INNER JOIN LineItems AS li ON o.OrderID = li.OrderID

2
Bana sorarsan en iyi cevap.
thomas

11

İlişkili alt sorgular, dış sorguya bağlı olan alt sorgulardır. SQL'deki for döngüsü gibi. Alt sorgu, dış sorgudaki her satır için bir kez çalışır:

select * from users join widgets on widgets.id = (
    select id from widgets
    where widgets.user_id = users.id
    order by created_at desc
    limit 1
)

5

EDIT: boşver, Quassnoi daha iyi bir cevabı var.

SQL2K için şöyle bir şey:

SELECT 
  Orders.OrderNumber
, LineItems.Quantity
, LineItems.Description
FROM (  
  SELECT 
    Orders.OrderID
  , Orders.OrderNumber
  , FirstLineItemID = (
      SELECT TOP 1 LineItemID
      FROM LineItems
      WHERE LineItems.OrderID = Orders.OrderID
      ORDER BY LineItemID -- or whatever else
      )
  FROM Orders
  ) Orders
JOIN LineItems 
  ON LineItems.OrderID = Orders.OrderID 
 AND LineItems.LineItemID = Orders.FirstLineItemID

4

Bu sorguyu çalıştırmak için en sevdiğim yolu, var olmayan bir cümle ile. Bu tür bir sorgu çalıştırmak için en verimli yolu olduğuna inanıyorum:

select o.OrderNumber,
       li.Quantity,
       li.Description
from Orders as o
inner join LineItems as li
on li.OrderID = o.OrderID
where not exists (
    select 1
    from LineItems as li_later
    where li_later.OrderID = o.OrderID
    and li_later.LineItemGUID > li.LineItemGUID
    )

Ancak bu yöntemi burada önerilen diğer yöntemlere karşı test etmedim.


2

Haçı denedim, güzel çalışıyor, ancak biraz daha uzun sürüyor. Ayarlanmış satır sütunları, hızı koruyan ve ekstra kaydı bırakan maksimum ve eklenen gruba sahip olacak şekilde ayarlandı.

Ayarlanan sorgu:

SELECT Orders.OrderNumber, max(LineItems.Quantity), max(LineItems.Description)
FROM Orders
    INNER JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID
Group by Orders.OrderNumber

10
Ancak, iki sütunda ayrı ayrı maks olması, miktarın açıklama ile ilişkili olmayabileceği anlamına gelir. Sipariş 2 Widget ve 10 Gadget olsaydı, sorgu 10 Widget döndürür.
Brianorca

1

bunu dene

SELECT
   Orders.OrderNumber,
   LineItems.Quantity, 
   LineItems.Description
FROM Orders
   INNER JOIN (
      SELECT
         Orders.OrderNumber,
         Max(LineItem.LineItemID) AS LineItemID
       FROM Orders 
          INNER JOIN LineItems
          ON Orders.OrderNumber = LineItems.OrderNumber
       GROUP BY Orders.OrderNumber
   ) AS Items ON Orders.OrderNumber = Items.OrderNumber
   INNER JOIN LineItems 
   ON Items.LineItemID = LineItems.LineItemID

2
Lütfen
sorunuzu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.