MySQL - JOIN, GROUP BY with ORDER 分组

MySQL - JOIN, GROUP BY with ORDER of grouping

我有两个 table:

CREATE TABLE Person {
    ID INT PRIMARY KEY,
    Name VARCHAR(50) NOT NULL,
    Surname VARCHAR(50) NOT NULL
}

CREATE TABLE Address {
    ID INT PRIMARY KEY,
    ID_Person INT NOT NULL,
    Street VARCHAR(50),
    HouseNumber VARCHAR(15),
    City VARCHAR(75),
    Zipcode VARCHAR(10),
    CountryCode CHAR(2),
    IsPrimary TINYINT(1) DEFAULT 0
}

每个人可以有多个地址,但最多只能有一个地址(IsPrimary = 1)。

我想获得一个地址的人员名单。如果person有主地址,应该提供,如果没有,没关系,找哪个地址。

我有这个查询:

SELECT
    p.Name,
    p.Surname,
    a.Street,
    a.Housenumber,
    a.City,
    a.Zipcode
FROM
    Person AS p        
LEFT JOIN (select * from Address ORDER BY IsPrimary DESC) AS a ON p.ID = a.ID_Person
GROUP BY p.ID

但这并没有提供我期望的结果。我希望在执行 GROUP BY 时检索第一行加入的 table,但事实并非如此。

有人问过类似的问题here,但在我的情况下解决起来相当困难。

ORDER BY 在子查询中通常只会减慢您的查询速度。 您将不得不订购结果:

SELECT
    p.Name,
    p.Surname,
    a.Street,
    a.Housenumber,
    a.City,
    a.Zipcode
FROM
    Person AS p        
LEFT JOIN Address AS a ON p.ID = a.ID_Person
GROUP BY p.ID ORDER BY a.isPrimary

此查询还有第二个问题:它不符合 ANSI,因此它仅在 MySQL 中有效,而 MySQL 不符合 运行 ANSI。

假设这样: 您有 1 p.ID,在 table Address 中有两行。没有 GROUP 函数应用于 Address.City,数据库如何知道要显示哪个 City?它没有,所以你看到一个随机的。为防止这种情况,将函数应用于不在分组依据中的所有列(或将列置于分组依据中)。

在 MySQL 8.0 中,这最好作为排名查询来完成。

WITH PersonAddress AS (
  SELECT 
    p.Name,
    p.Surname,
    a.Street,
    a.Housenumber,
    a.City,
    a.Zipcode,
    ROW_NUMBER() OVER (PARTITION BY p.ID ORDER BY a.IsPrimary DESC) AS rn
  FROM Person AS p
  LEFT OUTER JOIN Address AS a ON p.ID = a.ID_Person
)
SELECT * FROM PersonAddress WHERE rn = 1;

在 MySQL 8.0 之前,窗口功能不可用。解决方法是使用会话变量:

SELECT 
  t.Name,
  t.Surname,
  t.Street,
  t.Housenumber,
  t.City,
  t.Zipcode
FROM (
  SELECT 
    p.Name,
    p.Surname,
    a.Street,
    a.Housenumber,
    a.City,
    a.Zipcode,
    IF(p.ID = @pid, @rn:=@rn+1, @rn:=1) AS rn,
    @pid := p.ID
  FROM (SELECT @pid:=0, @rn:=1) AS _init
  CROSS JOIN Person AS p
  LEFT OUTER JOIN Address AS a ON p.ID = a.ID_Person
  ORDER BY p.ID, a.IsPrimary DESC
) AS t
WHERE t.rn = 1;