2009-04-21 11 views
26

私は2つのテーブル、つまりアカウントテーブルとユーザーテーブルを持っています。各アカウントは複数のユーザーを持つことができます。これらの2つのテーブルに対して単一のクエリ/ジョインを実行するシナリオがありますが、すべてのアカウントデータ(Account。*)と、の最初ののユーザーデータ(具体的にはその名前)のみが必要です。集計SQL各グループから最初のもののみを取得する機能

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

このクエリの取得方法についてのご意見はありますか?もちろん、アカウントのデカルト積を取得することは容易であるユーザーを×:

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

しかし、どのように私は約だけ自分User.IDの順序に基づいて製品の最初のユーザーを取得してしまったのでしょうか?

+0

SQL Serverがオフに悪化しているアカウントの最初のユーザ「を返すFirstUser」で参加します。私はそれがなぜSQL Serverに存在しないのか説得力のある説明を聞いたことがありません。時には、彼らがどんな順序であっても(それらがすべて特定のグループの列に同じ値を持っているかどうか)問題ではない場合もあります。どちらの方法でも、FIRST()が使用されます。 – micahhoover

答えて

22

はむしろグループよりも、ここでは、迅速かつ汚いものをこれを行う方法は多数あります

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 
+0

興味深いことに、first.row_num = 0のようなことができるかどうか分かりません – Matt

+1

ここでRank()を使用してRow_Number()から減算し、0を探しました。私はRow_Number()を使い(Account_IDで区切って)、Row_Num = 1でフィルタリングしました。結果は同じです(技術的にはもっと速いかもしれません)。 @AaronLSの例を見てください:http://stackoverflow.com/a/9220232/555798 – MikeTeeVee

+2

@MikeTeeVee合意しました。それはより良い解決策であり、私はそれが今日この問題を解決していたと思います。 –

1
SELECT (SELECT TOP 1 Name 
     FROM User 
     WHERE Account_ID = a.AccountID 
     ORDER BY UserID) [Name], 
     a.* 
FROM Account a 
+0

しかし、このアプローチでは、すべてのアカウント行に対して別のselect文が実行されます。あなたのアカウントが1000の場合、クエリは1001個の独立したselect文を実行します) –

+0

小さなテーブルでは大したことではありませんが、あなたの解決策はより良いでしょう:) –

0

...このようにそれについて行きます。

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

"First"を定義します。あなたが最初に考えることは、通常、クラスタ化されたインデックスの順序と関係がありますが、それに依存してはいけないという偶然のことです(あなたはそれを破る例を考案できます)。

MAX()またはMIN()は使用しないでください。誘惑しながら、名と姓が別々のフィールドにあるシナリオを考えてみましょう。あなたは別のレコードから名前を得るかもしれません。

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

+0

彼は最初にユーザIDに基づいて言いました –

2

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

+1

ようこそ!複数の質問に定型文/逐語答をコピーして貼り付けるときには注意が必要です。これらはコミュニティによって「スパム」と表示される傾向があります。あなたがこれをやっているなら、それは通常質問が重複していることを意味します。 – Kev

6
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などがあることを意味します。

したがって、各グループから最初のものを取得するためにランキング= 1でフィルタリングします。

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

9

私の答えは少し遅れますが、それは他人を助けるかもしれません。そこSQL Serverのファースト()とLast()を達成するための方法であり、ここにある:最初に()と最終マックス()()のための

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

利用分()。 DATE_FIELDは、それが最初のレコードか最後のレコードかを決定する日付でなければなりません。 DESIRED_FIELDは、最初または最後の値が必要なフィールドです。何それがないことである:

  1. は(そのフィールドのMIN/MAX値を取得します
  2. は、その文字列
  3. にDESIRED_FIELDを追加(23文字)の文字列の先頭にISO形式の日付を追加します。それが日付で開始するので、あなたは、最初または最後のレコードを取得します)
  4. あなたが行くここで最初の23文字(日付部分)

を削除するには、文字列をconcatenedスタッフ!

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

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

EDIT 2:私の元の意図は最後の(または最初の)NON NULL行を返すことでした。私は最後の行や最初の行を返す方法を聞かれました。 DESIRED_FIELDにISNULLを追加するだけです。 2つの文字列を+演算子で連結すると、そのうちの1つがNULLの場合、結果はNULLになります。したがって、以下を使用してください:

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

トップ1のネストされた選択肢の代わりにmin()を使用すると、かなりのパフォーマンスヒットが発生しました。その理由は、データセット全体を反復して、トップ1が最初のものだけを取るという理由が考えられます。 –

3

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

+0

コメントありがとう!私はあまりにも数週間前に気づいた、私の答えを更新しました。これはまた、あなたのdatetimeが.000ミリ秒で、それらが取り除かれ、最初の4文字が失われたときにも起こります。私はミリ秒単位でカットするために、フォーマットを126から20に変更しました。 –

2

OUTER APPLYを使用できます(documentationを参照)。

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 
1

私はすべてのメソッド、simpelest及びこれを達成するための最速の方法をベンチマークしてきたがCROSSだけでINNER JOINのような作品を適用

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

適用外/クロスを使用していると行の両方をフェッチテーブルは関連していますが、OUTER APPLYはLEFT OUTER JOINのように機能し、左のテーブルからすべての行を取り出します(アカウントはここにあります)

+0

このクエリは一貫性のない結果をもたらす可能性があります。 SORT BYのないSELECT TOP 1は、SqlServer Engineに依存する問合せのいずれかを返すことができます。したがって、このような結果は「ランダムな結果」をもたらす可能性があります。 – qub1n

0

(少しオフトピックですが)例外集計を表示するために集約クエリを実行することが多い結果に顧客がいる理由を知るためには、 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

作成し、副選択、それぞれが、それは何のFIRSTを持っていないので、

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 
関連する問題