Cara Bergabung ke baris pertama


773

Saya akan menggunakan contoh konkret, tetapi hipotetis.

Setiap pesanan biasanya hanya memiliki satu item baris :

Pesanan:

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

Item Baris:

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

Tetapi kadang-kadang akan ada pesanan dengan dua item baris:

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

Biasanya ketika menampilkan pesanan kepada pengguna:

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

Saya ingin menunjukkan satu item pada pesanan. Tapi dengan pesanan sesekali ini mengandung dua (atau lebih) item, perintah akan muncul akan digandakan :

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

Yang benar-benar saya inginkan adalah memiliki SQL Server hanya memilih satu , karena akan cukup baik :

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

Jika saya suka bertualang, saya mungkin menunjukkan kepada pengguna, sebuah elipsis untuk menunjukkan bahwa ada lebih dari satu:

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

Jadi pertanyaannya adalah bagaimana caranya

  • menghilangkan baris "duplikat"
  • hanya bergabung ke salah satu baris, untuk menghindari duplikasi

Percobaan pertama

Upaya naif pertama saya adalah hanya bergabung dengan item baris " TOP 1 ":

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

Tapi itu memberi kesalahan:

Kolom atau awalan 'Pesanan' tidak
cocok dengan nama tabel atau nama alias yang
digunakan dalam kueri.

Agaknya karena seleksi dalam tidak melihat tabel luar.


3
Tidak dapat Anda gunakan group by?
Dariush Jafari

2
Saya pikir (dan koreksi saya jika saya salah) group byakan membutuhkan daftar semua kolom lainnya, kecuali kolom yang tidak Anda inginkan duplikatnya. Sumber
Joshua Nelson

Jawaban:


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
         )

Di SQL Server 2005 dan di atas, Anda bisa mengganti INNER JOINdengan 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

Harap perhatikan bahwa TOP 1tanpa ORDER BYitu tidak deterministik: kueri ini akan membuat Anda mendapatkan satu item baris per pesanan, tetapi tidak ditentukan yang mana.

Berbagai permintaan kueri dapat memberi Anda item baris berbeda untuk urutan yang sama, bahkan jika yang mendasarinya tidak berubah.

Jika Anda ingin urutan deterministik, Anda harus menambahkan ORDER BYklausa ke permintaan paling dalam.


3
Luar biasa, itu berhasil; memindahkan TOP 1 dari klausa tabel turunan ke klausa gabungan.
Ian Boyd

107
dan padanan "OUTER JOIN" akan menjadi "OUTER APPLY"
Alex

9
Bagaimana kalau LEFT OUTER BERGABUNG?
Alex Nolasco

8
Bagaimana Anda melakukan ini jika gabungan melalui kunci gabungan / memiliki banyak kolom?
Brett Ryan

7
CROSS APPLYsebaliknya INNER JOINdan OUTER APPLYsebaliknya LEFT JOIN(sama seperti LEFT OUTER JOIN).
hastrb

117

Saya tahu pertanyaan ini dijawab beberapa saat yang lalu, tetapi ketika berhadapan dengan set data besar, query yang disarangkan bisa mahal. Berikut adalah solusi berbeda di mana kueri bersarang hanya akan dijalankan sekali, alih-alih untuk setiap baris yang dikembalikan.

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
Hal ini juga jauh lebih cepat jika Anda 'LineItemId' kolom tidak diindeks dengan benar. Dibandingkan dengan jawaban yang diterima.
APK

3
Tetapi bagaimana Anda melakukan ini jika Max tidak dapat digunakan karena Anda perlu memesan dengan kolom yang berbeda dengan yang ingin Anda kembalikan?
NickG

2
Anda dapat memesan tabel turunan dengan cara apa pun yang Anda inginkan dan menggunakan TOP 1 di SQL Server atau LIMIT 1 di MySQL
stifin

28

Anda bisa melakukannya:

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
  )

Ini membutuhkan indeks (atau kunci utama) LineItems.LineItemIDdan indeks aktif LineItems.OrderIDatau akan lambat.


2
Ini tidak berfungsi jika Pesanan tidak memiliki LineItems. Sub-ekspresi kemudian mengevaluasi LineItems.LineItemID = nulldan menghapus pesanan entitas kiri sepenuhnya dari hasilnya.
leo

6
Itu juga efek dari gabungan batin, jadi ... ya.
Tomalak

1
Solusi yang dapat disesuaikan untuk LEFT OUTER JOIN: stackoverflow.com/a/20576200/510583
leo

3
@leo Ya, tapi OP menggunakan batin bergabung sendiri, jadi saya tidak mengerti keberatan Anda.
Tomalak

27

@Quassnoi jawaban baik, dalam beberapa kasus (terutama jika tabel luar besar), kueri yang lebih efisien mungkin dengan menggunakan fungsi berjendela, seperti ini:

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

Terkadang Anda hanya perlu menguji kueri mana yang memberikan kinerja lebih baik.


3
Ini adalah satu-satunya jawaban yang saya temukan yang benar-benar bergabung dengan "Kiri", artinya tidak menambahkan baris lagi di tabel "Kiri". Anda hanya perlu memasukkan subquery dan menambahkan "di mana RowNum bukan nol"
user890332

1
Setuju ini adalah solusi terbaik. Solusi ini juga tidak mengharuskan Anda untuk memiliki ID unik di tabel tempat Anda bergabung, dan jauh lebih cepat daripada jawaban pilihan teratas. Anda juga dapat menambahkan kriteria untuk baris mana yang Anda inginkan untuk kembali, daripada hanya mengambil baris acak, dengan menggunakan klausa ORDER BY dalam subquery.
Geoff Griswald

Ini solusi yang bagus. Harap dicatat: ketika menggunakan untuk situasi Anda sendiri, berhati-hatilah bagaimana Anda MENGHASILKAN PARTION BY (biasanya Anda memang menginginkan kolom ID di sana) dan ORDER BY (yang dapat dilakukan oleh hampir semua hal, tergantung pada baris mana yang ingin Anda pertahankan, mis. Desc DateCreated desc akan menjadi salah satu pilihan untuk beberapa tabel, tetapi itu akan tergantung pada banyak hal)
JosephDoggie

14

, Pendekatan lain yang menggunakan ekspresi tabel umum:

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

atau, pada akhirnya mungkin Anda ingin menunjukkan semua baris bergabung?

versi yang dipisahkan koma di sini:

  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

Dari SQL Server 2012 dan selanjutnya saya pikir ini akan melakukan trik:

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
Jawaban terbaik jika Anda bertanya kepada saya.
thomas

11

Sub kueri yang terkait adalah sub kueri yang bergantung pada kueri luar. Ini seperti for loop dalam SQL. Sub-kueri akan dijalankan satu kali untuk setiap baris di kueri luar:

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: tidak apa-apa, Quassnoi memiliki jawaban yang lebih baik.

Untuk SQL2K, sesuatu seperti ini:

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

Cara favorit saya untuk menjalankan kueri ini adalah dengan klausa yang tidak ada. Saya percaya ini adalah cara paling efisien untuk menjalankan kueri semacam ini:

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
    )

Tapi saya belum menguji metode ini terhadap metode lain yang disarankan di sini.


2

Mencoba salib, bekerja dengan baik, tetapi membutuhkan waktu sedikit lebih lama. Kolom baris yang disesuaikan memiliki grup maks dan ditambahkan yang menjaga kecepatan dan menjatuhkan catatan tambahan.

Inilah kueri yang disesuaikan:

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

10
Tetapi memiliki max secara terpisah pada dua kolom berarti kuantitas mungkin tidak terkait dengan deskripsi. Jika pesanan adalah 2 Widget dan 10 Gadget, kueri akan mengembalikan 10 Widget.
Brianorca

1

coba ini

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
Silakan pertimbangkan untuk menjelaskan apa yang dilakukan permintaan Anda untuk menyelesaikan masalah OP
Simas Joneliunas
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.