这很难用 SQL 快速完成。在编程语言中,我会选择这个算法:
- 按日期对两个表进行排序并指向第一行。
- 将排名指针向前移动,直到我们匹配销售日期或超出销售日期。 (如果我们还没有。)
- 将销售日期与我们指向的排名日期以及前一行的排名日期进行比较。走近一点。
- 将销售指针向前移动一排。
- 转到 2。
使用此算法,我们已经处于我们想要的位置。让我们看看,如果我们可以用 SQL 做同样的事情。迭代是通过 SQL 中的递归查询完成的。这些在 MySQL 8.0 版本中可用。
我们从对行进行排序开始,即给它们编号。然后我们遍历这两个数据集。
with recursive
sales as
(
select *, row_number() over (partition by movie_id order by date) as rn
from movie_sales
),
ranks as
(
select *, row_number() over (partition by movie_id order by date) as rn
from movie_rank
),
cte (movie_id, revenue, srn, rrn, sdate, rdate, rrank, closest_rank) as
(
select
movie_id, s.revenue, s.rn, r.rn, s.date, r.date, r.ranking,
case when s.date <= r.date then r.ranking end
from (select * from sales where rn = 1) s
join (select * from ranks where rn = 1) r using (movie_id)
union all
select
cte.movie_id,
cte.revenue,
coalesce(s.rn, cte.srn),
coalesce(r.rn, cte.rrn),
coalesce(s.date, cte.sdate),
coalesce(r.date, cte.rdate),
coalesce(r.ranking, cte.rrank),
case when coalesce(r.date, cte.rdate) >= coalesce(s.date, cte.sdate) then
case when abs(datediff(coalesce(r.date, cte.rdate), coalesce(s.date, cte.sdate))) <
abs(datediff(cte.rdate, coalesce(s.date, cte.sdate)))
then coalesce(r.ranking, cte.rrank)
else cte.rrank
end
end
from cte
left join sales s on s.movie_id = cte.movie_id and s.rn = cte.srn + 1 and cte.closest_rank is not null
left join ranks r on r.movie_id = cte.movie_id and r.rn = cte.rrn + 1 and cte.rdate < cte.sdate
where s.movie_id is not null or r.movie_id is not null
-- where cte.closest_rank is null
)
select
movie_id,
sdate,
revenue,
closest_rank
from cte
where closest_rank is not null;
(顺便说一句:我将列命名为ranking,因为rank 是SQL 中的保留字。)
演示:https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e994cb56798efabc8f7249fd8320e1cf
这可能仍然很慢。原因是:SQL 中没有指向行的指针。如果我们想从第 1 行转到第 2 行,我们必须搜索该行,而在编程语言中,我们实际上只需将指针向前移动一步。如果表有 ID,我们可以构建一个链 (next_row_id) 而不是使用行号。这可以加快这个过程。不过好吧,我猜你已经注意到了:这不是为 SQL 设计的算法。