mysql distinct和order by 一起用时,order by的字段必须在select中

MySQL DISTINCT与ORDER BY使用
本文探讨了在MySQL中使用DISTINCT与ORDER BY时的注意事项,特别是ORDER BY字段必须出现在SELECT列表中,否则将导致未知错误3065。文章通过实例解释了这一规则的原因,并概述了SQL语句的执行过程。

mysql中distinct和order by 一起用时,order by的字段必须在select中

mysql 5.7.14

SELECT DISTINCT evt_tim FROM evt_etl ORDER BY evt_id ASC;

执行报错Unknown error 3065。

因为找不到3065错误的详细信息,因此做了一下测试:

- 1.SELECT DISTINCT evt_id FROM evt_etl ORDER BY evt_id ASC;

- 2.SELECT DISTINCT evt_tim,evt_id FROM evt_etl ORDER BY evt_id ASC;

- 3.SELECT evt_tim FROM evt_etl ORDER BY evt_id ASC;

1,2,3 这三条语句均执行正确。

mysql distinct和order by 一起用时,order by的字段必须在select中。

网上查了一下资料,原因总结如下:

  • 首先,在mysql中distinct 的执行顺序高于order by。
  • 第二,distinct执行时会对查询的记录进行去重,产生一张虚拟的临时表;
  • 第三,order by执行时对查询的虚拟临时表进行排序,产生新的虚拟临时表。

综合来看,如果order by的字段不在select中,执行sql语句时首先执行distinct,之后产生的虚拟临时表中没有order by的字段,所以再执行order by时会报错。

<think>我们遇到了一个SQL错误:如果指定了SELECT DISTINCT,则ORDER BY项必须出现在选择列表中。 这是因为在DISTINCT操作后,结果集可能已经去重,而ORDER BY使用的表达式如果不在SELECT列表中,数据库引擎无法确定如何排序(因为排序依据的列可能不在结果集中,或者有歧义)。 解决方案: 1. 将ORDER BY中的表达式添加到SELECT列表中(但这样会使结果中出现额外的列,可能不符合需求)。 2. 或者,如果不需要处理非数字开头的排序,可以简化排序逻辑,确保排序表达式与选择的列一致。 但是,我们的查询中ORDER BY使用了CASE表达式,这个表达式不在SELECT列表中(我们只选择了“工序”列),所以导致了错误。 根据SQL标准,当使用DISTINCT时,ORDER BY必须是SELECT列表中出现的列或者表达式(或者是基于SELECT列表中的列的表达式,但通常要求表达式完全一致)。 修改方案: 方法一:将排序表达式也加入到SELECT列表中,然后使用子查询,在外层只选择工序列并按照内层的排序列排序。 方法二:避免在ORDER BY中使用复杂的表达式,而是使用SELECT列表中已经存在的列。 这里我们使用方法一:通过子查询将排序依据的计算放到内层,然后外层使用这个排序列来排序。 原始查询中的排序逻辑: CASE WHEN 工序 LIKE '[0-9][0-9]%' OR 工序 LIKE '[0-9][^0-9]%' OR 工序 LIKE '[0-9]' THEN CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + ' ') - 1) AS INT) END 我们将其提取到SELECT列表中,并取一个别名,然后在外层查询中排序。 优化后的查询: 步骤1:内层查询计算排序用的数字,并选择工序这个数字(同时去重?注意:DISTINCT是对工序数字组合去重,但数字是从工序中提取的,所以实际上还是工序去重,但提取的数字可能不同?所以需要小心) 但是,由于同一个工序可能有不同的数字提取结果?不会,因为提取逻辑依赖于工序字段,同一个工序提取的数字是一样的。所以我们可以这样写: 修改后查询: ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9][0-9]%' OR 工序 LIKE '[0-9][^0-9]%' OR 工序 LIKE '[0-9]' THEN CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + ' ') - 1) AS INT) END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS T ORDER BY OrderKey ``` 但是这样会有一个问题:非数字开头的工序,其OrderKey为NULL,所以排序时会被排在一起(在SQL Server中NULL排在最前面?还是最后?默认情况下,SQL Server将NULL视为最小值,因此会排在最前面)。但我们的需求是非数字开头的排在最后。 另外,原始查询中,非数字开头的工序在CASE中没有被处理,所以也是NULL。所以排序行为是一致的。 但是,注意:原始查询中ORDER BY后面只有一个CASE表达式,现在我们在外层按OrderKey排序,那么非数字开头的工序(OrderKey为NULL)会排在最前面,而我们的需求是排在最后。所以我们需要调整。 在ORDER BY中可以用两个字段来控制:先按OrderKey是否为NULL排序,再按OrderKey排序。或者,我们可以在内层计算OrderKey时,对非数字开头的工序给一个很大的值,这样它们就会排在后面。但是,因为数字部分是整数,所以我们可以让非数字开头的工序的OrderKey为一个很大的整数(比如999999),这样它们就会排在后面。或者,我们可以在外层排序时使用条件排序。 更通用的做法:在排序时先判断是否为数字开头,然后分别处理。 由于原始逻辑就是NULL,而NULL在排序时默认是最小的(ASC排序在最前面),但我们希望它们排在最后。所以我们可以这样: ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9][0-9]%' OR 工序 LIKE '[0-9][^0-9]%' OR 工序 LIKE '[0-9]' THEN CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + ' ') - 1) AS INT) END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS T ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, -- 先排非NULL(0),再排NULL(1),这样非NULL在前,NULL在后 OrderKey, 工序 -- 为了稳定性,当OrderKey相同时,按工序排序 ``` 这样,非数字开头的工序会被标记为1(排在后面),数字开头的标记为0(排前面)。然后数字开头的按OrderKey升序,最后再按工序升序。 但是,注意:原始查询中,非数字开头的工序在CASE中返回NULL,所以我们的OrderKey也是NULL。上面的排序表达式:先按是否是NULL(非NULL为0,NULL为1)排序,再按OrderKey(数字部分)排序,最后按工序。 这样满足需求吗?是的,因为非数字开头的工序在第一个排序条件中都是1,所以会排在后面;而数字开头的工序都是0,然后它们再按OrderKey(数字)升序排序。 但是,我们还可以简化:因为非数字开头的工序OrderKey是NULL,所以我们也可以这样写: ```sql ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, OrderKey ``` 这样,当OrderKey非空时,按OrderKey排序;当OrderKey为空时,它们都被归为1,所以排在一起。但是,非数字开头的工序之间没有排序规则,所以我们可以再加一个工序排序。 因此,最终的查询: ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9][0-9]%' OR 工序 LIKE '[0-9][^0-9]%' OR 工序 LIKE '[0-9]' THEN CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + ' ') - 1) AS INT) END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS T ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, OrderKey, 工序 ``` 另外,我们之前优化过的逻辑(使用TRY_CAST简化数字判断)也可以整合进来: ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9]%' -- 只要以数字开头 THEN TRY_CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + 'x') - 1) AS INT) ELSE NULL END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS T ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, OrderKey, 工序 ``` 这样,我们解决了ORDER BY项不在选择列表中的问题,同时优化了数字提取的健壮性。 注意:子查询中SELECT DISTINCT 工序OrderKey,而OrderKey是从工序计算出来的,所以同一个工序只会有一个OrderKey,因此DISTINCT不会增加额外的行。 因此,这个查询应该可以正确运行。 但是,如果数据库版本不支持TRY_CAST(如SQL Server 2012及以上才支持),则可以用之前的CAST,但要确保不会转换失败。我们通过PATINDEX只提取数字部分,所以转换应该是安全的。但为了兼容旧版本,可以用: ```sql WHEN 工序 LIKE '[0-9]%' THEN CAST(LEFT(工序, CASE WHEN PATINDEX('%[^0-9]%', 工序) = 0 THEN LEN(工序) ELSE PATINDEX('%[^0-9]%', 工序) - 1 END) AS INT) ``` 不过,我们之前已经通过拼接非数字字符('x')确保PATINDEX一定会找到非数字字符,所以可以安全使用减1。因此,使用CAST也是安全的。 所以,如果不需要兼容TRY_CAST的错误处理,可以继续使用CAST。 最终代码(使用CAST,兼容性更好): ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9]%' THEN CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + 'x') - 1) AS INT) ELSE NULL END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS T ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, -- 非数字开头的排后面 OrderKey, -- 数字开头的按数字排序 工序 -- 相同数字时按工序文本排序 ``` 这样,我们解决了报错问题,并优化了查询。 解释: 1. 内层查询提取工序排序键(OrderKey),其中OrderKey是工序开头的数字部分(转为整数),非数字开头则为NULL。 2. 使用DISTINCT确保工序唯一。 3. 外层查询按三个条件排序: - 先按是否为数字开头(非数字开头排在后面,用1表示;数字开头用0表示,排在前面) - 然后数字开头的按数字大小升序排序 - 最后,数字相同或者非数字开头的多个工序,按工序文本排序(升序) 这样,既满足了原始需求,又避免了ORDER BY项不在选择列表中的错误。 注意:如果工序中有以0开头的数字,比如'012',则CAST后会变成12,排序按12排(而不是012)。这符合数字排序的常规需求。 如果希望按字符串数字排序(保留前导零的比较),那么就应该保留字符串形式,并按字符串排序。但我们的需求是按数字大小排序,所以转换为整数是正确的。 如果转换失败怎么办?由于我们只提取开头的连续数字,并用'x'确保PATINDEX一定能找到非数字字符,因此提取的部分一定是纯数字字符串,CAST不会失败。 因此,这个查询是安全的。 另外,考虑到性能,如果表很大,可以在WHERE条件中的列(线组、生产节拍)上建立索引。 如果性能仍然有问题,可以考虑在表中添加一个计算列(存储工序开头的数字)并建立索引,但需要根据实际情况调整。 现在,我们提供了修改后的查询,可以避免报错,并且满足排序需求。 但是,如果原始需求中非数字开头的工序希望按字母顺序排在后面,那么我们的第三个排序字段(工序)就实现了这个需求:当OrderKey为NULL时,它们会按工序文本排序。 因此,这个解决方案是完整的。 最后,请注意替换变量:`${线组}` `${TT}` 需要由应用程序替换为实际值。 如果是在存储过程中,可以使用参数化查询。 以上。 </think>### 解决 "SELECT DISTINCTORDER BY 项必须出现在选择列表中" 错误 这个错误发生在 SQL Server 中,当你使用 `SELECT DISTINCT` 时,`ORDER BY` 子句中使用的任何表达式都必须在 `SELECT` 列表中明确出现。以下是解决方案详细解释: #### 优化后的 SQL 查询 ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE WHEN 工序 LIKE '[0-9]%' THEN TRY_CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + 'x') - 1) AS INT) ELSE NULL END AS OrderKey FROM [dbo].[ods_TPS作业组合表] WHERE 线组 = '${线组}' AND 生产节拍 = '${TT}' ) AS SubQuery ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, -- 非数字开头排在最后 OrderKey, -- 数字开头按数值排序 工序 -- 相同数值时按原始工序文本排序 ``` #### 代码解释 1. **使用子查询解决 DISTINCT + ORDER BY 冲突**: ```sql SELECT 工序 FROM ( SELECT DISTINCT 工序, CASE ... END AS OrderKey ... ) AS SubQuery ``` - 在内层查询中,我们将 `DISTINCT` `OrderKey` 计算放在一起 - 外层查询只需选择 `工序` 并按 `OrderKey` 排序,满足 SQL Server 的要求 2. **优化数字提取逻辑**: ```sql WHEN 工序 LIKE '[0-9]%' THEN TRY_CAST(LEFT(工序, PATINDEX('%[^0-9]%', 工序 + 'x') - 1) AS INT) ``` - `LIKE '[0-9]%'` 简化了数字开头的检测 - `工序 + 'x'` 确保即使纯数字工序也能安全提取 - `TRY_CAST` 避免转换失败导致查询中断 3. **完善排序规则**: ```sql ORDER BY CASE WHEN OrderKey IS NULL THEN 1 ELSE 0 END, -- 非数字开头排最后 OrderKey, -- 数字开头按数值排序 工序 -- 最终按原始文本排序 ``` - 三级排序确保: 1. 数字开头工序 (0) 优先于非数字开头 (1) 2. 数字部分按数值升序排列 3. 相同数字时按原始工序文本排序 #### 执行效果示例 | 原始工序 | 排序顺序 | |----------|----------| | 2B | 第一组 | | 10A | 第二组 | | 5C | 第三组 | | XYZ | 第四组 | | ABC | 第五组 | *** ###
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值