web-dev-qa-db-ja.com

SQL Server:最初の行に参加する方法

具体的な、しかし仮説的な例を使用します。

注文 には通常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

注文に単一の商品を表示したいのですが。しかし、2つ(またはそれ以上)の項目を含むこの時折の注文では、注文は 出現 be 複製

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

問題はどちらか

  • 「重複」行を排除
  • 重複を避けるため、いずれかの行にのみ結合する

最初の試み

私の最初の素朴な試みは、 " 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'はそうではありません。
テーブル名または別名と一致
クエリで使用されています。

おそらく内側のselectが外側のテーブルを見ていないからです。

665
Ian Boyd
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句を追加する必要があります。

1055
Quassnoi

私はこの質問にしばらく前に回答したことを知っていますが、大規模なデータセットを扱う場合、入れ子になったクエリはコストがかかる可能性があります。これは、返される各行に対してではなく、ネストされたクエリが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
99
Justin Fisher

あなたがすることができます:

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のインデックスが必要ですが、遅くなります。

26
Tomalak

@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

時々 テストする必要がある どのクエリがより良いパフォーマンスを与えるか。

17
BornToCode

共通の表式を使用したもう1つの方法:

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
11
avb

相関サブクエリは、外側のクエリに依存するサブクエリです。これは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
)
7
Abdullah Yousuf

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
6
P. Olesen

編集:決して気にしないで、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
5
Peter Radocchia

このクエリを実行する私のお気に入りの方法は、存在しない句を使用することです。私はこれがこの種のクエリを実行するための最も効率的な方法だと思います。

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
    )

しかし、ここで提案した他の方法に対してこの方法をテストしたことはありません。

3
Anand

十字架を試してみました、うまく動作しますが、少し時間がかかります。速度を維持し、追加レコードを削除する最大および追加グループを持つように行の列を調整しました。

これが調整されたクエリです。

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

私はLEFT JOINとGROUP BY Orders.OrderNumberを使って同様の問題を解決します。このようにしない理由はありますか?

SELECT Orders.OrderNumber, LineItems.Quantity, LineItems.Description
FROM Orders
    LEFT JOIN LineItems 
    ON Orders.OrderID = LineItems.OrderID
GROUP BY Orders.OrderNumber

私はあなた自身の質問の答えと共にあなたの答えの質問に答えます。

Orders             LineItems
+-------------+    +---------+----------+---------------+
| OrderNumber |    | OrderID | Quantity | Description   |
+-------------+    +---------+----------+---------------+
| 22586       |    | 22586   | 17       | Trunion       |
+-------------+    | 22586   | 3        | Girdle Spring |
                   +---------+----------+---------------+

2つをOrderNumberで結合すると、次のようになります。

OrderNumber  Quantity  Description
-----------  --------  -------------
22586        17        Trunion
22586        3         Girdle Spring

2 row(s) affected

1行だけを返したい場合は、次のようにします。

OrderNumber  Quantity  Description
-----------  --------  -------------
22586        17        Trunion

1 row(s) affected

これが、OrderNumberごとに1行しか返さないGROUP BY Orders.OrderNumberを使用する理由です。

2
smerlung