この問題を解決するために、3つの異なるアプローチを提供しました。
- ピボットの使用
- ケースステートメントの使用
- where句でのインラインクエリの使用
すべてのソリューションは、列に基づいて「最新の」順序を決定していることを前提としていorderId
ます。列を使用すると、タイムスタンプの衝突により複雑さが増し、おそらくインデックス付きキーの一部ではないcreateDate
ため、パフォーマンスが大幅に低下します。createDate
これらのクエリはMSSQLServer 2005を使用してテストしただけなので、サーバーで機能するかどうかはわかりません。
ソリューション(1)と(2)はほぼ同じように機能します。実際、どちらもデータベースからの読み取り回数は同じになります。
解決策(3)は、大規模なデータセットを操作する場合の推奨されるアプローチではありません。それは一貫して(1)と(2)よりも多くの数百の論理読み取りを行います。1人の特定のユーザーをフィルタリングする場合、アプローチ(3)は他の方法と同等です。シングルユーザーの場合、CPU時間の短縮は、大幅に多い読み取り数に対抗するのに役立ちます。ただし、ディスクドライブがビジーになり、キャッシュミスが発生すると、このわずかな利点はなくなります。
結論
提示されたシナリオでは、DBMSでサポートされている場合はピボットアプローチを使用します。必要なコードはcaseステートメントよりも少なく、将来の注文タイプの追加が簡単になります。
場合によっては、PIVOTの柔軟性が十分でなく、caseステートメントを使用した特性値関数が最適な方法であることに注意してください。
コード
PIVOTを使用したアプローチ(1):
select
ud.userId, ud.fullname,
od1.orderId as orderId1, od1.createDate as createDate1, od1.orderType as orderType1,
od2.orderId as orderId2, od2.createDate as createDate2, od2.orderType as orderType2
from userData ud
inner join (
select userId, [1] as typeOne, [2] as typeTwo
from (select
userId, orderType, orderId
from orderData) as orders
PIVOT
(
max(orderId)
FOR orderType in ([1], [2])
) as LatestOrders) as LatestOrders on
LatestOrders.userId = ud.userId
inner join orderData od1 on
od1.orderId = LatestOrders.typeOne
inner join orderData od2 on
od2.orderId = LatestOrders.typeTwo
ケースステートメントを使用したアプローチ(2):
select
ud.userId, ud.fullname,
od1.orderId as orderId1, od1.createDate as createDate1, od1.orderType as orderType1,
od2.orderId as orderId2, od2.createDate as createDate2, od2.orderType as orderType2
from userData ud
-- assuming not all users will have orders use outer join
inner join (
select
od.userId,
-- can be null if no orders for type
max (case when orderType = 1
then ORDERID
else null
end) as maxTypeOneOrderId,
-- can be null if no orders for type
max (case when orderType = 2
then ORDERID
else null
end) as maxTypeTwoOrderId
from orderData od
group by userId) as maxOrderKeys on
maxOrderKeys.userId = ud.userId
inner join orderData od1 on
od1.ORDERID = maxTypeTwoOrderId
inner join orderData od2 on
OD2.ORDERID = maxTypeTwoOrderId
where句でインラインクエリを使用するアプローチ(3)(Steve K.の応答に基づく):
select ud.userId,ud.fullname,
order1.orderId, order1.orderType, order1.createDate,
order2.orderId, order2.orderType, order2.createDate
from userData ud,
orderData order1,
orderData order2
where ud.userId = order1.userId
and ud.userId = order2.userId
and order1.orderId = (select max(orderId)
from orderData od1
where od1.userId = ud.userId
and od1.orderType = 1)
and order2.orderId = (select max(orderId)
from orderData od2
where od2.userId = ud.userId
and od2.orderType = 2)
テーブルと1000人のユーザーをそれぞれ100件の注文で生成するスクリプト:
CREATE TABLE [dbo].[orderData](
[orderId] [int] IDENTITY(1,1) NOT NULL,
[createDate] [datetime] NOT NULL,
[orderType] [tinyint] NOT NULL,
[userId] [int] NOT NULL
)
CREATE TABLE [dbo].[userData](
[userId] [int] IDENTITY(1,1) NOT NULL,
[fullname] [nvarchar](50) NOT NULL
)
-- Create 1000 users with 100 order each
declare @userId int
declare @usersAdded int
set @usersAdded = 0
while @usersAdded < 1000
begin
insert into userData (fullname) values ('Mario' + ltrim(str(@usersAdded)))
set @userId = @@identity
declare @orderSetsAdded int
set @orderSetsAdded = 0
while @orderSetsAdded < 10
begin
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-06-08', 1)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-02-08', 1)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-08-08', 1)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-09-08', 1)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-01-08', 1)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-06-06', 2)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-02-02', 2)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-08-09', 2)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-09-01', 2)
insert into orderData (userId, createDate, orderType)
values ( @userId, '01-01-04', 2)
set @orderSetsAdded = @orderSetsAdded + 1
end
set @usersAdded = @usersAdded + 1
end
SQLプロファイラーに加えてMSSQLServerでクエリパフォーマンスをテストするための小さなスニペット:
-- Uncomment these to clear some caches
--DBCC DROPCLEANBUFFERS
--DBCC FREEPROCCACHE
set statistics io on
set statistics time on
-- INSERT TEST QUERY HERE
set statistics time off
set statistics io off