문제

나는 종종이 형식의 문제가 발생했지만 아직 좋은 해결책을 찾지 못했습니다.

전자 상거래 시스템을 나타내는 두 개의 데이터베이스 테이블이 있다고 가정합니다.

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

시스템의 모든 사용자에 대해 사용자 정보, 유형 = '1'인 최신 주문 정보 및 유형 = '2'가있는 가장 최근의 주문 정보를 선택하십시오. 한 쿼리로 이것을하고 싶습니다. 예제 결과는 다음과 같습니다.

(userId, name, ..., orderId1, orderType1, createDate1, ..., orderId2, orderType2, createDate2, ...)
(101, 'Bob', ..., 472, '1', '4/25/2008', ..., 382, '2', '3/2/2008', ...)
도움이 되었습니까?

해결책

이것은 작동해야합니다. 테이블 / 열 이름을 조정해야합니다.

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에.

다른 팁

이 문제를 해결하기위한 세 가지 다른 접근법을 제공했습니다.

  1. 피벗 사용
  2. 사례 진술 사용
  3. WHERE 절에서 인라인 쿼리 사용

모든 솔루션은 우리가 "가장 최근"주문을 기준으로 결정한다고 가정합니다. orderId 열. 사용 createDate 열은 타임 스탬프 충돌로 인해 복잡성을 더하고 성능을 심각하게 방해합니다. createDate 아마도 인덱스 키의 일부가 아닐 것입니다. MS SQL Server 2005를 사용하여 이러한 쿼리 만 테스트 했으므로 서버에서 작동하는지 전혀 모릅니다.

솔루션 (1) 및 (2)는 거의 동일하게 수행됩니다. 실제로, 둘 다 데이터베이스에서 동일한 수의 읽기를 초래합니다.

해결책 (3)은 ~ 아니다 큰 데이터 세트로 작업 할 때 선호하는 접근법. 그것은 일관되게 수백 개의 논리적 읽기를 (1)과 (2) 이상으로 만듭니다. 한 특정 사용자를 필터링 할 때 접근 (3)은 다른 방법과 비슷합니다. 단일 사용자의 경우 CPU 시간이 줄어들면 상당히 높은 수의 판독 값에 대응하는 데 도움이됩니다. 그러나 디스크 드라이브가 더 바빠지고 캐시가 발생하면이 약간의 이점이 사라집니다.

결론

제시된 시나리오의 경우 DBMS에서 지원되는 경우 피벗 방식을 사용하십시오. CASE 문보다 코드가 적고 향후 주문 유형 추가를 단순화합니다.

경우에 따라 피벗은 충분히 유연하지 않으며 사례 문을 사용하여 특징적인 값 기능이 갈 길입니다.

암호

접근 (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

접근 (3) WARE 절에서 인라인 쿼리 사용 (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)

각각 100 주문으로 테이블과 1000 명의 사용자를 생성하는 스크립트 :

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 프로파일 러 외에 MS SQL Server에서 쿼리 성능을 테스트하기위한 작은 스 니펫 :

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

죄송합니다. 내 앞에 오라클이 없지만 이것이 오라클에서 할 일의 기본 구조입니다.

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) 

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에서는 Over Function ()를 사용할 수도 있습니다. (그러나 Afaik는 완전히 MSSQL 특정 기능)

이를 위해 Union Query를 수행 할 수 있습니다. 정확한 구문에는 일부 작업, 특히 그룹별로 작업이 필요하지만 노조는이를 수행 할 수 있어야합니다.

예를 들어:

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

그들의 최신 당신은 오늘날 새로 새로운 것을 의미합니까? 생성물> = 현재 일인 경우 언제든지 생성물로 확인하고 모든 사용자 및 주문 데이터를받을 수 있습니다.

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

)

나는 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 ()를 사용하여 기준 필드 (생성)를 흥미로운 필드 (S) (Otherfield)로 선물하여 최신 최신을 얻으십시오. substring_index () 그런 다음 날짜를 제거합니다.

OTOH, 임의의 주문이 필요한 경우 (사용자 유형이 제한된 열거가 아닌 숫자가 될 수있는 경우); 별도의 쿼리로 처리하는 것이 좋습니다.

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

각 사용자에 대해.

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를 사용하는 경우 피벗 기능이 있습니다.

다음은 유형 1 및 2 데이터를 동일한 행으로 이동하는 한 가지 방법입니다.
(Type 1과 Type 2 정보를 자체 선택에 배치하여 From Clause에서 사용됩니다.)

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

내가하는 방법은 다음과 같습니다. 이것은 표준 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);

날짜가 최신 날짜와 동일한 두 유형의 여러 주문이있는 경우 결과 세트에서 여러 행이 나타납니다. 두 유형의 여러 주문이 있으면 결과 세트에서 N X M 행이 나타납니다. 따라서 각 유형의 행을 별도의 쿼리로 가져 오는 것이 좋습니다.

Steve 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]...;
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top