【问题标题】:How to sort a list of lists using a sql query?如何使用 sql 查询对列表列表进行排序?
【发布时间】:2020-02-27 04:15:24
【问题描述】:

抽象问题

我有一个 sql 表,其中包含以下形式的记录:
(list_id, value) 其中list_id 是标识特定列表的整数,value 是有顺序的东西。

我现在很难编写一个 sql 查询来返回该表的所有记录,首先按列表与其他列表相比的排名排序,然后按value 排序。

抽象的问题是,我想使用 sql 对列表进行排序。

比较两个列表的算法

比较两个列表的算法如下:

data CompareRes = FirstSmaller | FirstGreater | Equal deriving Show

compareLists :: Ord a => [a] -> [a] -> CompareRes
compareLists [] [] = Equal
-- Longer lists are considered to be smaller
compareLists _ [] = FirstSmaller
compareLists [] _ = FirstGreater
compareLists (x:xs) (y:ys) 
  | x < y = FirstSmaller
  | x > y = FirstGreater
  | otherwise = compareLists xs ys

详情

在我的具体情况下,这些值都是Dates。 所以我的表是这样的:

CREATE TABLE `list_date` (
  `list_id` INT  NOT NULL,
  `date`    DATE NOT NULL,
  PRIMARY KEY (`list_id`, `date`)
);

我使用的是 mysql:8.0 数据库,因此使用WINDOW-functions 的解决方案是可以接受的。

示例

数据

INSERT INTO `list_date` VALUES
   (1, '2019-11-02'), (1, '2019-11-03'), (1, '2019-11-04'), (1, '2019-11-05'), (1, '2019-11-07'), (1, '2019-11-08'), (1, '2019-11-09'),
   (2, '2019-11-01'), (2, '2019-11-03'), (2, '2019-11-04'),
   (3, '2019-11-01'), (3, '2019-11-02'), (3, '2019-11-03'),
   (4, '2019-11-02'), (4, '2019-11-04'), (4, '2019-11-13'), (4, '2019-11-14'),
   (5, '2019-11-03'), (5, '2019-11-04'), (5, '2019-11-05'), (5, '2019-11-10'),
   (6, '2019-11-01'), (6, '2019-11-02'), (6, '2019-11-03'), (6, '2019-11-05');

查询

我真正努力的地方是创建一个计算list_rank 的表达式:

SELECT 
    `list_id`, 
    `date`,
    <PLEASE HELP> as `list_rank`
FROM 
    `list_date`
ORDER BY 
    `list_rank`, `date`;

预期结果

| list_id | date       | list_rank |
|---------|------------|-----------|
| 6       | 2019-11-01 | 1         |
| 6       | 2019-11-02 | 1         |
| 6       | 2019-11-03 | 1         |
| 6       | 2019-11-05 | 1         |
| 3       | 2019-11-01 | 2         |
| 3       | 2019-11-02 | 2         |
| 3       | 2019-11-03 | 2         |
| 2       | 2019-11-01 | 3         |
| 2       | 2019-11-03 | 3         |
| 2       | 2019-11-04 | 3         |
| 1       | 2019-11-02 | 4         |
| 1       | 2019-11-03 | 4         |
| 1       | 2019-11-04 | 4         |
| 1       | 2019-11-05 | 4         |
| 1       | 2019-11-07 | 4         |
| 1       | 2019-11-08 | 4         |
| 1       | 2019-11-09 | 4         |
| 4       | 2019-11-02 | 5         |
| 4       | 2019-11-04 | 5         |
| 4       | 2019-11-13 | 5         |
| 4       | 2019-11-14 | 5         |
| 5       | 2019-11-03 | 6         |
| 5       | 2019-11-04 | 6         |
| 5       | 2019-11-05 | 6         |
| 5       | 2019-11-10 | 6         |

该图像是我的应用程序生成的当前实时结果。目前排序是使用Java实现的。

编辑

在没有收到更好的答案后,我按照@gordon-linoff 的建议实施了一个解决方案:

SELECT 
    `list_id`, 
    `date`
FROM 
    `list_date`
        INNER JOIN (
            SELECT `sub`.`list_id`,
            GROUP_CONCAT(`sub`.`date` ORDER BY `sub`.`date` SEPARATOR '')  as `concat_dates`
            FROM `list_date` as `sub`
            GROUP BY `sub`.`list_id`
        ) `all_dates` ON (`all_dates`.`list_id` = `list_date`.`list_id`)
ORDER BY 
    `all_dates`.`concat_dates`, `date`;

我还创建了一个SQL Fiddle - 这样您就可以使用您的解决方案了。

但此解决方案不会按预期对列表进行排序,因为较长的列表被认为比较小的列表大。

所以我仍然希望收到一个能 100% 解决我的要求的解决方案 :)

【问题讨论】:

  • 我看不到你的排名是基于什么。为什么 6 排名最低?
  • @nbk:我编辑了这个问题,以便清楚地表明,当比较两个列表并且一个列表从第二个列表的所有元素开始时,应该认为具有更多元素的列表更小。
  • 您的列表没有特征可以对特定的 list_id 进行排序或给出排名编号。你必须提供一个基本的想法,一个算法如何做到这一点。就像我在第一条评论中所说的那样,到目前为止还不清楚。你得解释一下list_id 1是怎么排到4的
  • @nbk 感谢您的澄清。我在 haskell 中添加了比较函数的规范。希望对您有所帮助。

标签: mysql sql sorting mysql-8.0


【解决方案1】:

如果我理解正确,您可以按连接在一起的日期对列表进行排序:

select ld.*
from list_date ld join
     (select list_id, group_concat(date) as dates
      from ld
      group by list_id
     ) ldc
     on ld.list_id = ldc.list_id
order by ldc.dates, ld.date;

【讨论】:

  • 感谢您的回答 - 它真的很有帮助 :) 还有一个小问题:正如您在我的示例结果中看到的那样,较长的列表应该出现在顶部。当我使用您提供的查询时,它会在列表6 之前返回列表3。你也知道我该如何改变吗?
【解决方案2】:

因为它是针对 MySql 8 的,所以可以使用窗口函数(耶)。

这是一个首先计算一些指标的查询,用于计算排名:

SELECT 
 list_id, 
 `date`,
 DENSE_RANK() OVER (ORDER BY ListMinDate ASC, ListCount DESC, ListMaxDate, list_id) AS list_rank
FROM
(
  SELECT 
   list_id,
   `date`,
   COUNT(*) OVER (PARTITION BY list_id) AS ListCount,
   MIN(`date`) OVER (PARTITION BY list_id) AS ListMinDate,
   MAX(`date`) OVER (PARTITION BY list_id) AS ListMaxDate
  FROM list_date
) q
ORDER BY list_rank, `date`

dbfiddle here

的测试

【讨论】:

  • 非常感谢您的回答。但是因为它只是按它们的最小元素、它们的最大元素和它们的大小对列表进行排序,所以您的查询错误地对具有相同数量的元素以及相同的最小和最大元素的列表进行排序。例如,两个列表:[1,3,4][1,2,4] 将错误地按照它们插入的顺序排序(预期输出:[[1,2,4], [1,3,4]])。 db<>fiddle
  • @JonasTulien 数据库表包含无序集。如果表格有类似通过 AUTO_INCREMENT 填充的 ID 或创建记录的日期时间,则可以根据集合的最低 ID 对排名进行排序。但是基于那个例子,人们不知道先插入的是哪个。如果有这样的 ID,那么就可以为它计算一个 MIN OVER,并在 DENSE_RANK 中使用它。
  • 我必须承认,我不明白 idcreated_at 列将如何根据我在问题中指定的算法对列表进行排序。可以举个例子吗?
  • 只是不能保证没有 ORDER BY 的表上的 SELECT 会以与插入时完全相同的顺序返回记录。因此,如果需要它们插入的顺序,则需要一些可用于此的字段。当前 3 个字段的值可以按任何顺序插入。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-16
  • 2014-04-07
相关资源
最近更新 更多