38

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

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

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

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

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

4

12 に答える 12

26

グループ化するのではなく、次のように実行します...

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
于 2009-04-21T16:19:22.693 に答える
12

私の答えは少し遅れていることは知っていますが、それは他の人を助けるかもしれません。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は日付をミリ秒なしの文字列として返すため、DESIRED_FIELDから最初の4文字が削除されます。フォーマットを「20」(ミリ秒なし)に変更しただけで、すべてうまく機能します。唯一の欠点は、同じ秒数で作成された2つのフィールドがある場合、並べ替えが煩雑になる可能性があることです。この場合、形式を「126」に戻すことができます。

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

編集2:私の当初の意図は、最後の(または最初の)NONNULL行を返すことでした。nullかどうかに関係なく、最後の行または最初の行を返す方法を尋ねられました。ISNULLをDESIRED_FIELDに追加するだけです。+演算子を使用して2つの文字列を連結する場合、そのうちの1つがNULLの場合、結果はNULLになります。したがって、次を使用します。

Stuff(Max(Convert(Varchar, DATE_FIELD, 20) + IsNull(Convert(Varchar, DESIRED_FIELD), '')), 1, 19, '')
于 2012-04-11T17:37:15.693 に答える
9
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

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

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

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

于 2012-02-09T22:56:48.320 に答える
6

私はすべての方法をベンチマークしました。これを達成するための最も簡単で最速の方法は、アウター/クロスアプライを使用することです。

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

CROSSAPPLYはINNERJOINと同じように機能し、両方のテーブルが関連する行をフェッチします。一方、OUTERAPPLYはLEFTOUTER JOINのように機能し、左側のテーブルからすべての行をフェッチします(ここのアカウント)

于 2015-10-29T12:48:52.697 に答える
4

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

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
于 2015-09-28T21:23:00.080 に答える
3
SELECT (SELECT TOP 1 Name 
        FROM User 
        WHERE Account_ID = a.AccountID 
        ORDER BY UserID) [Name],
       a.*
FROM Account a
于 2009-04-21T16:19:15.190 に答える
3

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

于 2012-07-05T14:05:03.210 に答える
2

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

于 2011-12-02T10:05:58.660 に答える
1

「最初」を定義します。あなたが最初に考えるのは、通常はクラスター化されたインデックスの順序に関係しているが、信頼されるべきではない偶然の一致です(それを破る例を考案することができます)。

MAX()またはMIN()を使用しないのは正しいことです。誘惑しながら、名と名前が別々のフィールドにあるシナリオを考えてみましょう。別のレコードから名前を取得する場合があります。

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

于 2009-04-21T16:20:54.093 に答える
0

これを行うにはいくつかの方法がありますが、ここではすばやく汚い方法を使用します。

Select (SELECT TOP 1 U.Name FROM Users U WHERE U.Account_ID = A.ID) AS "Name,
    A.*
FROM Account A
于 2009-04-21T16:19:29.200 に答える
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
于 2017-01-09T22:38:24.093 に答える
0

各アカウントの最初のユーザーを返す副選択「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
于 2017-04-21T14:37:18.177 に答える