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码是 49 。49 比 53('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 > 5、10 > 5、100 > 5、1000 > 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 本身就是一个数字编号,建表时直接使用 INTEGER 或 BIGINT ,不要用 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'),保证位数一致。但显然,这不适用于现在这种纯数字场景。

相关推荐
老纪的技术唠嗑局43 分钟前
把 seekdb 塞进 iOS App:让混合检索嵌入手机
数据库
ClouGence2 小时前
开源数据库管理工具 CloudDM 新版本发布,支持 OpenSearch、IoTDB 数据源
数据库·开源
徐小黑ACG2 小时前
Redis-基础
数据库·redis·缓存
火山引擎开发者社区2 小时前
案例实践 | 让 Agent 读懂业务口径:ContextSearch 在广告数据查询中的实践
数据库
10年前端老司机3 小时前
Milvus 8种检索能力实践
数据库·python·agent
Decay__4 小时前
14_MySQL 高可用与容灾架构 —— MGR / InnoDB Cluster / Keepalived+VIP / 双主 / 读写分离 / 两地三中心
运维·数据库·sql·mysql·架构
freejackman4 小时前
【实战三】撕开脱敏盲区:深度解析 CASE WHEN 与窗口函数血缘
大数据·数据库·sql
隔窗听雨眠6 小时前
云数仓向PostgreSQL生态延伸的技术动因与工程代价
postgresql·数据库架构·云数仓
codigger6 小时前
线上数据库连接池打满:从应急止损到架构根治的完整方案
数据库·sql·程序员
倔强的石头_6 小时前
集中式与分布式怎么选?我把手头11套系统重新过了一遍秤
数据库