条件聚合函数Count,翻译错误
问题描述及重现代码:
使用count()带条件时,返回的sql中 少了 or null,导致统计出来的值全部一样。
List<SubmiterData> submiterDataList = this.edoc2FreeSql.Select<ProductSuggestion>()
.Where(t => t.SubmitTime >= queryDto.StartTime)
.Where(t => t.SubmitTime <= queryDto.EndTime)
.GroupBy(t => new { t.CreateId })
.ToList(t => new SubmiterData
{
SubmiterName = t.Min(t.Value.Submiter),
Count_Submit = t.Count(t.Value.Status),
Count_Planed = t.Count(t.Value.Status == "已规划"),
Count_Adopt = t.Count(t.Value.Status == "已采纳"),
Count_Reject = t.Count(t.Value.Status == "已拒绝"),
Count_WaitProcess = t.Count(t.Value.Status == "待处理")
})
.OrderByDescending(t => t.Count_Submit)
.ToList();
翻译出来的SQL:
SELECT
min(a.`Submiter`) as1,
count(a.`Status`) as2,
count(a.`Status` = '已规划') as3,
count(a.`Status` = '已采纳') as4,
count(a.`Status` = '已拒绝') as5,
count(a.`Status` = '待处理') as6
FROM `eform_PSM_ProductSuggestion` a
WHERE (a.`SubmitTime` >= '2024-04-01 00:00:00.000') AND (a.`SubmitTime` <= '2024-04-30 00:00:00.000')
GROUP BY a.`createId`
期望 or 正确的SQL是:
SELECT
min(a.`Submiter`) as1,
count(a.`Status`) as2,
count(a.`Status` = '已规划' or null) as3,
count(a.`Status` = '已采纳'or null) as4,
count(a.`Status` = '已拒绝'or null) as5,
count(a.`Status` = '待处理'or null) as6
FROM `eform_PSM_ProductSuggestion` a
WHERE (a.`SubmitTime` >= '2024-04-01 00:00:00.000') AND (a.`SubmitTime` <= '2024-04-30 00:00:00.000')
GROUP BY a.`createId`
缺少`or null``导致统计正确,也可以使用其它写法,我查到的2种其它写法:
select count(if(age < 19, 1, null)) from sys_user // 返回 1条数据
select count(case when age < 19 then 1 end) from sys_user // 返回 1条数据
数据库版本
mysql 8.0
安装的Nuget包
freeSql.Provider.Mysql 3.2.825
.net framework/. net core? 及具体版本
.net core 3.1
翻了下源代码,没找到在那个文件改。。sum count的都没有找到对应位置QAQ。(FreeSql.Provider.MySql这个库里)
我暂时用t.Sum(t.Value.Status == "已规划")取代t.Count(t.Value.Status == "已规划")的位置可行,我的任务先完成了着
t.Sum(t.Value.Status == "已规划" ? 1 : 0),
正常应该这样写
t.Sum(t.Value.Status == "已规划" ? 1 : 0),
正常应该这样写
主要是要用Count,改用你说的这种写法就OK了。。 那这个issue可以改成建议吗?毕竟count本来就是计数的意思,用t.Count(t.Value.Status == "已规划")是最合适的
写成t.Count(t.Value.Status == "已规划" ? 1 : 0)去查询也会错误。 而用Sum表达含义时会错误
另一个就是, Sum返回的类型是decimal,这点设计上OK的,实际count的情况用sum,会导致类型转换,再写一次代码。 用Sum去干Count的会更麻烦一点(我这里就得先tolist后 再select 再tolist)
@hjkl950217 我在最初使用FreeSql时也提过类似的issue哈哈哈,API设计跟EF Core不太一样,适应就好了。
至于类型的问题,常用的整型可以直接在表达式内转换:
t => new SubmiterData
{
Count_Planed = (int)t.Sum(t.Value.Status == "已规划" ? 1 : 0),
})
double等类型若不支持(运行时会类型转换异常)的话,FreeSQL支持使用Convert函数:
t => new SubmiterData
{
Count_Planed = Convert.ToDouble(t.Sum(t.Value.Status == "已规划" ? 1 : 0)),
})
@LeaFrock
问题现在解决了,我就再套一层就行。 API设计跟ef core不一样也OK的,我也用freesql好久了(一直简单用而已)
只是说从表达含义上面, 用Sum去干Count的事很奇怪的。
t.Count(t.Value.Status == "已采纳" ? 1 : 0)也会翻译成count(case when a.Status = '待处理' then 1 else 0 end) 还是会查询错误。
那不如直接修改 t.Count(t.Value.Status == "已采纳") 这种bool。 又没歧义,又好用
只是说从表达含义上面, 用Sum去干Count的事很奇怪的。
这个要看SQL习惯,按条件数数,本质上是按条件求和问题的”子集',所以使用Sum其实也没啥问题,只是大部分人用ORM都是从EF Core及LINQ to Sql开始的,习惯了Count语法优先。
t.Sum(t.Value.Status == "已规划" ? 1 : 0) 翻译成 sum(case when a.Status = '已规划' then 1 else 0 end),C#代码上看有点不够自然,但从SQL上看不违和的。
只是说从表达含义上面, 用Sum去干Count的事很奇怪的。
这个要看SQL习惯,按条件数数,本质上是按条件求和问题的”子集',所以使用Sum其实也没啥问题,只是大部分人用ORM都是从EF Core及LINQ to Sql开始的,习惯了Count语法优先。
t.Sum(t.Value.Status == "已规划" ? 1 : 0)翻译成sum(case when a.Status = '已规划' then 1 else 0 end),C#代码上看有点不够自然,但从SQL上看不违和的。
恩,这样看也能解释得通。 那剩余的就是类型转换问题了。
用Count计数的习惯源自于linq to object, 它一直是Count计数,Sum求和,大部分人应该是先学linq to object,再学linq to sql的。 计数的类型肯定是int, 和Sum(返回值decimal)求和的情况不同
等等看叶老板咋说哇。。 用Sum有可能的歧义+类型转换 @2881099
这实际上是 mysql 使用上 和 sql 理解上的问题... 其他 db 好像都还不能 在 聚合 和 正常 Count 中 用条件表达式
讨论的是orm的API设计,不是mysql。
不过 查下来 在 MSSQL 中 下面两个等价,但是 直接使用条件判断 貌似不行
COUNT(CASE WHEN xxx = 1 THEN 1 ELSE NULL END) SUM(CASE WHEN xxx = 1 THEN 1 ELSE 0 END)
恩 ,这里得 else null才行。。但直接翻译Count(t=>t.xxx?1:0) 会翻译成 then 1 else 0,按mysql的用法是无效的。
所以我就想建议直接支持成t.Count(t.Value.Status == "已采纳")翻译COUNT(CASE WHEN xxx = 1 THEN 1 ELSE NULL END), 从ORM这边统一用法,也符合C#中linq的使用习惯
List<SubmiterData> submiterDataList = this.edoc2FreeSql.Select<ProductSuggestion>()
.Where(t => t.SubmitTime >= queryDto.StartTime)
.Where(t => t.SubmitTime <= queryDto.EndTime)
.GroupBy(t => new { t.CreateId })
.ToList(t => new
{
SubmiterName = t.Min(t.Value.Submiter),
Count_Submit = t.Count(t.Value.Status),
Count_Planed = t.Sum(t.Value.Status == "已规划" ? 1 : 0) //这里
})
.Select(t => new SubmiterData()
{
SubmiterName = t.SubmiterName,
Count_Submit = t.Count_Submit,
Count_Planed = (int)t.Count_Planed //这里得强转
Count_WaitProcess = (int)t.Count_WaitProcess
})
.OrderByDescending(t => t.Count_Submit)
.ToList();
@2881099 老板儿最终决定下哇, 我们早点把issue关闭。
现在我只能这样用,其它用法均有问题,现在这种用法就有点别扭。 我还是维持我原来的建议t.Count(t.Value.Status == "已采纳")翻译COUNT(CASE WHEN xxx = 1 THEN 1 ELSE NULL END), 这样符合C#的使用习惯,也可能符合数据库中的使用习惯吧。
原来的翻译不用调,应该只需要调整t.Count(t.Value.Status == "已采纳")这种情况。
当然,最终决定权肯定在你那里,我只能建议下
好的,我以前都是用 sum case when编写sql
只是mysql可以考虑,等我有空测试一下。
@2881099 其它的也可以考虑下,不然换数据库可能有问题QAQ。。 我们最近就在适配国产的各个数据库, 准备换成freesql, 可能连接的是 mssql 达梦 mysql等等。。 我觉得内部可以把count()的情况转换成sum的, 或是内部套一层类型强转,这样API使用上统一, 数据库执行层面功能也OK的
.GroupBy(a => a.GroupId)
.ToSql(g => new
{
cou1 = g.Count(),
cou2 = g.Count(g.Value.Nickname),
cou3 = g.Count(g.Value.Nickname == "xx"),
cou4 = g.Count(g.Value.Sort > 50),
cou5 = g.Count(g.Value.Sort > 50 || g.Value.Username == "xx"),
});
如上查询已优化 .Count 解析成 sum(case when ... then 1 else 0 end)
卧槽,这么晚,叶老板主意身体阿
这次改动的 commit