MySQL - JOIN,GROUP BY和分组

时间:2017-12-25 19:00:22

标签: mysql join group-by sql-order-by greatest-n-per-group

我有两张桌子:

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)。

我想获得一个地址的人员名单。如果有人有主要地址,则应提供,如果没有,则无关紧要,检索哪个地址。

我有这个问题:

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时检索第一行连接表,但事实并非如此。

类似的问题被问到here,但在我的情况下解决方案相当困难。

2 个答案:

答案 0 :(得分:1)

子查询中的

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不符合ANSI标准时才能在MySQL中运行。

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

答案 1 :(得分:1)

在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;