如何在mysql中实现自然排序
背景
熟悉mysql的同学应该清楚,mysql在对字符串做order by排序时是按照字典序进行排序的,但是如果字符串中包含数字的话(我们称这种类型的字符串为alphanumeric),仅按照字典序的排序结果对用户不太友好。我们举个例子,假设我们在mysql中存了一张files表,里面记录了文件的id以及文件的name,表里的数据如下:
| id | name |
|---|---|
| 1 | 1测试2 |
| 2 | 测试 |
| 3 | 1 |
| 4 | 1测试12 |
| 5 | 1测试1 |
| 6 | 1测试20 |
name字段目前是乱序的,现在我们对该表执行order by查询,即SELECT * FROM files ORDER BY name:
| id | name |
|---|---|
| 3 | 1 |
| 5 | 1测试1 |
| 4 | 1测试12 |
| 1 | 1测试2 |
| 6 | 1测试20 |
| 2 | 测试 |
我们重点关注以“1测试”开头的那四个name,他们末尾都带有了数字,但因为mysql默认采取字典序排序,所以排序的结果是"1" < "12" < "2" < "20",这显然不太友好,我们更期望数字部分是根据数字大小排序的。这种非数字部分按照字典序排序,数字部分按照数字大小进行排序的方式我们就称之为自然排序(natural sort)。
与字典序排序相比,自然排序的结果更加人性化,对用户更加友好。现代操作系统其实已经实现了文件名的自然排序,我们以Mac为例:
现代的编程语言也都内置了自然排序算法,比如php的natsort方法。但是由于mysql中没有内置对应的函数,我们只能通过其他的办法来实现mysql的自然排序。
思路
要想对mysql做一些扩展,一共有以下三种方法:
- 修改底层源码。
- 编写mysql扩展(plugin)。
- 编写存储函数(stored function)。
显然,要想实现自然排序,我们势必要对order by做一些手脚。如果是第一种方法,不仅难度大,而且不利于mysql的版本升级。如果是第二种方法,mysql扩展又不支持扩展语法层面的能力。那么我们只能采用第三种方法了,也就是存储函数或者又称之为UDF。
如果采用存储函数,那么其实我们在排序时还是用的字典序,所以我们需要借助存储函数将原来待排序的字段(比如例子中的name字段)转换成就算按照字典序排序也能达到自然排序效果的字段。
我们再来看看自然排序的核心思想:非数字部分按照字典序排序,数字部分按照数字大小排序。所以我们只要将数字部分转换成可按大小排序的字符串即可。我们以上例中的”1测试12“和”1测试2“为例,我们将末尾”12“和”2“转化为定长的字段,比如”0000012“和”0000002“。此时”0000012“按照字典序比”0000002“大,这就实现字典序下的自然排序。
好了说了这么多,show me the code:
DELIMITER ;;
CREATE FUNCTION NatSort (Varstring VARCHAR(50))
RETURNS VARCHAR(1000)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE v_length INT DEFAULT 0;
DECLARE v_num VARCHAR(50) DEFAULT '';
DECLARE v_index INT DEFAULT 1;
DECLARE v_result VARCHAR(1000) DEFAULT '';
DECLARE v_flag INT DEFAULT 0;
DECLARE v_char CHAR(1) DEFAULT '';
SET v_flag=0;
SET v_index=1;
SET v_length=CHAR_LENGTH(Varstring);
-- 遍历字符串
WHILE v_index <= v_length DO
SET v_char = mid(Varstring,v_index,1);
IF (ASCII(v_char)>=48 AND ASCII(v_char)<=57) THEN
SET v_num=concat(v_num,mid(Varstring,v_index,1)); -- 获取字符串里的数字
SET v_flag = 1;
ELSE
IF v_flag = 1 THEN
SET v_flag=0;
SET v_result=concat(v_result,lpad(cast(v_num AS UNSIGNED),10,'0')); -- 将数字转成定长字符串
SET v_num=''; -- 重置v_num
END IF;
SET v_result=concat(v_result, v_char);
END IF;
SET v_index = v_index + 1;
END WHILE;
IF v_flag=1 THEN
SET v_result=concat(v_result,lpad(cast(v_num AS UNSIGNED),10,'0'));
END IF;
RETURN v_result;
END;;复制代码
在上述代码中,我们将所有数字转成了共10位的定长字符串。我们看一下函数的具体效果,我们执行SELECT *, NatSort(name) as name_sort FROM files ORDER BY name_sort:
| id | name | name_sort |
|---|---|---|
| 3 | 1 | 0000000001 |
| 5 | 1测试1 | 0000000001测试0000000001 |
| 1 | 1测试2 | 0000000001测试0000000002 |
| 4 | 1测试12 | 0000000001测试0000000012 |
| 6 | 1测试20 | 0000000001测试0000000020 |
| 2 | 测试 | 测试 |
从结果中我们可以看到,已经实现了自然排序,经过实际测试,性能还行。
注意和优化
细心的同学可能已经发现了,上述算法并不完美。我们在代码中将数字扩充为了定长为10位的字符串,那么如果原字符串中的数字长度大于10位,那么算法就失效了。所以在实际使用过程中,要根据具体的业务场景设定定长的位数。
另外,为了提高查询性能,我们可以事先就将转换后的字符串存储在表中,这样就不需要每次查询时都需要调用存储函数。这也是常用的一种以“空间换时间”的优化手段。
本文首发于www.kissyu.org/2017/04/16/… 转载请标注作者和来源
如何在mysql中实现自然排序的更多相关文章
- 如何在MySQL中获得更好的全文搜索结果
如何在MySQL中获得更好的全文搜索结果 很多互联网应用程序都提供了全文搜索功能,用户可以使用一个词或者词语片断作为查询项目来定位匹配的记录.在后台,这些程序使用在一个SELECT 查询中的LIKE语 ...
- 如何在MySQL中查询每个分组的前几名【转】
问题 在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等. 在orcale等数据库中可以使用partition语句来解决,但在mysql中就比较麻烦了.这次翻译的文章就是专门 ...
- 如何在mysql中存储音乐和图片文件
如何在mysql中存储音乐和图片文件? 果你想把二进制的数据,比如说图片文件和HTML文件,直接保存在你的MySQL数据库,那么这篇文章就是为你而写的! 我将告诉你怎样通过HTML表单来储存这些文件, ...
- 如何在MySQL中分配innodb_buffer_pool_size
如何在MySQL中分配innodb_buffer_pool_size innodb_buffer_pool_size是整个MySQL服务器最重要的变量. 1. 为什么需要innodb buffer p ...
- 关于如何在mysql中插入一条数据后,返回这条数据的id
简单的总结一下如何在mysql中出入一条数据后,返回该条数据的id ,假如之后代码需要这个id,这样做起来就变得非常方便,内容如下: <insert id="insertAndGetI ...
- MySQL中order by排序时,数据存在null咋办
order by排序是最常用的功能,但是排序有时会遇到数据为空null的情况,这样排序就会乱了,这里以MySQL为例,记录我遇到的问题和解决思路. 问题: 网页要实现table的行鼠标拖拽排序,我用A ...
- MySQL中 指定字段排序函数field()的用法
MySQL中的field()函数,可以用来对SQL中查询结果集进行指定顺序排序. 函数使用格式如下: order by (str,str1,str2,str3,str4……),str与str1,str ...
- PHP与MYSQL中UTF8 中文排序例子
1. 需要在php数组中用中文排序,但是一般使用utf8格式的文件,直接用asort排序不行.用gbk和gb2312可以.这跟几种格式的编码有关系.gbk和gb2312本身的编码就是用拼音排序的. 代 ...
- 如何在mysql中查询每个分组的前几名
问题 在工作中常会遇到将数据分组排序的问题,如在考试成绩中,找出每个班级的前五名等. 在orcale等数据库中可以使用partition 语句来解决,但在MySQL中就比较麻烦了.这次翻译的文章就是 ...
随机推荐
- PTA | 1020. 月饼 (25)
月饼是中国人在中秋佳节时吃的一种传统食品,不同地区有许多不同风味的月饼.现给定所有种类月饼的库存量.总售价.以及市场的最大需求量,请你计算可以获得的最大收益是多少. 注意:销售时允许取出一部分库存.样 ...
- Mongo日期
当通过mongo shell来插入日期类型数据时,使用new Date()和使用Date()是不一样的: > db.tianyc04.insert({mark:, mark_time:new D ...
- Linux服务器架设篇,DNS服务器(二),cache-only DNS服务器的搭建
一.理论基础 什么是cache-only服务器?即不具备自己正反解Zone的能力,仅进行缓存或转发的DNS服务器.其实它也称不上是DNS服务器.但是也是一个必备的知识点. 这种服务器只有缓存搜索结果的 ...
- 深入理解equals和hashCode关系和区别
为什么要说equals和hashCode这两个东西,一来是因为有不少小伙伴面试时被问过这个东西,二来则是因为如果了解了这两个东西的原理,那么实际的开发过程中,对效率和容错率上还是能帮上很大的忙! 直入 ...
- 【python实现卷积神经网络】卷积层Conv2D实现(带stride、padding)
关于卷积操作是如何进行的就不必多说了,结合代码一步一步来看卷积层是怎么实现的. 代码来源:https://github.com/eriklindernoren/ML-From-Scratch 先看一下 ...
- Python操作rabbitmq系列(五):根据主题分配消息
接着上一章,使用exchange_type='direct'进行消息传递.这样消息会完全匹配后发送到对应的接收端.现在我们想干这样一件事: C1获取消息中包含:orange内容的消息,并且消息是由3个 ...
- 如何练习python?有这五个游戏,实操经验就已经够了
现在学习python的人越来越多了,但仅仅只是学习理论怎么够呢,如何练习python?已经是python初学者比较要学会的技巧了! 其实,最好的实操练习,就是玩游戏. 也许你不会信,但这五个小游戏足够 ...
- c++学习day01基础知识学习
一.代码示例解析: #include <iostream> int main() { using namespace std; cout << "come up an ...
- 一些SpringBoot的初步理解
SpringBoot SpringBoot作为近几年很火的微服务框架,只需要简单的几个依赖,少量的配置,就可以使用它快速搭建一个轻量级的微服务,优点是简单.快速.大道至简,缺点是真的太单一,不适于项目 ...
- Supermarket POJ - 1456(贪心)
题目大意:n个物品,每个物品有一定的保质期d和一定的利润p,一天只能出售一个物品,问最大利润是多少? 题解:这是一个贪心的题目,有两种做法. 1 首先排序,从大到小排,然后每个物品,按保质期从后往前找 ...