Cách tham gia hàng đầu tiên


773

Tôi sẽ sử dụng một ví dụ cụ thể, nhưng giả thuyết.

Mỗi đơn hàng thường chỉ có một mục hàng :

Đơn đặt hàng:

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

Mục hàng:

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

Nhưng đôi khi sẽ có một đơn hàng với hai chi tiết đơn hàng:

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

Thông thường khi hiển thị các đơn đặt hàng cho người dùng:

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

Tôi muốn hiển thị các mục duy nhất trên đơn đặt hàng. Nhưng với trật tự thỉnh thoảng này chứa hai (hoặc nhiều hơn) các mặt hàng, các đơn đặt hàng sẽ xuất hiện được nhân đôi :

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

Điều tôi thực sự muốn là có SQL Server chỉ cần chọn một , vì nó sẽ đủ tốt :

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

Nếu tôi thích phiêu lưu, tôi có thể chỉ cho người dùng, dấu chấm lửng để chỉ ra rằng có nhiều hơn một:

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

Vì vậy, câu hỏi là làm thế nào để

  • loại bỏ các hàng "trùng lặp"
  • chỉ tham gia vào một trong các hàng để tránh trùng lặp

Lần thử đầu tiên

Nỗ lực ngây thơ đầu tiên của tôi là chỉ tham gia vào các chi tiết đơn hàng " 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

Nhưng điều đó mang lại lỗi:

Cột hoặc tiền tố 'Đơn hàng' không
khớp với tên bảng hoặc tên bí danh
được sử dụng trong truy vấn.

Có lẽ bởi vì lựa chọn bên trong không nhìn thấy bảng bên ngoài.


3
Bạn không thể sử dụng group by?
Dariush Jafari

2
Tôi nghĩ (và sửa tôi nếu tôi sai) group bysẽ yêu cầu liệt kê tất cả các cột khác, ngoại trừ cột mà bạn không muốn trùng lặp. Nguồn
Joshua Nelson

Câu trả lời:


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
         )

Trong SQL Server 2005 trở lên, bạn chỉ có thể thay thế INNER JOINbằng 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

Xin lưu ý rằng TOP 1không có tính ORDER BYxác định: truy vấn này bạn sẽ nhận được cho bạn một mục hàng trên mỗi đơn hàng, nhưng nó không được xác định đó sẽ là mục nào.

Nhiều yêu cầu của truy vấn có thể cung cấp cho bạn các chi tiết đơn hàng khác nhau cho cùng một đơn hàng, ngay cả khi cơ sở không thay đổi.

Nếu bạn muốn thứ tự xác định, bạn nên thêm một ORDER BYmệnh đề vào truy vấn trong cùng.


3
Tuyệt vời, hoạt động; di chuyển TOP 1 từ mệnh đề bảng dẫn xuất sang mệnh đề nối.
Ian Boyd

107
và tương đương "OUTER THAM GIA" sẽ là "ỨNG DỤNG NGOÀI"
Alex

9
Làm thế nào về LEFT OUTER THAM GIA?
Alex Nolasco

8
Làm thế nào để bạn làm điều này nếu tham gia thông qua một khóa ghép / có nhiều cột?
Brett Ryan

7
CROSS APPLYthay vào đó INNER JOINOUTER APPLYthay vào đó LEFT JOIN(giống như LEFT OUTER JOIN).
hastrb

117

Tôi biết câu hỏi này đã được trả lời cách đây một thời gian, nhưng khi xử lý các tập dữ liệu lớn, các truy vấn lồng nhau có thể tốn kém. Đây là một giải pháp khác nhau trong đó truy vấn lồng nhau sẽ chỉ được chạy một lần, thay vì cho mỗi hàng được trả về.

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
Điều này cũng nhanh hơn nhiều nếu cột 'LineItemId' của bạn không được lập chỉ mục đúng. So với câu trả lời được chấp nhận.
GER

3
Nhưng bạn sẽ làm điều này như thế nào nếu Max không thể sử dụng được vì bạn cần đặt hàng theo một cột khác với cột bạn muốn trả về?
NickG

2
bạn có thể đặt hàng bảng dẫn xuất theo bất kỳ cách nào bạn muốn và sử dụng TOP 1 trong SQL Server hoặc
GIỚI

28

Bạn có thể làm:

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
  )

Điều này đòi hỏi một chỉ mục (hoặc khóa chính) trên LineItems.LineItemIDvà một chỉ mục trên LineItems.OrderIDhoặc nó sẽ chậm.


2
Điều này không hoạt động nếu một Đơn đặt hàng không có LineItems. Biểu thức con sau đó đánh giá LineItems.LineItemID = nullvà loại bỏ hoàn toàn các đơn đặt hàng thực thể bên trái khỏi kết quả.
leo

6
Đó cũng là hiệu ứng của sự tham gia bên trong, vì vậy ... vâng.
Tomalak

1
Giải pháp có thể được điều chỉnh cho LEFT OUTER THAM GIA: stackoverflow.com/a/20576200/510583
leo

3
@leo Có, nhưng OP đã tự mình tham gia nội bộ, vì vậy tôi không hiểu sự phản đối của bạn.
Tomalak

27

Câu trả lời @Quassnoi là tốt, trong một số trường hợp (đặc biệt nếu bảng bên ngoài lớn), một truy vấn hiệu quả hơn có thể bằng cách sử dụng các hàm cửa sổ, như thế này:

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

Đôi khi bạn chỉ cần kiểm tra truy vấn nào cho hiệu suất tốt hơn.


3
Đây là câu trả lời duy nhất tôi tìm thấy khi tham gia "Trái" thực sự, có nghĩa là nó không thêm bất kỳ dòng nào nữa trong bảng "Trái". Bạn chỉ cần đặt truy vấn con và thêm "trong đó RowNum không null"
user890332

1
Đồng ý đây là giải pháp tốt nhất. Giải pháp này cũng không yêu cầu bạn phải có một ID duy nhất trong bảng mà bạn tham gia và nhanh hơn nhiều so với câu trả lời được bình chọn hàng đầu. Bạn cũng có thể thêm tiêu chí cho hàng nào bạn muốn trả về, thay vì chỉ lấy một hàng ngẫu nhiên, bằng cách sử dụng mệnh đề ORDER BY trong truy vấn con.
Geoff Griswald

Đây là một giải pháp tốt. Xin lưu ý: khi sử dụng cho trường hợp của riêng bạn, hãy cẩn thận cách bạn THAM GIA B (NG (thông thường bạn có thể muốn có một số cột ID ở đó) và ĐẶT HÀNG B (NG (có thể được thực hiện bởi hầu hết mọi thứ, tùy thuộc vào hàng bạn muốn giữ, ví dụ: DateCreated desc sẽ là một lựa chọn cho một số bảng, nhưng nó sẽ phụ thuộc vào rất nhiều thứ)
JosephDoggie

14

, Một aproach khác sử dụng biểu thức bảng chung:

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

hoặc, cuối cùng, có lẽ bạn muốn hiển thị tất cả các hàng đã tham gia?

phiên bản được phân tách bằng dấu phẩy tại đây:

  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

Từ SQL Server 2012 trở đi, tôi nghĩ rằng điều này sẽ thực hiện thủ thuật:

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
Câu trả lời tốt nhất nếu bạn hỏi tôi.
thomas

11

Các truy vấn phụ tương quan là các truy vấn phụ phụ thuộc vào truy vấn bên ngoài. Nó giống như một vòng lặp for trong SQL. Truy vấn phụ sẽ chạy một lần cho mỗi hàng trong truy vấn bên ngoài:

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: nevermind, Quassnoi có một câu trả lời tốt hơn.

Đối với SQL2K, một cái gì đó như thế này:

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

Cách ưa thích của tôi để chạy truy vấn này là với một mệnh đề không tồn tại. Tôi tin rằng đây là cách hiệu quả nhất để chạy loại truy vấn này:

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
    )

Nhưng tôi chưa thử nghiệm phương pháp này so với các phương pháp khác được đề xuất ở đây.


2

Đã thử thập tự giá, hoạt động độc đáo, nhưng mất nhiều thời gian hơn. Các cột dòng được điều chỉnh để có nhóm tối đa và được thêm giữ tốc độ và giảm bản ghi bổ sung.

Đây là truy vấn được điều chỉnh:

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

10
Nhưng có tối đa riêng biệt trên hai cột có nghĩa là số lượng có thể không liên quan đến mô tả. Nếu đơn hàng là 2 Tiện ích và 10 Tiện ích, truy vấn sẽ trả về 10 Tiện ích.
Brianorca

1

thử cái này

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
Vui lòng xem xét giải thích những gì truy vấn của bạn để giải quyết vấn đề của OP
Simas Joneliunas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.