为啥强制转换 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

但还要确保您没有在 datetimedatetime2 之间来回转换,例如这个

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# 中无效? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥 LEFT(Datetime,1) 给我“J”?

SQL的类型转换

强制转换中的数据类型不一致

TypeError:强制转换为 Unicode:需要字符串或缓冲区,找到 datetime.timedelta

提示:无法将类型为“System.DateTime”的对象强制转换为类型“System.String”。

无法将类型为“System.DateTime”的对象强制转换为类型“System.String”