如何有效地构造选择了最后一个子项的父子表的查询

时间:2011-02-15 17:23:42

标签: sql mysql

我有两张表,反映了有关递交给客户的卡的数据。

第一个表是所有客户的列表。客户有一张卡号,这是第一张交给客户的卡。

card:
+----------+-------------+
| card_id  | Name, etc   |
+----------+-------------+
| 123123   | First Client|
| 123124   | 2nd   Client|
+----------+-------------+

第二个表是历史文件,其中包含card_id作为外键。 更换卡(例如,丢失,被盗,过期)时,卡表不会更改,但会在历史文件中创建一个条目。

card_history:
+----------+-------------+--------------+----------------+
| card_id  | new_card_id | Date_created | date_replaced  |
+----------+-------------+--------------+----------------+
| 123123   | 123123      | 2010-01-01   | 0000-00-00     |
| 123123   | 123789      | 0000-00-00   | 2010-01-31     |
| 123123   | 123790      | 0000-00-00   | 2010-02-15     |
+----------+-------------+--------------+----------------+

在这里,您可以看到2010-01-01上出现了一张新卡,并在2010-01-31和2010-02-15进行了两次更换。

我需要生成一个如下所示的报告(使用mysql)。

Name,           old_card, new_card, date_issued
------------------------------------------------
"First Client", 123123,   123790,   2010-02-15

我目前的查询太慢了。

我尝试了两种方法:

0.1。使用连接创建单个查询

 SELECT ...
        FROM card
   LEFT JOIN card_history ON card.card_id = card_history.card_id
         AND ( select ....)

但我似乎无法使子查询正确。

0.2。创建了一个视图

   CREATE VIEW v1 as
      SELECT MAX(GREATER(Date_created, date_replaced) as date_issued
        FROM card_history
       GROUP BY card_id

视图有效,但速度非常慢(每次查找约40秒)。这两个表都很大,有大约200万条记录。我在card_ids上有索引。

如何构建查询以便我可以有效地提取所需的数据?

更新

首先,我没有提到这些卡不是以任何顺序发行的,它们是随机发放的,这使得解决方案提供了方便,但对我的情况不正确。

其次,我对派生表的奇迹感到敬畏,并拥有当前查询,它导出两个表之间的连接,并尝试获取我们寻找的记录(card_history表中最后更新的条目)。 / p>

SELECT * FROM 
(
   SELECT card.card_id, card.name,
          card_history.new_card_id,
          card_history.date_created, card_history.date_replaced, GREATEST(card_history.date_created, card_history.date_replaced) AS    last_date
          FROM card
LEFT JOIN card_history ON card.card_id  = card_history.card_id 
ORDER BY last_date DESC

) AS B
;

但是,我怀疑我的查询是随机选择派生表行。

我需要的是提取new_card_id和发布或替换的最后日期。

我的查询是否会删除它?

1 个答案:

答案 0 :(得分:1)

use test
DROP TABLE IF EXISTS card;
DROP TABLE IF EXISTS card_history;

CREATE TABLE card ( card_id INT NOT NULL PRIMARY KEY,name VARCHAR(255) );

CREATE TABLE card_history
(card_id INT NOT NULL,new_card_id INT NOT NULL,
date_created date,date_replaced date,
PRIMARY KEY (card_id,new_card_id),
KEY (card_id,date_replaced));

INSERT INTO card VALUES (123123,'First Client'),(123124,'2ndClient');
INSERT INTO card_history VALUES
(123123,123123,'2010-01-01','0000-00-00'),
(123123,123789,'0000-00-00','2010-01-31'),
(123123,123790,'0000-00-00','2010-02-15');

SELECT AA.name,AA.card_id old_card,
(SELECT MAX(new_card_id)
 FROM card_history BB
 WHERE BB.card_id=AA.card_id) new_card,
(SELECT MAX(GREATEST(date_created,date_replaced))
 FROM card_history BB
 WHERE BB.card_id=AA.card_id) date_issued
FROM (SELECT A.name,B.card_id
      FROM card A
      INNER JOIN card_history B USING (card_id)
      WHERE B.card_id=B.new_card_id) AA;

+--------------+----------+----------+-------------+
| name         | old_card | new_card | date_issued |
+--------------+----------+----------+-------------+
| First Client |   123123 |   123790 | 2010-02-15  |
+--------------+----------+----------+-------------+

如果要排除没有发出新卡的行,请执行以下操作:

SELECT *
FROM
  (SELECT AA.name,AA.card_id old_card,
     (SELECT MAX(new_card_id)
      FROM card_history BB
      WHERE BB.card_id=AA.card_id) new_card,
     (SELECT MAX(GREATEST(date_created,date_replaced))
      FROM card_history BB
      WHERE BB.card_id=AA.card_id) date_issued
   FROM (
      SELECT A.name,B.card_id
      FROM card A
      INNER JOIN card_history B USING (card_id)
      WHERE B.card_id=B.new_card_id) AA
) AAA
WHERE old_card <> new_card;

它对我有用!!!试一试!!!

更新

我查看了我的EXPLAIN计划,我不喜欢我所看到的。

请确保card_history的主键是(card_id,new_card_id)
我还添加了索引(card_id,date_replaced)

试试这个;这有一个更好的EXPLAIN计划。

SELECT AA.name,AA.card_id old_card,
   (SELECT MAX(new_card_id)
    FROM card_history BB
    WHERE BB.card_id=AA.card_id) new_card,
   (SELECT MAX(date_replaced)
    FROM card_history BB
    WHERE BB.card_id=AA.card_id) date_issued
FROM (
  SELECT A.name,B.card_id
  FROM card A
  INNER JOIN card_history B USING (card_id)
  WHERE B.card_id=B.new_card_id) AA;

如果您希望报告显示新卡片,请坚持我的第一个查询。

玩得开心!!!