见@ljh's Answer if you are working on SQL Server。下面的解决方案适用于 MySQL。因为 MySQL 不支持 CTE 和 Window Function,所以有点乱。
MySQL
SET @entity_name = 'ABCD';
SELECT b.*, d.Status
FROM Entity a
INNER JOIN Balances b
ON a.EntityID = b.EntityID
LEFT JOIN
(
SELECT a.EntityID,
a.StatusDate StartDate,
b.StatusDate + Interval -1 DAY EndDate,
a.Status
FROM
(
SELECT b.*, @r1 := @r1 + 1 AS Row_number
FROM `Entity` a
INNER JOIN Status b
ON a.EntityID = b.EntityID
CROSS JOIN (SELECT @r1 := 0) rowCount
WHERE a.EntityName = @entity_name
ORDER BY b.Status ASC
) a
LEFT JOIN
(
SELECT b.*, @r2 := @r2 + 1 AS Row_number
FROM `Entity` a
INNER JOIN Status b
ON a.EntityID = b.EntityID
CROSS JOIN (SELECT @r2 := 1) rowCount
WHERE a.EntityName = @entity_name
ORDER BY b.Status ASC
) b ON a.Row_number = b.Row_number
) d
ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate
WHERE a.EntityName = @entity_name
简要分解
由于 MySQL 不支持 Windowing Function,例如 ROW_NUMBER(),下面的查询使用 User Variable 为每条记录提供类似于 ROW_NUMBER() 的行号,然后将其用于连接在另一个子查询上。
SELECT b.*, @r1 := @r1 + 1 AS Row_number
FROM `Entity` a
INNER JOIN Status b
ON a.EntityID = b.EntityID
CROSS JOIN (SELECT @r1 := 0) rowCount
WHERE a.EntityName = @entity_name
ORDER BY b.Status ASC
输出
╔══════════╦═════════════════════════════════╦════════╦════════════╗
║ ENTITYID ║ STATUSDATE ║ STATUS ║ ROW_NUMBER ║
╠══════════╬═════════════════════════════════╬════════╬════════════╣
║ 1 ║ May, 29 2010 00:00:00+0000 ║ A ║ 1 ║
║ 1 ║ April, 16 2010 00:00:00+0000 ║ B ║ 2 ║
║ 1 ║ April, 02 2010 00:00:00+0000 ║ C ║ 3 ║
║ 1 ║ February, 26 2010 00:00:00+0000 ║ D ║ 4 ║
╚══════════╩═════════════════════════════════╩════════╩════════════╝
为记录提供行号的主要目的是将其用于连接另一个子查询,因此我们可以获得每个Status 的StartDate 和EndDate。这在SQL Server 2012 上很容易,因为它有一个名为LAG() 的窗口函数
╔══════════╦═════════════════════════════════╦══════════════════════════════╦════════╗
║ ENTITYID ║ STARTDATE ║ ENDDATE ║ STATUS ║
╠══════════╬═════════════════════════════════╬══════════════════════════════╬════════╣
║ 1 ║ May, 29 2010 00:00:00+0000 ║ (null) ║ A ║
║ 1 ║ April, 16 2010 00:00:00+0000 ║ May, 28 2010 00:00:00+0000 ║ B ║
║ 1 ║ April, 02 2010 00:00:00+0000 ║ April, 15 2010 00:00:00+0000 ║ C ║
║ 1 ║ February, 26 2010 00:00:00+0000 ║ April, 01 2010 00:00:00+0000 ║ D ║
╚══════════╩═════════════════════════════════╩══════════════════════════════╩════════╝
一旦组织好状态范围。它现在是每个 Balances 的 LookUp 状态的基础。
最终结果
╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║ BALANCEDATE ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║ 1 ║ May, 01 2010 00:00:00+0000 ║ 100 ║ B ║
║ 1 ║ April, 01 2010 00:00:00+0000 ║ 50 ║ D ║
║ 1 ║ March, 01 2010 00:00:00+0000 ║ 75 ║ D ║
║ 1 ║ February, 01 2010 00:00:00+0000 ║ 85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝
SQL Server 2012
上面在MySQL 中演示的查询可以通过使用Common Table Expression 和使用LAG() (introduce in SQL Server 2012 only) 的Window Function 轻松转换为TSQL
WITH lookupTable
AS
(
SELECT EntityID,
StatusDate StartDate,
DATEADD(DAY, -1, LAG(StatusDate) OVER(PARTITION BY EntityID ORDER BY Status)) EndDate,
Status
FROM Status
)
SELECT b.*, d.Status
FROM Entity a
INNER JOIN Balances b
ON a.EntityID = b.EntityID
LEFT JOIN lookupTable d
ON b.BalanceDate BETWEEN d.StartDate AND d.EndDate AND
d.EntityID = a.EntityID
WHERE a.EntityName = 'ABCD'
输出
╔══════════╦═════════════════════════════════╦═════════╦════════╗
║ ENTITYID ║ BALANCEDATE ║ BALANCE ║ STATUS ║
╠══════════╬═════════════════════════════════╬═════════╬════════╣
║ 1 ║ May, 01 2010 00:00:00+0000 ║ 100 ║ B ║
║ 1 ║ April, 01 2010 00:00:00+0000 ║ 50 ║ D ║
║ 1 ║ March, 01 2010 00:00:00+0000 ║ 75 ║ D ║
║ 1 ║ February, 01 2010 00:00:00+0000 ║ 85 ║ (null) ║
╚══════════╩═════════════════════════════════╩═════════╩════════╝