最初の行に参加する方法


773

具体的で架空の例を使用します。

通常、各注文には1つの品目しかありません。

注文:

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

LineItems:

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

ただし、場合によっては、2つの広告申込情報を含む注文が発生することがあります。

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

注文の1つのアイテムを表示します。ただし、この偶発的な注文に2つ(またはそれ以上)のアイテムが含まれていると、注文が重複して表示れます。ます。

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 に1つだけ選択させることです。これで十分です。です。

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, ...

だから問題はどのように

  • 「重複」行を排除する
  • 重複を避けるために、行の1つにのみ結合する

最初の試み

私の最初の素朴な試みは、「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 2017

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 Server 2005とでは、あなただけ置き換えることができINNER JOINCROSS 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は確定的ではないことに。このクエリでは、注文ごとに1つのラインアイテムを取得しますが、どれを使用するかは定義されていません。

クエリを複数回呼び出すと、基になるものが変更されていなくても、同じ注文に対して異なる広告申込情報が表示される可能性があります。

確定的な順序が必要な場合はORDER BY、最も内側のクエリに句を追加する必要があります。


3
すばらしいです。TOP 1を派生テーブル句から結合句に移動しています。
Ian Boyd

107
「OUTER JOIN」は「OUTER APPLY」になります
Alex

9
LEFT OUTER JOINはどうですか?
Alex Nolasco、2012年

8
結合が複合キーによるものであるか、複数の列がある場合、どのようにこれを行いますか?
ブレット・ライアン

7
CROSS APPLY代わりに(代わりにINNER JOINと同じ)。OUTER APPLYLEFT JOINLEFT OUTER JOIN
hastrb 2018年

117

私はこの質問が少し前に回答されたことを知っていますが、大きなデータセットを扱う場合、ネストされたクエリはコストがかかる可能性があります。ネストされたクエリが返される各行ではなく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
場合、これははるかに高速でもあり、あなたの 「の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
OrdersにLineItemがない場合、これは機能しません。次に、部分式が評価されLineItems.LineItemID = null、左エンティティの順序が結果から完全に削除されます。
2015

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句を使用して、ランダムな行を取得するだけでなく、返す行の基準を追加することもできます。
ジェフグリスワルド

これは良い解決策です。注意:自分の状況で使用する場合は、PARTION BY(通常はそこにID列が必要になる可能性があります)とORDER BY(保持する行に応じて、ほとんどすべての場合に実行できます)の方法に十分注意してください。 DateCreated descは一部のテーブルでは1つの選択肢になりますが、それは多くのことに依存します)
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ループのようなものです。サブクエリは、外部クエリの行ごとに1回実行されます。

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つの列に個別に最大値を設定すると、数量が説明に関係しなくなる可能性があります。注文が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.