Laravel查询构建器中如何结合LIKE与BETWEEN语句?
首先直接给你结论:不能直接把LIKE和BETWEEN结合起来实现你要的筛选。原因很简单:BETWEEN对字符串是按字典顺序比较的,不是按实际存储容量的数值大小。比如你写的BETWEEN '%120GB%' and '%10TB%',字符串'10TB'的字典序比'120GB'小,所以这个条件实际上会筛选出所有字符串在'10TB'到'120GB'之间的内容,完全不是你要的容量范围。而且LIKE是匹配模式的操作,不能直接放在BETWEEN的两端当参数用。
那该怎么解决你的问题呢?因为你的HDD列是把多个存储单元拼接成的非结构化字符串,我们得先把这些单元拆分开,提取每个单元的容量数值和单位,转换成统一单位后再做范围判断。下面以MySQL为例(从你的SQL语法看应该用的是MySQL),给你两种实用的方案:
方案1:筛选记录并返回符合条件的存储单元
这个方案不仅会找出符合条件的服务器记录,还会把每个记录里符合120GB~1TB范围的存储单元单独列出来:
SELECT sd.id, sd.HDD, GROUP_CONCAT(filtered_units.hdd_unit SEPARATOR ' ') AS matching_hdd_units FROM server_details sd -- 第一步:把HDD列拆成单个存储单元,去掉开头的"HDD" JOIN ( SELECT id, TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sd2.HDD, ' ', n.n), ' ', -1)) AS hdd_unit FROM server_details sd2 -- 生成数字序列用于拆分(这里最多支持10个单元,不够可以加更多UNION ALL) JOIN ( SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 ) n ON n.n <= LENGTH(sd2.HDD) - LENGTH(REPLACE(sd2.HDD, ' ', '')) + 1 WHERE TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sd2.HDD, ' ', n.n), ' ', -1)) != 'HDD' ) filtered_units ON sd.id = filtered_units.id -- 第二步:提取每个单元的容量数值和单位,转换为GB JOIN ( SELECT hdd_unit, CAST(REGEXP_SUBSTR(hdd_unit, '[0-9.]+') AS DECIMAL(10,2)) AS capacity_num, REGEXP_SUBSTR(hdd_unit, '[A-Z]+') AS capacity_unit FROM filtered_units ) capacity_info ON filtered_units.hdd_unit = capacity_info.hdd_unit -- 第三步:判断容量是否在120GB~1TB之间 WHERE CASE WHEN capacity_info.capacity_unit LIKE '%TB%' THEN capacity_info.capacity_num * 1024 WHEN capacity_info.capacity_unit LIKE '%GB%' THEN capacity_info.capacity_num END BETWEEN 120 AND 1024 GROUP BY sd.id, sd.HDD
方案2:仅筛选出包含符合条件存储单元的记录
如果只需要找出有至少一个符合条件存储单元的服务器记录,不需要单独列出单元,可以用EXISTS子查询:
SELECT * FROM server_details sd WHERE EXISTS ( SELECT 1 FROM ( -- 拆分HDD列成单个单元 SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sd.HDD, ' ', n.n), ' ', -1)) AS hdd_unit FROM ( SELECT 1 n UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 ) n WHERE n.n <= LENGTH(sd.HDD) - LENGTH(REPLACE(sd.HDD, ' ', '')) + 1 AND TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sd.HDD, ' ', n.n), ' ', -1)) != 'HDD' ) units JOIN ( -- 提取容量数值和单位 SELECT hdd_unit, CAST(REGEXP_SUBSTR(hdd_unit, '[0-9.]+') AS DECIMAL(10,2)) AS capacity_num, REGEXP_SUBSTR(hdd_unit, '[A-Z]+') AS capacity_unit FROM units ) capacity_info ON units.hdd_unit = capacity_info.hdd_unit -- 判断容量范围 WHERE CASE WHEN capacity_info.capacity_unit LIKE '%TB%' THEN capacity_info.capacity_num * 1024 WHEN capacity_info.capacity_unit LIKE '%GB%' THEN capacity_info.capacity_num END BETWEEN 120 AND 1024 )
额外建议
其实你现在的HDD列存储方式是很不合理的,这种拼接字符串的形式会让查询、维护都很麻烦。如果可以的话,建议重构数据库结构:新建一个关联表(比如server_hdd),每个存储单元单独存一行,包含server_id(关联服务器表)、quantity(数量,比如4)、capacity(容量,比如1024)、unit(单位,比如GB)、interface(接口,比如SATA2)这些字段。这样后续的查询、统计都会简单高效很多。
内容的提问来源于stack exchange,提问作者Amitesh Bharti

