【问题标题】:SQL Server 2005 query works in SQL Server Management Studio Express but not in Delphi 2010SQL Server 2005 查询在 SQL Server Management Studio Express 中有效,但在 Delphi 2010 中无效
【发布时间】:2013-01-09 00:35:29
【问题描述】:

我正在使用 SQL Server 2005 Management Studio Express 和 Delphi 2010。Fecha_hora = Date_Time 是 smalldatetime

我的日期格式是dd/mm/yyy

我表中的日期是这样保存的:

08/01/2013 11:22:00 a.m.

我在 Delphi 中有此查询,以了解给定一段时间内的销售额在哪个时间段更高;天/月,在这种情况下,我在 2013 年 1 月 8 日的同一天进行测试:

  conect.Q_total_hora.Active:=false;
  conect.Q_total_hora.SQL.Clear;
  conect.Q_total_hora.SQL.Add('select datepart(hh, fecha_hora) as Hora, sum(Total) as Venta, a.tipo as Tipo');
  conect.Q_total_hora.SQL.Add('from ventas v join articulos a on v.id_articulo=a.id_articulo');
  conect.Q_total_hora.SQL.Add('where tipo='+char(39)+DBLUCB_tipo.Text+char(39)+' and cast(Convert(varchar(10), fecha_hora, 112) as datetime) between'+char(39)+DateToStr(DateTimePicker_fecha1.Date)+char(39)+ 'and'+char(39)+DateToStr(DateTimePicker_fecha2.Date)+char(39));
  conect.Q_total_hora.SQL.Add('group by datepart(hh,fecha_hora), a.tipo order by datepart(hh,fecha_hora) ');
  conect.Q_total_hora.Active:=true;

我使用cast(Convert(varchar(10), fecha_hora, 112) as datetime)是因为我在互联网上发现这样我只能检索日期而没有时间检索日期之间的数据。

DateTimePickers 中,我选择08/01/2013 作为 2013 年 1 月 8 日

我用备忘录查看查询memo1.Text:=conect.Q_total_hora.Text;

我收到的查询是:

select datepart(hh, fecha_hora) as Hora, sum(Total) as Venta, a.tipo as Tipo
from ventas v join articulos a on v.id_articulo=a.id_articulo
where tipo='Burrito Grande' and cast(Convert(varchar(10), fecha_hora, 112) as datetime) between'08/01/2013'and'08/01/2013'
group by datepart(hh,fecha_hora), a.tipo order by datepart(hh,fecha_hora)

我遇到的问题是,当我在 SQL Server Mgmt Studio 中运行此查询时,它返回值,但不在 Delphi 中,如果我将 DateTimePickers 的值设置为 01/08/2013 为 2013 年 8 月 1 日,则在 Delphi 中它返回08/01/2012 的值。

据我所知(而且我知道的不多……)当我向 SQL Server 发送查询时,就像我用 SQL 编写它一样……为什么要发送日期08/01/2013 作为字符串不返回任何内容?

提前谢谢你。我不擅长数据库,大部分东西我都在网上找他们^^

【问题讨论】:

标签: delphi sql-server-2005 datetime delphi-2010


【解决方案1】:

您可以避免所有这些问题,并使用参数化查询编写更简洁的代码。

试试这个

  conect.Q_total_hora.Active:=false;
  conect.Q_total_hora.SQL.Clear;
  conect.Q_total_hora.SQL.Add('select datepart(hh, fecha_hora) as Hora, sum(Total) as Venta, a.tipo as Tipo');
  conect.Q_total_hora.SQL.Add('from ventas v join articulos a on v.id_articulo=a.id_articulo');
  conect.Q_total_hora.SQL.Add('where tipo=:tipo and fecha_hora between :fecha1 and :fecha2');
  conect.Q_total_hora.SQL.Add('group by datepart(hh,fecha_hora), a.tipo order by datepart(hh,fecha_hora) ');
  conect.Q_total_hora.Prepared:=True;
  conect.Q_total_hora.ParamByName('tipo').AsString   := DBLUCB_tipo.Text;
  conect.Q_total_hora.ParamByName('fecha1').AsDateTime := DateTimePicker_fecha1.Date;
  conect.Q_total_hora.ParamByName('fecha2').AsDateTime := DateTimePicker_fecha2.Date;
  conect.Q_total_hora.Open;

【讨论】:

  • 也许新查询会起作用,但问题要求解释为什么给定查询不起作用,而不是替代方法。
  • @RobKennedy,为什么要担心试图找到由错误做法引起的问题的解释,就像将日期时间值作为字符串传递一样?
  • 因为解释是问题所要求的。
  • 给出解决方法和建议,即使用参数而不是使用文字 sql 语句,也是一个有效的答案。
  • 在没有充分理由的情况下使用未参数化查询通常是不好的做法,正因为如此,使用 DATE/DATETIME 更是如此。
【解决方案2】:

试试这个:

conect.Q_total_hora.SQL.Add('where tipo='+char(39)+DBLUCB_tipo.Text+char(39)+' and  fecha_hora between '+char(39)+DateToStr(DateTimePicker_fecha1.Date)+char(39)+ ' and '+char(39)+DateToStr(DateTimePicker_fecha2.Date)+char(39));

应该是这样的:

where tipo='Burrito Grande' and fecha_hora between '08/01/2013' and '08/01/2013'

SQL 应该自动将日期转换为日期时间,并在您想要的地方执行。

你也可以试试这个:

where tipo='Burrito Grande' and month(fecha_hora) = 8 and year(fecha_hora) = 2013 and day(fecha_hora) = 1

【讨论】:

  • 它不起作用,我相信是因为 smalldatetime 中的时间。如果我使用where tipo='Burrito Grande' and fecha_hora between '2013-08-01 00:00:00' and '2013-08-01 23:59:00' 它会起作用,但我只想使用日期
  • 我强烈建议不要使用区域格式。甚至我都分不清你是说 8 月 1 日还是 1 月 8 日。
  • 您能否描述一下您建议的第一行代码的不同之处?它看起来和原版很相似,而且我在那些找不同的游戏中总是很糟糕。
  • @RobKennedy - 我用fecha_hora替换了cast(Convert(varchar(10), fecha_hora, 112) as datetime)
【解决方案3】:

如果您使用术语BETWEEN '08/01/2013' and '08/01/2013',您确实只会看到日期值正好为“08/01/2013”​​的记录。

如果列的类型是date,你会得到你想要的,但是因为你的列类型是datetime,所以内容'08/01/2013 11:22'不在'08/01/2013之间00:00:00' 和 '08/01/2013 00:00:00' 因此被忽略。

查询日期时间列时,使用这样的模式

...
where DateTimeCol between '<StartDate>' and '<EndDate> 23:59:59,997'

这可能看起来有点奇怪,但会包含给定日期范围内的所有日期戳。

更新:这是一个肮脏的解决方案,仅适用于数据类型 DATETIME,但不适用于 SMALLDATETIMEDATETIME2

在我看来,有两种干净的解决方案:要么使用

where (DateTimeCol>='<StartDate>') 
  and (DateTimeCol <'<The day following the EndDate>')

或将日期和时间分隔为两列。当然,只有在可能的情况下,并且不需要小于一天的范围(例如一小时左右)。

【讨论】:

  • 请注意,这也适用于参数化查询。
  • 感谢您指出这一点。事实上,我试图只回答最初的问题。参数化查询的优点已经详细解释了。
  • @alzaimar 您还可以在传递Date/DateTime 值时使用DateUtils 中的StartOfTheDay/EndOfTheDay 函数。
  • @LightBulb:感谢您的提示。
  • @LightBulb EndOfTheDay 究竟会产生什么?它是否了解不同的数据类型,以及如何根据底层数据类型是否为 SMALLDATETIME、DATETIME、DATETIME2 等来舍入或遗漏数据?请参阅sqlblog.com/blogs/aaron_bertrand/archive/2011/10/19/… 了解为什么您不想计算范围的“结束”以便可以使用 BETWEEN - 始终计算下一个范围的开始并使用小于。
【解决方案4】:

您应该避免使用BETWEEN 进行日期范围查询,并避免使用任何含糊的日期格式,例如 m/d/y 或 d/m/y。你最终得到的查询应该是这样的(你如何在你的 delphi 代码中到达那里,我将留给你):

WHERE fecha_hora >= '20130801' AND fecha_hora < '20130802'

但更好的是,正如其他人所建议的那样,您应该通过参数传递日期值,例如

WHERE fecha_hora >= @date_param AND fecha_hora < DATEADD(DAY, 1, @date_param)

(如果需要单日以外的其他情况,则使用两个参数。)

有关日期/范围查询的更多背景信息以及为什么应该这样做:

以及为什么不应该使用 CONVERT 从日期时间中删除时间:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-09
    • 2020-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-15
    相关资源
    最近更新 更多