为啥强制转换 SQL MAX(DateTime) 命令在 SSMS 中有效,但在 C# 中无效? [复制]
Posted
技术标签:
【中文标题】为啥强制转换 SQL MAX(DateTime) 命令在 SSMS 中有效,但在 C# 中无效? [复制]【英文标题】:Why does casting a SQL MAX(DateTime) command work in SSMS but not in C#? [duplicate]为什么强制转换 SQL MAX(DateTime) 命令在 SSMS 中有效,但在 C# 中无效? [复制] 【发布时间】:2022-01-09 09:42:02 【问题描述】:以下查询在 Azure 托管的 SQL 数据库表上的 MSSSMS 版本 18.1 中按预期工作:
select Name, cast(MAX(DTStamp) as Datetime) AS Last_Seen FROM dbo.MyTable GROUP BY Name
示例输出:
Fairyflosser29 2021-11-11 19:26:00.323
GoofballAnticz 2021-11-25 14:43:57.443
WeirdAlJankyVic 2021-12-01 19:30:20.341
但是,C# 中通过以下方法运行的相同 SQL 命令始终会从 DateTime 字段中减少毫秒数,尽管有强制转换以及我编写强制转换的方式。这对我来说很重要,因为我的后续查询依赖于 DateTime 字段的准确性,精确到毫秒,否则后续查询什么也不返回。
该进程调用GetLastSeenList()
,它返回一个以逗号分隔的记录列表,表示表中每个Name
的最新(按日期时间戳)记录。使用填充列表,稍后会调用另一个方法,该方法使用这些结果提取每个项目的完整行。
由于日期时间戳包含毫秒,因此后续查询必须包含毫秒值,否则即使有记录,查询也会失败。
首先,获取姓名列表以及他们最后一次出现的时间。接下来,为最后一次看到的列表中的每条记录提取整行(此处不包括在内,因为第一个查询未提供表中的日期时间 - 完整的毫秒数)。
我不太擅长 SQL,所以请帮助我了解我在这里缺少什么。
备注
1:DTStamp
列定义为datetime
2:我已经从里到外尝试了演员阵容,达到了荒谬的长度,例如:cast(MAX(cast(GPS_DTStamp as Datetime)) as Datetime) ...
[编辑]:以防万一,sql命令 sqlcmd_GetLastSeenList
与顶部的SSMS查询相同:
private static readonly string sqlcmd_GetLastSeenList =
@"select Name, cast(MAX(DTStamp) as Datetime) AS Last_Seen FROM dbo.MyTable GROUP BY Name;";
GetLastSeen:
internal static async Task<string> GetLastSeenList()
StringBuilder sb = new StringBuilder();
var list = await ReadSQL(sqlcmd_GetLastSeenList, false);
if(list != null && list.Count > 0)
// Just return a CSV list of ID's with last seen timestamps
foreach(var record in list)
sb.Append($"record[0] record[1],");
return sb.ToString().Trim(',');
读取 SQL:
private static async Task<List<object[]>> ReadSQL(string cmdText, bool isProd)
List<object[]> response = new List<object[]>();
string connect = string.Empty;
if (isProd)
connect = await SetConnectionProd(_uidProd, _p_pwd);
else
connect = await SetConnectionTest(_uidTest, _t_pwd);
try
using (SqlConnection conn = new SqlConnection(connect))
using (SqlCommand cmd = new SqlCommand(cmdText, conn))
response = await ExecuteQuery2(cmd, conn);
catch (Exception ex)
//TODO: log it
throw ex;
return response;
执行查询2:
private static async Task<List<object[]>> ExecuteQuery2(SqlCommand cmd, SqlConnection conn)
List<object[]> retval = new List<object[]>();
try
object[] myVals = new object[MAXCOLUMNCOUNT];
object[] myRow = null;
cmd.Connection.Open();
using(SqlDataReader sqlreader = cmd.ExecuteReader())
if(sqlreader.HasRows)
while(sqlreader.Read())
var count = sqlreader.GetValues(myVals);
myRow = new object[count];
Array.Copy(myVals, myRow, count);
retval.Add(myRow);
catch(Exception ex)
throw ex;
finally
cmd.Connection.Close();
return retval;
【问题讨论】:
支点:datetime
只精确到 1/3 秒,你可能想要datetime2
。您也不需要if(sqlreader.HasRows)
,因为while(sqlreader.Read())
无论如何都会告诉您。而catch... throw ex;
是错误的,它会擦除堆栈跟踪
请给出一个后续查询的例子;我想检查是否有更好的方法来做你正在做的事情(高度怀疑答案是“是”)
@Charlieface 是的,我 60 岁还在学习。我很少接触数据库,所以这真的是一个新的旅程。关键问题,就像表格一样,字段和方法已针对问题进行了清理和简化 - 无需将其与那里更糟糕的东西混为一谈,哈。 ;-)
Caius - 哦,我敢肯定有很多更好、更优雅的方法比我的可乐玻璃杯、火腿拳头的方法。最终,我根本不知道 SQL - 所以我正在做的一件事是试图找回该用户上次活动的时间,但要返回完整的数据行,而不仅仅是他们的姓名和最近的活动日期-时间戳。
那听起来你需要窗口函数,ROW_NUMBER()
可能比你有的简单很多
【参考方案1】:
毫秒就在那里。 DateTime.ToString() 默认不显示它们。例如:
var cmd = new SqlCommand("select cast('2021-11-11 19:26:00.323' as datetime) d", con);
using (var rdr = cmd.ExecuteReader())
rdr.Read();
var vals = new object[rdr.FieldCount];
rdr.GetValues(vals);
var val = (DateTime)vals[0];
Console.WriteLine($"val.GetType().Name val val.ToString("O")");
输出
DateTime 11/11/2021 7:26:00 PM 2021-11-11T19:26:00.3230000
但还要确保您没有在 datetime
和 datetime2
之间来回转换,例如这个
select cast(cast('2021-11-11 19:26:00.323' as datetime) as datetime2) d
输出
d
---------------------------
2021-11-11 19:26:00.3233333
而这种转换可能是后续查询错误比较的根源。
【讨论】:
Browne - 核心问题确实是毫秒的默认隐藏。由于我的 reader 方法用于其他查询,因此当我将行作为对象弹出到字符串中时,它使用默认的 .ToString()。谢谢!以上是关于为啥强制转换 SQL MAX(DateTime) 命令在 SSMS 中有效,但在 C# 中无效? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
TypeError:强制转换为 Unicode:需要字符串或缓冲区,找到 datetime.timedelta