web-dev-qa-db-ja.com

各グループの最初のSQL関数のみを取得するSQL関数の集計

AccountテーブルとUsersテーブルの2つのテーブルがあります。各アカウントは複数のユーザーを持つことができます。これらの2つのテーブルに対して単一のクエリ/結合を実行したいシナリオがありますが、すべてのアカウントデータ(Account。*)とfirstユーザーデータのセット(特にそれらの名前)のみが必要です)。

集約されたグループで「最小」または「最大」を実行する代わりに、「最初」を実行する必要がありました。しかし、明らかに、TSQLには「最初の」集約関数はありません。

このクエリを取得する方法について何か提案はありますか?明らかに、アカウントxユーザーのデカルト積は簡単に取得できます。

 SELECT User.Name, Account.* FROM Account, User
 WHERE Account.ID = User.Account_ID

しかし、User.IDの順序に基づいて、製品から最初のユーザーのみを取得するにはどうすればよいでしょうか?

29
Matt

グループ化するのではなく、このようにしてください...

select
    *

from account a

join (
    select 
        account_id, 
        row_number() over (order by account_id, id) - 
            rank() over (order by account_id) as row_num from user
     ) first on first.account_id = a.id and first.row_num = 0
25
Adam Robinson

私の答えは少し遅いのですが、それが他の人の役に立つかもしれません。 SQL ServerでFirst()およびLast()を達成する方法があり、ここにそれがあります:

Stuff(Min(Convert(Varchar, DATE_FIELD, 126) + Convert(Varchar, DESIRED_FIELD)), 1, 23, '')

First()にはMin()を、Last()にはMax()を使用します。 DATE_FIELDは、それが最初のレコードか最後のレコードかを決定する日付でなければなりません。 DESIRED_FIELDは、最初または最後の値が必要なフィールドです。それは何ですか:

  1. 文字列の先頭にISO形式の日付を追加します(23文字)
  2. その文字列にDESIRED_FIELDを追加します
  3. そのフィールドのMIN/MAX値を取得します(日付で始まるため、最初または最後のレコードを取得します)
  4. 文字列を連結して最初の23文字を削除するもの(日付部分)

どうぞ!

編集:最初の式で問題が発生しました:DATE_FIELDのミリ秒が.000の場合、SQL Serverは日付をミリ秒なしの文字列として返し、最初の4文字をDESIRED_FIELDから削除します。私は単にフォーマットを「20」(ミリ秒なし)に変更しただけで、すべてうまくいきました。唯一の欠点は、同じ秒に作成された2つのフィールドがある場合、並べ替えが乱雑になる可能性があることです。この場合、フォーマットを「126」に戻すことができます。

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + Convert(Varchar, DESIRED_FIELD)), 1, 19, '')

編集2:私の本来の目的は、最後(または最初)のNON NULL行を返すことでした。最後の行または最初の行を返す方法を尋ねられました。それがnullかどうかは関係ありません。 ISNULLをDESIRED_FIELDに追加するだけです。 +演算子を使用して2つの文字列を連結するとき、それらの1つがNULLの場合、結果はNULLになります。したがって、以下を使用します。

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
9
Dominic Goulet
Select *
From Accounts a
Left Join (
    Select u.*, 
    row_number() over (Partition By u.AccountKey Order By u.UserKey) as Ranking
    From Users u
  ) as UsersRanked
  on UsersRanked.AccountKey = a.AccountKey and UsersRanked.Ranking = 1

これは、Partition By句を使用して簡略化できます。上記では、アカウントに3人のユーザーがいる場合、サブクエリはユーザーに1、2、3の番号を付け、別のAccountKeyの場合は、ナンバリングをリセットします。つまり、一意のAccountKeyごとに、常に1、場合によっては2、3、4などが存在します。

したがって、Ranking = 1でフィルタリングして、各グループの最初のグループを取得します。

これにより、アカウントごとに1行が表示され、そのアカウントに少なくとも1人のユーザーがいる場合は、最も低いキーを持つユーザーが表示されます(左結合を使用しているため、いいえの場合でも常にアカウントリストが表示されますユーザーが存在します)。置換Order By u.UserKey最初のユーザーをアルファベット順またはその他の基準で選択する場合は、別のフィールドを使用します。

7
AaronLS

Dominic GouletからのSTUFF応答は滑らかです。ただし、DATE_FIELDが(DATETIMEではなく)SMALLDATETIMEの場合、ISO 8601の長さは23ではなく19になります(SMALLDATETIMEにはミリ秒がないため)。したがって、STUFFパラメータを適切に調整しないと、STUFF関数からの戻り値が正しくありません(最初の4文字が欠落しています)。

3
mweaver

OUTER APPLYを使用できます。 ドキュメント を参照してください。

SELECT User1.Name, Account.* FROM Account
OUTER APPLY 
    (SELECT  TOP 1 Name 
    FROM [User]
    WHERE Account.ID = [User].Account_ID
    ORDER BY Name ASC) User1
2
Tomas Kubes

私はすべての方法をベンチマークしました、これを達成するための最も簡単で最速の方法は、外側/クロス適用を使用することです

SELECT u.Name, Account.* FROM Account
OUTER APPLY (SELECT TOP 1 * FROM User WHERE Account.ID = Account_ID ) as u

CROSS APPLYはINNER JOINと同じように機能し、両方のテーブルが関連している行をフェッチしますが、OUTER APPLYはLEFT OUTER JOINと同様に機能し、左側のテーブルからすべての行をフェッチします(ここのアカウント)

2

FirstとLastはSql Server 2005または2008には存在しませんが、Sql Server 2012にはFirst_Value、Last_Value関数があります。 SQL Server 2005の集計FirstとLastを実装しようとしたところ、SQLサーバーが定義された順序で集計の計算を保証するという障害に直面しました。 (属性SqlUserDefinedAggregateAttribute.IsInvariantToOrderプロパティを参照してください。これは実装されていません。)これは、クエリアナライザーが複数のスレッドで集計の計算を実行し、結果を結合しようとするために実行が高速化されますが、順序は保証されません。集約される要素。

2
Christoph K
SELECT (SELECT TOP 1 Name 
        FROM User 
        WHERE Account_ID = a.AccountID 
        ORDER BY UserID) [Name],
       a.*
FROM Account a
1
Jimmie R. Houts

これを行う方法はいくつかありますが、ここでは手早く簡単な方法を紹介します。

Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
    A.*
FROM Account A
0
Mitchel Sellers

「最初」を定義します。最初に考えるのは、通常はクラスター化インデックスの順序に関係する偶然ですが、これに依存すべきではありません(これを壊す例を考案することができます)。

MAX()またはMIN()を使用しないでください。魅力的な間、あなたの名と姓が別々のフィールドにあるシナリオを考えてください。異なるレコードから名前を取得する場合があります。

本当に気にしているように、グループごとに1つの任意のレコードを取得するだけなので、そのレコードのIDフィールドをMINまたはMAXにして、テーブルをそのIDのクエリに結合するだけです。

0
Joel Coehoorn

各アカウントの最初のユーザーを返す副選択「FirstUser」を作成して参加

SELECT User.Name, Account.* 
FROM Account, User, 
 (select min(user.id) id,account_id from User group by user.account_id) as firstUser
WHERE Account.ID = User.Account_ID 
 and User.id = firstUser.id and Account.ID = firstUser.account_id
0
Leon Droog

(トピックから少し外れていますが)集計クエリを実行して例外の概要を一覧表示することがよくあります。そのため、顧客が結果に含まれる理由を知りたいので、MINとMAXを使用して、2つのセミランダムサンプルを表示します。詳細例.

SELECT Customer.Id, COUNT(*) AS ProblemCount
      , MIN(Invoice.Id) AS MinInv, MAX(Invoice.Id) AS MaxInv
FROM Customer
INNER JOIN Invoice on Invoice.CustomerId = Customer.Id
WHERE Invoice.SomethingHasGoneWrong=1
GROUP BY Customer.Id
0
brewmanz