วิธีเข้าร่วมแถวแรก


773

ฉันจะใช้รูปธรรม แต่เป็นตัวอย่างสมมุติ

โดยปกติแล้วคำสั่งซื้อแต่ละรายการจะมีเพียงหนึ่งรายการ :

คำสั่งซื้อ:

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

รายการโฆษณา:

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

แต่บางครั้งจะมีคำสั่งซื้อที่มีรายการโฆษณาสองรายการ:

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

โดยปกติเมื่อแสดงคำสั่งซื้อให้กับผู้ใช้:

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

ฉันต้องการที่จะแสดงรายการเดียวในการสั่งซื้อ แต่กับการสั่งซื้อเป็นครั้งคราวนี้มีสอง (หรือมากกว่า) รายการสั่งซื้อที่จะปรากฏจะซ้ำกัน :

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

สิ่งที่ฉันต้องการจริงๆคือมี SQL Server เพียงเลือกอย่างใดอย่างหนึ่งเพราะจะดีพอ :

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

ถ้าฉันชอบผจญภัยฉันอาจแสดงผู้ใช้เป็นจุดไข่ปลาเพื่อระบุว่ามีมากกว่าหนึ่ง:

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

ดังนั้นคำถามคือทำอย่างไร

  • กำจัดแถว "ซ้ำ"
  • เข้าร่วมกับหนึ่งในแถวเท่านั้นเพื่อหลีกเลี่ยงการทำซ้ำ

ความพยายามครั้งแรก

ความพยายามที่ไร้เดียงสาครั้งแรกของฉันคือการเข้าร่วมรายการโฆษณา" 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

แต่นั่นทำให้เกิดข้อผิดพลาด:

คอลัมน์หรือคำนำหน้า 'คำสั่งซื้อ' ไม่
ตรงกับชื่อตารางหรือชื่อนามแฝงที่
ใช้ในการสืบค้น

น่าจะเป็นเพราะตัวเลือกด้านในไม่เห็นตารางด้านนอก


3
คุณใช้group byไม่ได้เหรอ
Dariush Jafari

2
ฉันคิดว่า (และแก้ไขให้ฉันถ้าฉันผิด) group byจะต้องมีรายชื่อคอลัมน์อื่น ๆ ทั้งหมดยกเว้นที่คุณไม่ต้องการให้ซ้ำกัน ที่มา
โจชัวเนลสัน

คำตอบ:


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
         )

ใน SQL Server 2005 และสูงกว่าคุณสามารถแทนที่INNER JOINด้วย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

โปรดทราบว่าTOP 1ไม่มีการORDER BYกำหนดไม่ได้: แบบสอบถามนี้คุณจะได้รับหนึ่งรายการโฆษณาต่อคำสั่งซื้อ แต่ไม่ได้กำหนดว่าจะเป็นรายการใด

การร้องขอหลายครั้งของการสืบค้นสามารถให้รายการโฆษณาที่แตกต่างกันสำหรับการสั่งซื้อเดียวกันแม้ว่าการเปลี่ยนแปลงนั้นจะไม่เปลี่ยนแปลง

หากคุณต้องการคำสั่งที่กำหนดได้คุณควรเพิ่มส่วนORDER BYคำสั่งลงในแบบสอบถามด้านในสุด


3
ยอดเยี่ยมใช้งานได้; ย้าย TOP 1 จากส่วนคำสั่งของตารางที่ได้รับไปยังส่วนคำสั่ง
Ian Boyd

107
และ "OUTER JOIN" ที่เทียบเท่าจะเป็น "OUTER APPLY"
Alex

9
วิธีการเกี่ยวกับซ้ายซ้ายเข้าร่วม?
Alex Nolasco

8
คุณจะทำเช่นนี้ได้อย่างไรหากการเข้าร่วมผ่านคีย์ผสม / มีหลายคอลัมน์
Brett Ryan

7
CROSS APPLYแทนINNER JOINและOUTER APPLYแทนLEFT JOIN(เช่นเดียวกับLEFT OUTER JOIN)
hastrb

117

ฉันรู้ว่าคำถามนี้ได้รับคำตอบมาระยะหนึ่งแล้ว แต่เมื่อต้องรับมือกับชุดข้อมูลขนาดใหญ่แบบสอบถามแบบซ้อนอาจมีค่าใช้จ่ายสูง นี่คือโซลูชันที่แตกต่างกันซึ่งแบบสอบถามแบบซ้อนจะรันเพียงครั้งเดียวแทนที่จะส่งคืนแต่ละแถว

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
นอกจากนี้ยังเร็วกว่านี้มากหาก คอลัมน์ 'LineItemId' ของคุณไม่ได้รับการจัดทำดัชนีอย่างถูกต้อง เปรียบเทียบกับคำตอบที่ยอมรับ
GER

3
แต่คุณจะทำอย่างไรถ้า Max ไม่สามารถใช้งานได้ตามที่คุณต้องการโดยคอลัมน์ที่แตกต่างจากคอลัมน์ที่คุณต้องการส่งคืน
NickG

2
คุณสามารถสั่งซื้อตารางที่ได้รับตามวิธีที่คุณต้องการและใช้อันดับ 1 ใน SQL Server หรือ LIMIT 1 ใน MySQL
stifin

28

คุณสามารถทำได้:

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
  )

สิ่งนี้ต้องใช้ดัชนี (หรือคีย์หลัก) LineItems.LineItemIDและดัชนีในLineItems.OrderIDหรือจะช้า


2
สิ่งนี้จะไม่ทำงานหากคำสั่งซื้อไม่มี LineItems จากนั้นนิพจน์ย่อยจะประเมินLineItems.LineItemID = nullและลบคำสั่งเอนทิตีทางด้านซ้ายออกจากผลลัพธ์อย่างสมบูรณ์
leo

6
นั่นเป็นผลของการเข้าร่วมวงในด้วยดังนั้น ...
Tomalak

1
โซลูชันที่สามารถปรับให้เหมาะกับการใช้งานด้านซ้ายด้านนอก: stackoverflow.com/a/20576200/510583
leo

3
@leo ใช่ แต่ OP ใช้การรวมภายในตัวเองดังนั้นฉันไม่เข้าใจคำคัดค้านของคุณ
Tomalak

27

คำตอบ @Quassnoi เป็นสิ่งที่ดีในบางกรณี (โดยเฉพาะถ้าตารางด้านนอกมีขนาดใหญ่) แบบสอบถามที่มีประสิทธิภาพมากขึ้นอาจจะมีการใช้ฟังก์ชั่นแบบหน้าต่างเช่นนี้

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

บางครั้งคุณเพียงแค่ต้องทดสอบว่าแบบสอบถามใดให้ประสิทธิภาพที่ดีกว่า


3
นี่เป็นคำตอบเดียวที่ฉันพบว่าเข้าร่วม "Left" จริงซึ่งหมายความว่าจะไม่เพิ่มบรรทัดใด ๆ เพิ่มเติมจากนั้นจะอยู่ในตาราง "Left" คุณเพียงแค่ต้องใส่แบบสอบถามย่อยและเพิ่ม "ที่ RowNum ไม่เป็นโมฆะ"
user890332

1
ตกลงนี้เป็นทางออกที่ดีที่สุด โซลูชันนี้ไม่ต้องการให้คุณมี ID เฉพาะในตารางที่คุณเข้าร่วมและเร็วกว่าคำตอบที่ได้รับการโหวตสูงสุด นอกจากนี้คุณยังสามารถเพิ่มเกณฑ์สำหรับแถวที่คุณต้องการส่งคืนแทนที่จะใช้การสุ่มแถวโดยใช้คำสั่งย่อย ORDER BY ในแบบสอบถามย่อย
Geoff Griswald

นี่เป็นทางออกที่ดี โปรดทราบ: เมื่อใช้สำหรับสถานการณ์ของคุณเองโปรดระมัดระวังวิธีที่คุณเข้าร่วม (โดยปกติคุณอาจต้องการคอลัมน์ ID บางส่วนที่นั่น) และ ORDER BY (ซึ่งสามารถทำได้ทุกอย่างโดยขึ้นอยู่กับแถวที่คุณต้องการเก็บไว้เช่น DateCreated desc จะเป็นตัวเลือกหนึ่งสำหรับบางตาราง แต่ขึ้นอยู่กับหลายสิ่ง)
JosephDoggie

14

aproach อื่นโดยใช้นิพจน์ตารางทั่วไป:

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

หรือในที่สุดคุณอาจต้องการแสดงแถวทั้งหมดเข้าร่วมหรือไม่

คั่นด้วยเครื่องหมายจุลภาครุ่นที่นี่:

  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 เป็นต้นไปฉันคิดว่านี่จะเป็นการหลอกลวง:

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
คำตอบที่ดีที่สุดถ้าคุณถามฉัน
มัส

11

แบบสอบถามย่อยที่สัมพันธ์กันคือแบบสอบถามย่อยที่ขึ้นอยู่กับแบบสอบถามด้านนอก มันเหมือนกับ for loop ใน SQL แบบสอบถามย่อยจะทำงานหนึ่งครั้งสำหรับแต่ละแถวในแบบสอบถามด้านนอก:

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

แก้ไข: ไม่เป็นไร Quassnoi มีคำตอบที่ดีกว่า

สำหรับ SQL2K สิ่งนี้:

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

วิธีที่ฉันโปรดปรานในการเรียกใช้แบบสอบถามนี้อยู่ที่ส่วนคำสั่งไม่มีอยู่ ฉันเชื่อว่านี่เป็นวิธีที่มีประสิทธิภาพที่สุดในการเรียกใช้คิวรีประเภทนี้:

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
    )

แต่ฉันยังไม่ได้ทดสอบวิธีนี้กับวิธีอื่น ๆ ที่แนะนำที่นี่


2

ลองใช้ไม้กางเขนทำงานได้ดี แต่ใช้เวลานานกว่าเล็กน้อย ปรับคอลัมน์บรรทัดให้มีกลุ่มสูงสุดและกลุ่มที่เพิ่มความเร็วและลดบันทึกพิเศษ

นี่คือข้อความค้นหาที่ปรับแล้ว:

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

10
แต่การมีค่าสูงสุดแยกกันในสองคอลัมน์หมายความว่าปริมาณอาจไม่เกี่ยวข้องกับคำอธิบาย หากคำสั่งคือ 2 วิดเจ็ตและ 10 แกดเจ็ตแบบสอบถามจะส่งคืน 10 วิดเจ็ต
Brianorca

1

ลองนี้

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
โปรดลองอธิบายว่าคำค้นหาของคุณทำอะไรเพื่อแก้ปัญหาของ OP
Simas Joneliunas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.