【发布时间】: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列定义为datetime2:我已经从里到外尝试了演员阵容,达到了荒谬的长度,例如:
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()可能比你有的简单很多