加入另一个 table 仅匹配特定记录

Join to another table only matching specific records

我有 table 个端口:

drop table if exists ports;
create table ports(id int, name char(20));

insert into ports (id, name ) values
  (1, 'Port hedland'), 
  (2, 'Kwinana');

以及与这些港口相关的 table 关税:

drop table if exists tariffs;
create table tariffs(id int, portId int, price decimal(12,2), expiry bigint(11));

insert into tariffs (id, portId, price, expiry ) values
    (1, 2, 11.00, 1648408400), 
    (2, 2, 12.00, 1648508400), 
    (3, 2, 13.00, 1648594800), 
    (4, 2, 14.00, 1651273200),
    (5, 2, 15.00, 2250000000 );

insert into tariffs (id, portId, price, expiry ) values
    (1, 1, 21.00, 1648408400), 
    (2, 1, 22.00, 1648508400), 
    (3, 1, 23.00, 1648594800), 
    (4, 1, 24.00, 1651273200),
    (5, 1, 25.00, 2250000000 );

每个关税都有有效期。

我可以很容易地进行查询,以找出每个港口特定日期的正确关税。例如在时间戳 1648594700 正确的关税是:

SELECT * FROM tariffs 
WHERE 1648594700 < expiry AND portId = 2
ORDER BY expiry
LIMIT 1

结果:

id  portId  price   expiry
3   2   13.00   1648594800

但是,在我的应用程序中,我希望能够从 ports 记录开始提取正确的关税。

对于一条条记录,我可以这样做:

SELECT * FROM ports 
LEFT JOIN tariffs on tariffs.portId = ports.id 
WHERE 1648594700 < tariffs.expiry AND ports.id = 2
LIMIT 1

结果:

id  name    id  portId  price   expiry
2   Kwinana 3   2   13.00   1648594800

这感觉有点'dirty',尤其是因为我正在对一条记录进行查找,然后使用 LIMIT 强制只有一个结果。但是,好吧。

我不能做,也不知道怎么做的是一个查询,它将 return 一个 list 端口,并且 each 端口具有匹配上述约束的 price 字段(即,与每个端口的 1648594700 相比,具有最高 expiry 的记录)。

这显然行不通:

SELECT * FROM ports 
left join tariffs on tariffs.portId = ports.id
where 1648594700 < tariffs.expiry

由于查询的结果,使用时间戳 1648594700 进行测试,将是:

id  name    id  portId  price   expiry
2   Kwinana 3   2   13.00   1648594800
2   Kwinana 4   2   14.00   1651273200
2   Kwinana 5   2   15.00   2250000000
1   Port he 3   1   23.00   1648594800
1   Port he 4   1   24.00   1651273200
1   Port he 5   1   25.00   2250000000

相反,所有端口的结果(在进一步过滤之前)应该是:

id  name    id  portId  price   expiry
2   Kwinana 3   2   13.00   1648594800
1   Port he 3   1   23.00   1648594800

是否有一种干净、非 hacky 的方式来获得这样的结果? 作为一个附加的约束,这是否可以在一个查询中完成,没有临时 tables 等?

在 MySQL 8+ 上,ROW_NUMBER 应该可以在这里工作:

WITH cte AS (
    SELECT p.id, p.name, t.price, t.expiry,
           ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY t.expiry) rn
    FROM ports p
    LEFT JOIN tariffs t ON t.portId = p.id
    WHERE t.expiry > 1648594700
)

SELECT id, name, price, expiry
FROM cte
WHERE rn = 1
ORDER BY id;

此逻辑会 return 每个端口都有最近到期的记录。

您可以 select 最低到期时间,进行连接并仅获取具有此最低到期时间的行:

SELECT p.id, p.name, t.id, t.portId, t.price, t.expiry
FROM ports p 
LEFT JOIN tariffs t ON p.id = t.portId
WHERE expiry = (SELECT MIN(expiry) FROM tariffs WHERE 1648594700 < expiry)
ORDER BY p.id;

这将得到你想要的结果,请看这里:db<>fiddle