첫 번째 행에 참여하는 방법


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

그러나 오류가 발생합니다.

열 또는 접두사 'Orders'가 쿼리에 사용 된
테이블 이름 또는 별명과 일치 하지 않습니다
.

내부 선택에 외부 테이블이 표시되지 않기 때문일 수 있습니다.


3
사용할 수 없습니까 group by?
Dariush Jafari

2
group by중복을 원하지 않는 열을 제외하고 다른 모든 열을 나열해야 한다고 생각 합니다. 출처
Joshua Nelson

답변:


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 서버 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
LEFT OUTER JOIN은 어떻습니까?
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
원하는 방식으로 파생 테이블을 주문하고 SQL Server에서 TOP 1을 사용하거나 MySQL에서 LIMIT 1을 사용할 수 있습니다
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.LineItemID = null은 왼쪽 엔티티 순서 를 평가 하고 결과에서 완전히 제거합니다.
leo

6
그것은 또한 내부 조인의 효과이기도합니다.
Tomalak

1
LEFT OUTER JOIN에 적용 가능한 솔루션 : 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
이것은 실제 "왼쪽"조인을 수행하는 유일한 대답입니다. 즉, "왼쪽"테이블에 더 이상 줄을 추가하지 않습니다. 당신은 하위 쿼리에 넣고 "ROWNUM가 null가 아닌 경우"추가 할 필요가
user890332

1
이것이 최선의 해결책이라고 동의했습니다. 또한이 솔루션을 사용하면 가입하려는 테이블에 고유 한 ID가 없어도 가장 인기있는 답변보다 훨씬 빠릅니다. 하위 쿼리에서 ORDER BY 절을 사용하여 임의의 행을 취하는 것이 아니라 반환하려는 행에 대한 기준을 추가 할 수도 있습니다.
Geoff Griswald

이것은 좋은 해결책입니다. 참고 : 자신의 상황에 사용할 때 PARTION BY (보통 ID 열을 원할 것입니다) 및 ORDER BY (보관하려는 행에 따라 대부분의 작업을 수행 할 수 있음)에 매우주의하십시오. DateCreated desc는 일부 테이블에는 하나의 선택이지만 많은 것들에 달려 있습니다.)
JosephDoggie

14

공통 테이블 표현식을 사용하는 또 다른 방법 :

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

상관 된 하위 쿼리는 외부 쿼리에 의존하는 하위 쿼리입니다. SQL의 for 루프와 같습니다. 하위 쿼리는 외부 쿼리의 각 행마다 한 번씩 실행됩니다.

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

편집 : 신경 쓰지 마라.

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.