【问题标题】:Select 10 records associated with each key选择与每个键关联的 10 条记录
【发布时间】:2016-02-23 21:38:34
【问题描述】:

这是我有两个表标签和客户的情况,结构如下

Tags Table
ID Name   
1  Tag1
2  Tag2

Customers Table
ID Tag_ID Name
1  1      C1
2  2      C2
3  1      C3

我想要一条 SQL 语句来获取每个标签的前 10 个客户(按字母顺序)?是否可以在一个查询中完成。

P.S表格中的数据是样本数据,不是实际数据

【问题讨论】:

  • 您想如何为给定标签订购客户?
  • 我更喜欢让它们按标签 ID 排序,但我现在可以接受任何东西
  • @Strawberry 根据排序从客户表中找到的前 10 条记录
  • @Strawberry 按客户名称排序
  • 但是他们都有相同的名字!?!?!无论如何,请相应地编辑您的问题

标签: mysql


【解决方案1】:

考虑以下几点:

DROP TABLE IF EXISTS tags;

CREATE TABLE tags 
(tag_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY 
,name VARCHAR(12) NOT NULL
);

INSERT INTO tags VALUES
(1,'One'),
(2,'Two'),
(3,'Three'),
(4,'Four'),
(5,'Five'),
(6,'Six');

DROP TABLE IF EXISTS customers;

CREATE TABLE customers  
(customer_id INT NOT NULL
,customer VARCHAR(12)
);

INSERT INTO customers VALUES
(1,'Dave'),
(2,'Ben'),
(3,'Charlie'),
(4,'Michael'),
(5,'Steve'),
(6,'Clive'),
(7,'Alice'),
(8,'Ken'),
(9,'Petra');

DROP TABLE IF EXISTS customer_tag;

CREATE TABLE customer_tag
(customer_id INT NOT NULL
,tag_ID INT NOT NULL
,PRIMARY KEY(customer_id,tag_id)
);

INSERT INTO customer_tag VALUES
(1,1),
(1,2),
(1,4),
(2,3),
(2,2),
(3,1),
(4,4),
(4,2),
(5,2),
(5,5),
(5,6),
(6,6);

以下查询返回与每个标签关联的所有客户,以及按字母顺序排序时他们各自的“排名”...

SELECT t.*, c1.*, COUNT(ct2.tag_id) rank
  FROM tags t
  JOIN customer_tag ct1 
    ON ct1.tag_id = t.tag_id
  JOIN customers c1 
    ON c1.customer_id = ct1.customer_id 
  JOIN customer_tag ct2 
    ON ct2.tag_id = ct1.tag_id 
  JOIN customers c2 
    ON c2.customer_id = ct2.customer_id 
   AND c2.customer <= c1.customer 
 GROUP 
    BY t.tag_id, c1.customer_id
 ORDER 
    BY t.tag_id,rank;
+--------+-------+-------------+----------+------+
| tag_id | name  | customer_id | customer | rank |
+--------+-------+-------------+----------+------+
|      1 | One   |           3 | Charlie  |    1 |
|      1 | One   |           1 | Dave     |    2 |
|      2 | Two   |           2 | Ben      |    1 |
|      2 | Two   |           1 | Dave     |    2 |
|      2 | Two   |           4 | Michael  |    3 |
|      2 | Two   |           5 | Steve    |    4 |
|      3 | Three |           2 | Ben      |    1 |
|      4 | Four  |           1 | Dave     |    1 |
|      4 | Four  |           4 | Michael  |    2 |
|      5 | Five  |           5 | Steve    |    1 |
|      6 | Six   |           6 | Clive    |    1 |
|      6 | Six   |           5 | Steve    |    2 |
+--------+-------+-------------+----------+------+

如果我们只想要前 2 个,比如说,对于每个标签,我们可以重写如下...

SELECT t.*  
     , c1.*
  FROM tags t
  JOIN customer_tag ct1 
    ON ct1.tag_id = t.tag_id
  JOIN customers c1 
    ON c1.customer_id = ct1.customer_id 
  JOIN customer_tag ct2 
    ON ct2.tag_id = ct1.tag_id 
  JOIN customers c2 
    ON c2.customer_id = ct2.customer_id 
   AND c2.customer <= c1.customer 
 GROUP 
    BY t.tag_id, c1.customer_id
HAVING COUNT(ct2.tag_id) <=2
 ORDER 
   BY t.tag_id, c1.customer;
+--------+-------+-------------+----------+
| tag_id | name  | customer_id | customer |
+--------+-------+-------------+----------+
|      1 | One   |           3 | Charlie  |
|      1 | One   |           1 | Dave     |
|      2 | Two   |           2 | Ben      |
|      2 | Two   |           1 | Dave     |
|      3 | Three |           2 | Ben      |
|      4 | Four  |           1 | Dave     |
|      4 | Four  |           4 | Michael  |
|      5 | Five  |           5 | Steve    |
|      6 | Six   |           6 | Clive    |
|      6 | Six   |           5 | Steve    |
+--------+-------+-------------+----------+

这很好,但是在性能存在问题的情况下,类似以下的解决方案会更快 - 尽管您可能需要在构建表之前运行 SET NAMES utf8; (我必须这样做)才能使其正常工作:

SELECT tag_id, name, customer_id,customer 
  FROM
     (
       SELECT t.*
            , c.*
            , CASE WHEN @prev=t.tag_id THEN @i:=@i+1 ELSE @i:=1 END rank
            , @prev := t.tag_id
         FROM tags t
         JOIN customer_tag ct
           ON ct.tag_id = t.tag_id
         JOIN customers c
           ON c.customer_id = ct.customer_id
         JOIN ( SELECT @i:=1, @prev:=0) vars
        ORDER
           BY t.tag_id
            , c.customer
     ) x
 WHERE rank <=2
 ORDER 
    BY tag_id,customer;
+--------+-------+-------------+----------+
| tag_id | name  | customer_id | customer |
+--------+-------+-------------+----------+
|      1 | One   |           3 | Charlie  |
|      1 | One   |           1 | Dave     |
|      2 | Two   |           2 | Ben      |
|      2 | Two   |           1 | Dave     |
|      3 | Three |           2 | Ben      |
|      4 | Four  |           1 | Dave     |
|      4 | Four  |           4 | Michael  |
|      5 | Five  |           5 | Steve    |
|      6 | Six   |           6 | Clive    |
|      6 | Six   |           5 | Steve    |
+--------+-------+-------------+----------+

【讨论】:

  • 感谢您的回答,这正是我要找的。目前,性能不是问题,因为我正在使用这种结构来探索一种方法
【解决方案2】:

为此,我们必须使用两个会话变量,一个用于行号,另一个用于存储旧客户 ID 以将其与当前 ID 进行比较,如下查询:

select c.name, @row_number:=CASE
    WHEN @cid = c.id THEN @row_number + 1
    ELSE 1
END AS rows,
@id:=c.id as CustomerId from tags t, customers c where t.id=c.id group by c.name where Rows<=10

我们在查询中使用了 CASE 语句。如果客户编号不变,我们增加row_number变量

Reference

【讨论】:

  • 我非常感谢答案非常接近我正在寻找的@Strawberry,设法解决了这个问题。如果您能解释您的解决方案,我会很高兴,因为我认为这将在未来有所帮助。 Strawberry 说他的解决方案存在性能问题,也许我们可以将您的解决方案合并为一个完美的解决方案
  • @Strawberry 解决方案不容易,所以我可以标记他。
【解决方案3】:

你的问题让我想起了this one(尤其是投票最多的答案),所以我想出了这个问题:

SELECT Tags.ID,
       Tags.Name,
       SUBSTRING_INDEX(GROUP_CONCAT(Customers.Name
                                    ORDER BY Customers.Name),
                       ',', 10) AS Customers
FROM Customers
INNER JOIN Tags
ON Tags.ID = Customers.Tag_ID
GROUP BY Tags.ID
ORDER BY Tags.Id;

It works,但这显然是一种 hacky 方式,因为 MySQL 还没有提供更自然的工具。

【讨论】:

  • "更自然地做到这一点。" - 那是战斗的谈话;-)
猜你喜欢
  • 2010-09-23
  • 2010-09-15
  • 2020-03-12
  • 1970-01-01
  • 2015-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-24
相关资源
最近更新 更多