4

この形式の問題に頻繁に遭遇しますが、まだ適切な解決策を見つけていません。

e コマース システムを表す 2 つのデータベース テーブルがあるとします。

userData (userId, name, ...)
orderData (orderId, userId, orderType, createDate, ...)

システム内のすべてのユーザーについて、ユーザー情報、タイプ = '1' の最新の注文情報、およびタイプ = '2' の最新の注文情報を選択します。これを1つのクエリで実行したい。結果の例を次に示します。

(userId, name, ..., orderId1, orderType1, createDate1, ..., orderId2, orderType2, createDate2, ...)
(101, 'Bob', ..., 472, '1', '4/25/2008', ..., 382, '2', '3/2/2008', ...)
4

11 に答える 11

4

これは機能するはずです。テーブル/列名を調整する必要があります。

select ud.name,
       order1.order_id,
       order1.order_type,
       order1.create_date,
       order2.order_id,
       order2.order_type,
       order2.create_date
  from user_data ud,
       order_data order1,
       order_data order2
 where ud.user_id = order1.user_id
   and ud.user_id = order2.user_id
   and order1.order_id = (select max(order_id)
                            from order_data od1
                           where od1.user_id = ud.user_id
                             and od1.order_type = 'Type1')
   and order2.order_id = (select max(order_id)
                             from order_data od2
                            where od2.user_id = ud.user_id
                              and od2.order_type = 'Type2')

データを非正規化することも良い考えです。このタイプのことを行うには、かなりの費用がかかります。したがってlast_order_date、userData に a を追加できます。

于 2008-09-30T18:54:24.667 に答える
3

この問題を解決するために、3つの異なるアプローチを提供しました。

  1. ピボットの使用
  2. ケースステートメントの使用
  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
于 2008-09-30T21:57:51.057 に答える
1

申し訳ありませんが、目の前にオラクルがありませんが、これがオラクルで行う基本構造です。

SELECT b.user_id, b.orderid, b.orderType, b.createDate, <etc>,
       a.name
FROM orderData b, userData a
WHERE a.userid = b.userid
AND (b.userid, b.orderType, b.createDate) IN (
  SELECT userid, orderType, max(createDate) 
  FROM orderData 
  WHERE orderType IN (1,2)
  GROUP BY userid, orderType) 
于 2008-09-30T18:25:24.470 に答える
1

T-SQLサンプルソリューション(MS SQL):

SELECT
    u.*
    , o1.*
    , o2.* 
FROM
(
    SELECT
        , userData.*
        , (SELECT TOP 1 orderId.url FROM orderData WHERE orderData.userId=userData.userId AND orderType=1 ORDER BY createDate DESC)
            AS order1Id
        , (SELECT TOP 1 orderId.url FROM orderData WHERE orderData.userId=userData.userId AND orderType=2 ORDER BY createDate DESC)
            AS order2Id
    FROM userData
) AS u
LEFT JOIN orderData o1 ON (u.order1Id=o1.orderId)
LEFT JOIN orderData o2 ON (u.order2Id=o2.orderId)

SQL 2005では、RANK()OVER関数を使用することもできます。(ただし、AFAIKは完全にMSSQL固有の機能です)

于 2008-09-30T18:29:38.627 に答える
0

これが私のやり方です。これは標準SQLであり、あらゆるブランドのデータベースで機能します。

SELECT u.userId, u.name, o1.orderId, o1.orderType, o1.createDate,
  o2.orderId, o2.orderType, o2.createDate
FROM userData AS u
  LEFT OUTER JOIN (
    SELECT o1a.orderId, o1a.userId, o1a.orderType, o1a.createDate
    FROM orderData AS o1a 
      LEFT OUTER JOIN orderData AS o1b ON (o1a.userId = o1b.userId 
        AND o1a.orderType = o1b.orderType AND o1a.createDate < o1b.createDate)
    WHERE o1a.orderType = 1 AND o1b.orderId IS NULL) AS o1 ON (u.userId = o1.userId)
  LEFT OUTER JOIN (
    SELECT o2a.orderId, o2a.userId, o2a.orderType, o2a.createDate
    FROM orderData AS o2a 
      LEFT OUTER JOIN orderData AS o2b ON (o2a.userId = o2b.userId 
        AND o2a.orderType = o2b.orderType AND o2a.createDate < o2b.createDate)
    WHERE o2a.orderType = 2 AND o2b.orderId IS NULL) o2 ON (u.userId = o2.userId);

日付が最新の日付と等しいいずれかのタイプの注文が複数ある場合は、結果セットに複数の行が含まれることに注意してください。両方のタイプの注文が複数ある場合は、結果セットにNxM行が含まれます。したがって、各タイプの行を別々のクエリでフェッチすることをお勧めします。

于 2008-09-30T19:08:46.020 に答える
0

スティーブKは絶対に正しいです、ありがとう!私は彼の答えを少し書き直して、特定のタイプの注文がない可能性があるという事実を説明しました(私は言及しなかったので、スティーブKを責めることはできません)。

これが私が使ってしまったものです:

select ud.name,
       order1.orderId,
       order1.orderType,
       order1.createDate,
       order2.orderId,
       order2.orderType,
       order2.createDate
  from userData ud
  left join orderData order1
   on order1.orderId = (select max(orderId)
                            from orderData od1
                           where od1.userId = ud.userId
                             and od1.orderType = '1')
  left join orderData order2
   on order2.orderId = (select max(orderId)
                            from orderData od2
                           where od2.userId = ud.userId
                             and od2.orderType = '2')
 where ...[some limiting factors on the selection of users]...;
于 2008-09-30T19:24:18.807 に答える
0

私はMySQLで次のようなものを使用します:

SELECT
   u.*,
   SUBSTRING_INDEX( MAX( CONCAT( o1.createDate, '##', o1.otherfield)), '##', -1) as o2_orderfield,
   SUBSTRING_INDEX( MAX( CONCAT( o2.createDate, '##', o2.otherfield)), '##', -1) as o2_orderfield
FROM
   userData as u
   LEFT JOIN orderData AS o1 ON (o1.userId=u.userId AND o1.orderType=1)
   LEFT JOIN orderData AS o2 ON (o1.userId=u.userId AND o2.orderType=2)
GROUP BY u.userId

つまり、MAX()を使用して、基準フィールド(createDate)を対象のフィールド(otherfield)の前に追加して、最新のものを取得します。次に、SUBSTRING_INDEX()は日付を取り除きます。

OTOH、任意の数の注文が必要な場合(userTypeは任意の数であり、限定されたENUMではない場合)。次のような別のクエリで処理することをお勧めします。

select * from orderData where userId=XXX order by orderType, date desc group by orderType

ユーザーごとに。

于 2008-09-30T18:29:50.277 に答える
0

タイプ 1 とタイプ 2 のデータを同じ行に移動する方法の 1 つを次に示します
(タイプ 1 とタイプ 2 の情報を独自の select に配置し、それを from 句で使用します)。

SELECT
  a.name, ud1.*, ud2.*
FROM
    userData a,
    (SELECT user_id, orderid, orderType, reateDate, <etc>,
    FROM orderData b
    WHERE (userid, orderType, createDate) IN (
      SELECT userid, orderType, max(createDate) 
      FROM orderData 
      WHERE orderType = 1
      GROUP BY userid, orderType) ud1,
    (SELECT user_id, orderid, orderType, createDate, <etc>,
    FROM orderData 
    WHERE (userid, orderType, createDate) IN (
      SELECT userid, orderType, max(createDate) 
      FROM orderData 
      WHERE orderType = 2
      GROUP BY userid, orderType) ud2
于 2008-09-30T18:57:45.110 に答える
0

彼らの最新とは、今日ではすべて新しいということですか?createDate >= 当日の場合は、いつでも createDate を確認して、すべてのユーザーと注文のデータを取得できます。

SELECT * FROM
"orderData", "userData"
WHERE
"userData"."userId"  ="orderData"."userId"
AND "orderData".createDate >= current_date;

更新しました

ここにコメントした後に欲しいものは次のとおりです。

SELECT * FROM
"orderData", "userData"
WHERE
"userData"."userId"  ="orderData"."userId"
AND "orderData".type = '1'
AND "orderData"."orderId" = (
SELECT "orderId" FROM "orderData"
WHERE 
"orderType" = '1'
ORDER "orderId" DESC
LIMIT 1

)

于 2008-09-30T18:14:29.690 に答える
0

これについては、ユニオン クエリを実行できる場合があります。正確な構文、特にセクションごとのグループ化には多少の作業が必要ですが、共用体はそれを実行できるはずです。

例えば:

SELECT orderId, orderType, createDate
FROM orderData
WHERE type=1 AND MAX(createDate)
GROUP BY orderId, orderType, createDate

UNION

SELECT orderId, orderType, createDate
FROM orderData
WHERE type=2 AND MAX(createDate)
GROUP BY orderId, orderType, createDate
于 2008-09-30T18:21:18.490 に答える
0

orderId が時間とともに単調に増加すると仮定します。

SELECT *
FROM userData u
INNER JOIN orderData o
  ON o.userId = u.userId
INNER JOIN ( -- This subquery gives the last order of each type for each customer
  SELECT MAX(o2.orderId)
    --, o2.userId -- optional - include if joining for a particular customer
    --, o2.orderType -- optional - include if joining for a particular type
  FROM orderData o2
  GROUP BY o2.userId
    ,o2.orderType
) AS LastOrders
  ON LastOrders.orderId = o.orderId -- expand join to include customer or type if desired

次に、クライアントでピボットするか、SQL Server を使用している場合は PIVOT 機能があります

于 2008-09-30T18:36:16.020 に答える