where id > ‘5‘ 的查询 sql 查不到 id = ‘10‘ 的数据?

一、现象

sql 复制代码
-- 注意:id 字段是 varchar 类型
highgo=# create table test(id varchar,name varchar);
CREATE TABLE
highgo=# insert into test values ('1','a'),('2','b'),('3','c'),('4','d'),('5','e'),('6','f'),('7','g'),('8','h'),('9','i'),('10','j'),('100','k'),('1000','l');
INSERT 0 12
highgo=# select * from test;
  id  | name
------+------
 1    | a
 2    | b
 3    | c
 4    | d
 5    | e
 6    | f
 7    | g
 8    | h
 9    | i
 10   | j
 100  | k
 1000 | l
(12 rows)

想查询 id 大于 '5' 的所有记录,于是执行了下面这条SQL,本以为会得到 6,7,8,9,10,100,1000,结果却只返回了 4 条:

sql 复制代码
highgo=# select * from test where id > '5';
 id | name
----+------
 6  | f
 7  | g
 8  | h
 9  | i
(4 rows)

10、100、1000 哪去了? 难道它们不比 5 大吗?更诡异的是,如果去掉引号再查,10、100、1000 又回来了!

sql 复制代码
highgo=# select * from test where id > 5;
  id  | name
------+------
 6    | f
 7    | g
 8    | h
 9    | i
 10   | j
 100  | k
 1000 | l
(7 rows)

二、原因:ASCII码字典序 vs 数值序

1. 加了引号 >,是字符串比较

  当条件写成 WHERE id > '5' 时,数据库会把它当作字符串(文本)之间的比较 。字符串比较的规则是:从左到右,逐位比较ASCII码值(也就是我们常说的字典序)。

  • 字符 '6' 的ASCII码是 54'5'53 ,所以 '6' > '5' 成立。
  • 但是,'10' 的第一个字符是 '1',ASCII码是 494953('5')小,所以比较器直接判定 '10' < '5',根本不会去看后面的 '0'
  • 同理,'100''1000' 的首字符也都是 '1',统统被判为小于 '5'

  这就像我们查字典时,所有以 "A" 开头的单词,永远排在所有以 "B" 开头的单词前面 ,不管它后面有多长。甚至,如果你查 WHERE id > '9',返回结果是空(0行),因为 '10' 的首字符 '1' 小于 '9'

2. 不加引号 > 5,是数值比较

  当条件写成 WHERE id > 5 时,数据库发现左边是 VARCHAR,右边是 INTEGER,类型不匹配。于是,数据库默默做了隐式类型转换 ,把 id 字段的内容转换成了数值类型(比如 double precision)再去比较。这时候就是实打实的数值比大小了:6 > 510 > 5100 > 51000 > 5 全都成立,所以它们都回来了。通过执行计划可观察到是数值还是字符串比较:

sql 复制代码
highgo=# explain(analyze,buffers) select * from test where id > '5';
                                            QUERY PLAN
--------------------------------------------------------------------------------------------------
 Seq Scan on test  (cost=0.00..20.12 rows=270 width=64) (actual time=0.007..0.007 rows=4 loops=1)
   Filter: ((id)::text > '5'::text)
   Rows Removed by Filter: 8
   Buffers: shared hit=1
 Planning Time: 0.022 ms
 Execution Time: 0.020 ms
(6 rows)

highgo=# explain(analyze,buffers) select * from test where id > 5;
                                            QUERY PLAN
--------------------------------------------------------------------------------------------------
 Seq Scan on test  (cost=0.00..24.18 rows=270 width=64) (actual time=0.011..0.012 rows=7 loops=1)
   Filter: ((id)::double precision > '5'::double precision)
   Rows Removed by Filter: 5
   Buffers: shared hit=1
 Planning Time: 0.043 ms
 Execution Time: 0.021 ms
(6 rows)

三、解决方案

为了避免用户因为这种规则查不到想要的数据,我们在设计和写SQL时,有几种解法:

方案一:建表时用对数据类型(最推荐)

如果 id 本身就是一个数字编号,建表时直接使用 INTEGERBIGINT ,不要用 VARCHAR。这是从根源上解决问题。

sql 复制代码
CREATE TABLE test (
    id INT,  -- 直接定义为数字类型
    name VARCHAR
);

方案二:查询时显式类型转换(适用于字段无法改类型)

如果字段已经定死了是 VARCHAR 且改不了,那就在查询时手动把字符串转成数字:

sql 复制代码
SELECT * FROM test WHERE id::INT > 5;
-- 或者
SELECT * FROM test WHERE CAST(id AS INTEGER) > 5;

注意:这种写法会走不了索引(除非建函数索引),数据量大的时候要小心性能。

方案三:如果必须按字符串存,且想按数值排序/比较

 如果字段必须存字符串(比如存的是 'A001''B002' 这种),那就老老实实用字典序,并提前给数字补零(如 '001''010''100'),保证位数一致。但显然,这不适用于现在这种纯数字场景。

相关推荐
Darling噜啦啦1 小时前
从零实战Milvus向量数据库:用RAG打造你的AI日记助手
数据库
淼澄研学2 小时前
PyTorch深度学习实战:5个核心方法从0到1构建神经网络
前端·数据库·python
吴声子夜歌2 小时前
MongoDB 4.2——分片简介
数据库·mongodb
腾渊信息科技公司2 小时前
工业时序数据存储选型实战:从PostgreSQL到TDengine,查询从15秒到200ms
数据库·postgresql·tdengine
TDengine (老段)2 小时前
TDengine Node.js 与 C# 连接器 — Web 服务与 .NET 集成
大数据·数据库·node.js·c#·.net·时序数据库·tdengine
l1t2 小时前
PostgreSQL 11–18 中的 SQL 改进:个人精选-1
数据库·sql·postgresql
逃跑的浣熊3 小时前
MySQL 性能分析报告:Page Cache 与 fsync 对读写性能的影响
数据库
eam0511233 小时前
简易AI SQL助手搭建方式
数据库·人工智能·sql
路由侠内网穿透.3 小时前
本地部署开源日志收集系统 Log Bull 并实现外部访问
运维·服务器·网络·数据库·开源