MySQL JOIN最近的行而已?


102

我有一个表客户,存储有customer_id,电子邮件和参考。还有一个附加表customer_data,用于存储对客户所做的更改的历史记录,即,当发生更改时,将插入新行。

为了在一个表中显示客户信息,需要将两个表连接起来,但是只有customer_data中的最新行才应该连接到客户表中。

由于查询是分页的,因此变得有些复杂,因此有一个限制和一个偏移量。

我该如何使用MySQL?我想我想在某个地方放一个DISTINCT ...

此刻的查询是这样的-

SELECT *, CONCAT(title,' ',forename,' ',surname) AS name
FROM customer c
INNER JOIN customer_data d on c.customer_id=d.customer_id
WHERE name LIKE '%Smith%' LIMIT 10, 20

另外,我认为我可以通过这种方式将CONCAT与LIKE一起使用吗?

(我很欣赏INNER JOIN可能是错误的JOIN类型。我实际上不知道不同的JOIN之间有什么区别。我现在要研究一下!)


客户历史记录表的外观如何?如何确定最近的行?是否有一个时间戳字段?
丹尼尔·瓦萨洛

最新的只是插入的最后一行-因此其主键是最高的编号。
bcmcfc 2010年

为什么不触发?看看这个答案:stackoverflow.com/questions/26661314/...
罗德里戈·波罗

大多数/所有答案花费的时间过长,且行数百万。有一些 解决方案具有更好的性能。
HalilÖzgür

Answers:


142

您可能需要尝试以下方法:

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
JOIN      (
              SELECT    MAX(id) max_id, customer_id 
              FROM      customer_data 
              GROUP BY  customer_id
          ) c_max ON (c_max.customer_id = c.customer_id)
JOIN      customer_data cd ON (cd.id = c_max.max_id)
WHERE     CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT     10, 20;

请注意,a JOIN只是的同义词INNER JOIN

测试用例:

CREATE TABLE customer (customer_id int);
CREATE TABLE customer_data (
   id int, 
   customer_id int, 
   title varchar(10),
   forename varchar(10),
   surname varchar(10)
);

INSERT INTO customer VALUES (1);
INSERT INTO customer VALUES (2);
INSERT INTO customer VALUES (3);

INSERT INTO customer_data VALUES (1, 1, 'Mr', 'Bobby', 'Smith');
INSERT INTO customer_data VALUES (2, 1, 'Mr', 'Bob', 'Smith');
INSERT INTO customer_data VALUES (3, 2, 'Mr', 'Jane', 'Green');
INSERT INTO customer_data VALUES (4, 2, 'Miss', 'Jane', 'Green');
INSERT INTO customer_data VALUES (5, 3, 'Dr', 'Jack', 'Black');

结果(不含LIMIT和的查询WHERE):

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
JOIN      (
              SELECT    MAX(id) max_id, customer_id 
              FROM      customer_data 
              GROUP BY  customer_id
          ) c_max ON (c_max.customer_id = c.customer_id)
JOIN      customer_data cd ON (cd.id = c_max.max_id);

+-----------------+
| name            |
+-----------------+
| Mr Bob Smith    |
| Miss Jane Green |
| Dr Jack Black   |
+-----------------+
3 rows in set (0.00 sec)

2
感谢您提供的详细信息。我希望它不仅对我也对他人有帮助!
bcmcfc 2010年

20
从长远来看,这种方法可能会导致性能问题,因为它需要创建一个临时表。因此,另一种解决方案(如果可能的话)是在customer_data中添加一个新的布尔字段(is_last),每次添加新条目时都必须对其进行更新。最后一个条目的is_last = 1,该客户的所有其他条目-is_last = 0。
cephuo 2014年

4
人们还应该(请)从丹尼·库洛姆贝(Danny Coulombe)那里阅读以下答案,因为这个答案(对不起,丹尼尔)对于更长的查询/更多的数据来说非常慢。使我的页面“等待”加载12秒钟;因此,请同时检查stackoverflow.com/a/35965649/2776747。直到进行了许多其他更改后,我才注意到它,所以花了很长时间才找出来。
艺术

您不知道这对我有多大帮助:)谢谢大师
node_man

102

如果要处理大量查询,则最好将请求移至where子句中的最新行。它快很多,看起来更干净。

SELECT c.*,
FROM client AS c
LEFT JOIN client_calling_history AS cch ON cch.client_id = c.client_id
WHERE
   cch.cchid = (
      SELECT MAX(cchid)
      FROM client_calling_history
      WHERE client_id = c.client_id AND cal_event_id = c.cal_event_id
   )

4
哇,我几乎无法相信这与性能差异有多大。不知道为什么那是如此激烈,但到目前为止它是如此之快,以至于感觉就像我在其他地方搞砸了……
Brian Leishman

2
我真的希望我可以不止一次为此+1,以便能看到更多。我已经对此进行了相当多的测试,并且某种程度上它使我的查询几乎是瞬时的(即使使用sql_no_cache set,WorkBench也可以说0.000秒),而在联接中进行搜索要花几秒钟才能完成。仍然感到困惑,但是我的意思是你不能与那样的结果争论。
Brian Leishman

1
您将直接直接连接2个表,然后使用WHERE进行过滤。我认为,如果您拥有一百万个客户和数以千万计的通话记录,这将是一个巨大的性能问题。因为SQL会先尝试联接2个表,然后再过滤到单个客户端。我宁愿先在子查询中从表中过滤客户端和相关的调用历史记录,然后再联接表。
塔里克

1
我想两者的“ ca.client_id”和“ ca.cal_event_id”都必须为“ c”。
Herbert Van-Vliet

1
我同意@NickCoons。NULL值不会被返回,因为它们被where子句排除。您将如何包括NULL值,并且仍然保持此查询的出色性能?
aanders77 '19

10

假设中的自动增量列customer_data名为Id,您可以执行以下操作:

SELECT CONCAT(title,' ',forename,' ',surname) AS name *
FROM customer c
    INNER JOIN customer_data d 
        ON c.customer_id=d.customer_id
WHERE name LIKE '%Smith%'
    AND d.ID = (
                Select Max(D2.Id)
                From customer_data As D2
                Where D2.customer_id = D.customer_id
                )
LIMIT 10, 20

9

对于必须使用旧版本的MySQL(5.0之前的版本)的用户,您将无法对此类型的查询进行子查询。这是我能够做的解决方案,它似乎很棒。

SELECT MAX(d.id), d2.*, CONCAT(title,' ',forename,' ',surname) AS name
FROM customer AS c 
LEFT JOIN customer_data as d ON c.customer_id=d.customer_id 
LEFT JOIN customer_data as d2 ON d.id=d2.id
WHERE CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%'
GROUP BY c.customer_id LIMIT 10, 20;

从本质上讲,这是找到将数据表连接到客户的数据表的最大ID,然后将数据表连接到找到的最大ID。原因是因为选择一个组的最大值并不能保证其余数据与id匹配,除非您将其重新加入自身。

我尚未在较新版本的MySQL上进行过测试,但它在4.0.30上有效。


这很简单。为什么这是我第一次见过这种方法?请注意,这EXPLAIN表示使用临时表和文件排序。最后添加ORDER BY NULL除杂文件。
Timo 2015年

令我遗憾的是,我自己的非完美解决方案的数据处理速度是我的数据的3.5倍。我使用子查询选择主表以及已连接表的最新ID,然后使用外部查询选择子查询并从已连接表中读取实际数据。我将5个表连接到主表上,并使用选择1000条记录的where条件进行测试。索引是最佳的。
Timo 2015年

我正在使用您的解决方案SELECT *, MAX(firstData.id), MAX(secondData.id) [...]。逻辑上,通过更改为,SELECT main.*, firstData2.*, secondData2.*, MAX(firstData.id), MAX(secondData.id), [...]我可以使其速度大大提高。这允许第一次连接仅从索引中读取,而不必从主索引中读取所有数据。现在,漂亮的解决方案仅需要基于子查询的解决方案的1.9倍的时间。
Timo

它在MySQL 5.7中不再起作用。现在d2。*将返回组中第一行而不是最后一行的数据。。SELECT MAX(R1.id)中,R 2 * FROM发票我LEFT JOIN响应R1 ON I.id = R1.invoice_id LEFT JOIN R2响应ON R1.id = R2.id GROUP BY I.id LIMIT 0,10
马可马沙拉

5

我知道这个问题很旧,但是多年来已经引起了很多关注,我认为它缺少一个可以在类似情况下为某人提供帮助的概念。为了完整起见,我在这里添加它。

如果您不能修改原始数据库架构,那么将提供许多好的答案,并且可以很好地解决问题。

但是,如果可以修改架构,建议在customer表中添加一个字段,该字段包含该客户id的最新customer_data记录:

CREATE TABLE customer (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  current_data_id INT UNSIGNED NULL DEFAULT NULL
);

CREATE TABLE customer_data (
   id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
   customer_id INT UNSIGNED NOT NULL, 
   title VARCHAR(10) NOT NULL,
   forename VARCHAR(10) NOT NULL,
   surname VARCHAR(10) NOT NULL
);

查询客户

查询尽可能简单,快捷:

SELECT c.*, d.title, d.forename, d.surname
FROM customer c
INNER JOIN customer_data d on d.id = c.current_data_id
WHERE ...;

缺点是创建或更新客户时的额外复杂性。

更新客户

每当您要更新客户时,就在customer_data表中插入一条新记录,然后更新该customer记录。

INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(2, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = 2;

建立客户

创建客户只需插入customer条目,然后运行相同的语句即可:

INSERT INTO customer () VALUES ();

SET @customer_id = LAST_INSERT_ID();
INSERT INTO customer_data (customer_id, title, forename, surname) VALUES(@customer_id, 'Mr', 'John', 'Smith');
UPDATE customer SET current_data_id = LAST_INSERT_ID() WHERE id = @customer_id;

包起来

创建/更新客户的额外复杂性可能令人生畏,但可以使用触发器轻松地将其自动化。

最后,如果您使用的是ORM,这确实很容易管理。ORM可以为您自动插入值,更新ID和自动将两个表连接在一起。

这是可变Customer模型的样子:

class Customer
{
    private int id;
    private CustomerData currentData;

    public Customer(String title, String forename, String surname)
    {
        this.update(title, forename, surname);
    }

    public void update(String title, String forename, String surname)
    {
        this.currentData = new CustomerData(this, title, forename, surname);
    }

    public String getTitle()
    {
        return this.currentData.getTitle();
    }

    public String getForename()
    {
        return this.currentData.getForename();
    }

    public String getSurname()
    {
        return this.currentData.getSurname();
    }
}

而您的不可变CustomerData模型仅包含吸气剂:

class CustomerData
{
    private int id;
    private Customer customer;
    private String title;
    private String forename;
    private String surname;

    public CustomerData(Customer customer, String title, String forename, String surname)
    {
        this.customer = customer;
        this.title    = title;
        this.forename = forename;
        this.surname  = surname;
    }

    public String getTitle()
    {
        return this.title;
    }

    public String getForename()
    {
        return this.forename;
    }

    public String getSurname()
    {
        return this.surname;
    }
}

我将此方法与@ payne8的解决方案结合在一起(上面),以得到我想要的结果,而没有任何子查询。
生姜和薰衣草

2
SELECT CONCAT(title,' ',forename,' ',surname) AS name * FROM customer c 
INNER JOIN customer_data d on c.id=d.customer_id WHERE name LIKE '%Smith%' 

我认为您需要将c.customer_id更改为c.id

否则更新表结构


我之所以投票,是因为我误读了您的答案,最初我认为这是错误的。急速是个糟糕的顾问:-)
Wirone

1

您也可以这样做

SELECT    CONCAT(title, ' ', forename, ' ', surname) AS name
FROM      customer c
LEFT JOIN  (
              SELECT * FROM  customer_data ORDER BY id DESC
          ) customer_data ON (customer_data.customer_id = c.customer_id)
GROUP BY  c.customer_id          
WHERE     CONCAT(title, ' ', forename, ' ', surname) LIKE '%Smith%' 
LIMIT     10, 20;

0

将实际数据记录到“ customer_data ”表中是一个好主意。使用此数据,您可以根据需要从“ customer_data”表中选择所有数据。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.