PAYATHON 2026

SQL 如何关联两张表,并按 Term 将日期展开为每个 ID 一行?

数据阿明

可以使用 LEFT JOIN 和条件聚合:先按 ID 关联两张表,再用 MAX(CASE WHEN ...) 将各个 Term 的起止日期放入对应列。示例中的 Term 1、2、3 是固定的,无需逐行循环。

查询的前提是 Customer.ID 唯一,且 Dates 中每个 (ID, Term) 最多有一条记录。

为什么 JOIN 和 DISTINCT 没有得到预期结果?

Bob 在 Dates 中有两条记录,John 有三条。普通 JOIN 对每条匹配记录都会输出一行,因此同一个客户会出现多行,这是一对多关联的正常结果。

DISTINCT 只能删除所选列完全相同的重复行。如果记录的 Term 或日期不同,这些行就不会合并;不选择这些字段,又无法保留需要的日期信息。

要得到目标表,需要按客户分组,把不同 Term 的日期放入对应的列。

使用条件聚合完成行转列

下面的查询使用标准 SQL 的双引号引用带空格的字段名和列别名。实际使用时,需要按照数据库的标识符引用规则调整。

SELECT
    c.ID,
    c."First Name",
    c."Family Name",
    MAX(CASE WHEN d.Term = 1 THEN d.DateStart END) AS "Term 1 Start",
    MAX(CASE WHEN d.Term = 1 THEN d.DateEnd END)   AS "Term 1 End",
    MAX(CASE WHEN d.Term = 2 THEN d.DateStart END) AS "Term 2 Start",
    MAX(CASE WHEN d.Term = 2 THEN d.DateEnd END)   AS "Term 2 End",
    MAX(CASE WHEN d.Term = 3 THEN d.DateStart END) AS "Term 3 Start",
    MAX(CASE WHEN d.Term = 3 THEN d.DateEnd END)   AS "Term 3 End"
FROM Customer AS c
LEFT JOIN Dates AS d
    ON d.ID = c.ID
GROUP BY
    c.ID,
    c."First Name",
    c."Family Name"
ORDER BY c.ID;

这里假设 Term 是数值类型。如果是字符类型,条件需要按实际类型改成 d.Term = '1' 等形式。

查询中各部分的作用:

  • LEFT JOIN 保留所有客户,即使某个客户没有日期记录。
  • CASE WHEN d.Term = 1 THEN d.DateStart END 只保留 Term = 1 的开始日期,其余行返回 NULL。
  • MAX 忽略 NULL,在每组最多有一个匹配值的前提下取出该日期。
  • GROUP BY 将同一个客户的匹配记录汇总到一行。

某个 Term 没有对应记录时,该列参与聚合的值全为 NULL,结果也是 NULL。因此,Bob 的 Term 3 Start 和 Term 3 End 会保持为空。

先检查同一个 Term 是否存在多条记录

上述查询适用于示例数据。如果同一个客户在同一个 Term 下有多条记录,需要先确定选择规则。可以用下面的查询检查:

SELECT
    ID,
    Term,
    COUNT(*) AS record_count
FROM Dates
GROUP BY ID, Term
HAVING COUNT(*) > 1;

如果存在多条记录,分别对 DateStart 和 DateEnd 取 MAX,可能会把不同记录的日期拼成一对。

具体是选取某条记录,还是计算最早开始日期与最晚结束日期,需要根据业务要求确定。原问题没有提供这样的规则,不能假定应保留哪一条。如果业务规定每个客户的每个 Term 只能出现一次,可以通过 (ID, Term) 的唯一约束来保证。

如何将结果保存为新表?

确认上面的查询结果正确后,可以使用数据库支持的建表查询语法保存结果。

  • 在支持 CREATE TABLE ... AS SELECT 的数据库中,可以在上述查询的 SELECT 前添加 CREATE TABLE CustomerTerms AS。
  • 在 SQL Server 中,可以使用 SELECT ... INTO CustomerTerms FROM ...,将 INTO CustomerTerms 放在选择列表之后、FROM 之前。带空格的字段名和别名可用方括号引用,例如 [First Name]。

原问题没有说明数据库类型,因此建表语句需要按实际数据库调整。SELECT INTO 用于保存查询结果,日期行转列仍由条件聚合完成。

Term 数量不固定时怎么办?

上面的查询固定输出三个 Term。如果后续出现 Term = 4,需要增加对应的两列。

如果要根据数据自动生成全部 Term 列,通常需要动态 SQL,或在应用层完成透视。具体写法取决于数据库。当前已知的三个 Term 可以用条件聚合处理,也可以选择 PIVOT。

备注:内容仅供参考。