ユーザーごとに1つの行を選択しようとしています。どの画像を取得してもかまいません。このクエリはMySQLで機能しますが、SQLServerでは機能しません。
SELECT user.id, (images.path + images.name) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
ユーザーごとに1つの行を選択しようとしています。どの画像を取得してもかまいません。このクエリはMySQLで機能しますが、SQLServerでは機能しません。
SELECT user.id, (images.path + images.name) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
MIN/MAX
これまでに集計を使用して投稿されたソリューションは、ROW_NUMBER
グループごとに1つを選択する前に、一致するすべての行を検査する必要があるため、(データ分散によっては)最も効率的ではない場合があります。
例としてAdventureWorksサンプルデータベースを使用すると、次のクエリはすべて1つTransactionType
を選択しReferenceOrderID
、それぞれのトランザクション履歴テーブルから次のようになりますProductID
。
MIN
/MAX
集計を使用するSELECT
p.ProductID,
MIN(th.TransactionType + STR(th.ReferenceOrderID, 11))
FROM Production.Product AS p
INNER JOIN Production.TransactionHistory AS th ON
th.ProductID = p.ProductID
GROUP BY
p.ProductID;
ROW_NUMBER
WITH x AS
(
SELECT
th.ProductID,
th.TransactionType,
th.ReferenceOrderID,
rn = ROW_NUMBER() OVER (PARTITION BY th.ProductID ORDER BY (SELECT NULL))
FROM Production.TransactionHistory AS th
)
SELECT
p.ProductID,
x.TransactionType,
x.ReferenceOrderID
FROM Production.Product AS p
INNER JOIN x ON x.ProductID = p.ProductID
WHERE
x.rn = 1
OPTION (MAXDOP 1);
ANY
集計を使用するSELECT
q.ProductID,
q.TransactionType,
q.ReferenceOrderID
FROM
(
SELECT
p.ProductID,
th.TransactionType,
th.ReferenceOrderID,
rn = ROW_NUMBER() OVER (
PARTITION BY p.ProductID
ORDER BY p.ProductID)
FROM Production.Product AS p
JOIN Production.TransactionHistory AS th ON p.ProductID = th.ProductID
) AS q
WHERE
q.rn = 1;
ANY
集計の詳細については、このブログ投稿を参照してください。
TOP
SELECT p.ProductID,
(
-- No ORDER BY, so could be any row
SELECT TOP (1)
th.TransactionType + STR( th.ReferenceOrderID, 11)
FROM Production.TransactionHistory AS th WITH (FORCESEEK)
WHERE
th.ProductID = p.ProductID
)
FROM Production.Product AS p;
CROSS APPLY
するTOP (1)
前のクエリは連結を必要とNULL
し、トランザクション履歴のない製品のを返します。CROSS APPLY
withを使用するとTOP
、両方の問題が解決します。
SELECT
p.Name,
ca.TransactionType,
ca.ReferenceOrderID
FROM Production.Product AS p
CROSS APPLY
(
SELECT TOP (1)
th.TransactionType,
th.ReferenceOrderID
FROM Production.TransactionHistory AS th WITH (FORCESEEK)
WHERE
th.ProductID = p.ProductID
) AS ca;
最適なインデックスを作成し、各ユーザーが通常多くの画像を持っているAPPLY
場合は、が最も効率的です。
ユーザーが複数の画像を持っていて、1つの画像だけが必要な場合、どれが必要ですか?MySQLには、選択を強制することなく、古い任意の値を提供するだけの、お粗末な構文がありますが、SQLServerでは選択できます。1つの方法はMIN
:
SELECT u.id, MIN(i.path + i.name) AS image_path
FROM dbo.users AS u
INNER JOIN dbo.images AS i
ON u.id = i.user_id
GROUP BY u.id;
MAX
の代わりに使用することもできますMIN
。また、SQL Serverのバージョンによっては、実際にもっと多くの列が必要かどうかによって、これを少し効率的に行う他の方法がある場合があります(並べ替え/グループ作業の一部を回避します)。たとえば、パスと名前を別々にしたい場合、これはあまりうまくいきません:
SELECT u.id, MIN(i.path), MIN(i.name)
FROM dbo.users AS u
INNER JOIN dbo.images AS i
ON u.id = i.user_id
GROUP BY u.id;
...理論的には2つの異なる行からパスと名前を取得でき、この結果はもはや意味をなさないためです。したがって、代わりにこれを行うことができます:
;WITH x AS
(
SELECT user_id, path, name, rn = ROW_NUMBER() OVER
(PARTITION BY user_id ORDER BY (SELECT NULL))
FROM dbo.images
)
SELECT u.id, x.path, x.name
FROM dbo.users AS u
INNER JOIN x
ON u.id = x.user_id
WHERE x.rn = 1;
既存のケースでこのバリエーションを使用することが理にかなっているかどうかは、これら2つのテーブルのインデックス作成方法に大きく依存しますが、このアプローチを試して、計画/パフォーマンスを比較することができます。
;WITH x AS
(
SELECT user_id, path + name AS image_path, rn = ROW_NUMBER() OVER
(PARTITION BY user_id ORDER BY (SELECT NULL))
FROM dbo.images
)
SELECT u.id, x.image_path
FROM dbo.users AS u
INNER JOIN x
ON u.id = x.user_id
WHERE x.rn = 1;
SELECT NULL
(そして、の狭いインデックスの先頭の列に置き換えてみてくださいdbo.images
。)
AS 'alias'
PS構文を使用しないでください。この形式は非推奨になり、エイリアスは文字列リテラルのように見えます。また、スキーマプレフィックスを常に使用し、エイリアスを使用して、クエリ全体で完全なテーブル名を繰り返す必要がないようにします。
集計関数が必要です。適切な集計関数はアプリケーションに依存します。それはあなたが言うことができる唯一の人であることを意味します。それに対する1つの原始的なハック:
SELECT user.id, max((images.path + images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
MySQLによるGROUPBY句の処理は、広くBADと見なされています。
必要に応じて最大または最小を使用します。
SELECT user.id, max(images.path + images.name) as image_path
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
1人のユーザーが複数の画像を使用できる場合、これにより最初の(アルファベット順の)エントリが選択されます
SELECT user.id, min(images.path + images.name) as image_path
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
使用GROUP BY
する場合は、集計する列と他の集計関数のみを使用できます。
これを実現する1つの方法は次のとおりです。
SELECT user.id, (MAX(images.path) + MAX(images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id
あなたが望む可能性が高いですが:
SELECT user.id, MAX(images.path + images.name)) as 'image_path'
FROM users
JOIN images ON images.user_id = users.id
GROUP BY users.id