使用ICriteria从Min / Max聚合函数中排除特定值

时间:2010-04-27 22:36:54

标签: .net nhibernate aggregate-functions icriteria

我有一个这样的时间表(Voyages)表:

ID      Arrival        Departure    OrderIndex
1     01/01/1753      02/10/2009        0
1     02/11/2009      02/15/2009        1
1     02/16/2009      02/19/2009        2
1     02/21/2009      01/01/1753        3

2     01/01/1753      03/01/2009        0
2     03/04/2009      03/07/2009        1
2     03/09/2009      01/01/1753        2

按照设计我将'01/01/1753'保存为默认值,如果用户没有填写捕获屏幕上的字段以及从未提供的第一个到达和最后一次出发。 我正在使用Nhibernate和Criteria,如果我想知道表中每次航行的第一次出发和最后到达,我想知道查询这些数据的最佳方式。

我的第一个想法是一个groupby(ID),然后在到达和离开时做一些Min和Max,但是`'01 / 01 / 1753'VALUE正在弄乱aronud。

...
.SetProjection(Projections.ProjectionList()
               .Add(Projections.GroupProperty("ID"), "ID")
               .Add(Projections.Min("DepartureDate"), "DepartureDate")
               .Add(Projections.Max("ArrivalDate"), "ArrivalDate")
               )
...

那么有没有办法在Min函数比较中跳过这个值(不丢失整行数据),或者有更好的方法可以做到这一点,也许利用总是指示元素正确顺序的OrderIndex ,也许订购ASC取第一个然后订购DESC并再次获得第一个,但我不太确定如何使用标准语法。

1 个答案:

答案 0 :(得分:2)

当然,最好的(也是最好的)方法是使用NULL值而不是最小datetime值。如果您已经这样做了(或者更改了您的应用程序),那么您编写的代码将按照书面形式运行。现在的方式,我保证那些伪造的价值最终会回来困扰某人(如果这是一个真正的应用程序)。也许不是你,但下一个人。当然,你也应该规范化这个表......

但无论如何。稍后会详细介绍这些内容。你问了一个具体问题。

以下是应该工作的代码(不是缺少测试 - 继续阅读)。

DateTime bogusDate = new DateTime(1753, 1, 1);

ICriteria criteria =
    session.CreateCriteria(typeof(Voyage))
    .SetProjection
    (
        Projections.ProjectionList()
        .Add
        (
            Projections.Min
            (
                Projections.Conditional
                (
                    Restrictions.Eq("Departure", bogusDate),
                    Projections.Constant(DateTime.MaxValue, NHibernateUtil.DateTime),
                    Projections.Property("Departure")
                )
            )
        )
        .Add(Projections.Max("Arrival"))
        .Add(Projections.GroupProperty("Id"))
    );

(我发送DateTime.MaxValue的唯一原因是因为NHibernate将条件的真/假结果强制为相同类型,我无法弄清楚如何将NULL引入true部分。)

该代码将此查询发送到数据库引擎(我使用的是SQL Express 2005支持的SQL Server 2005方言):

SELECT min((case when this_.Departure = ? then ? else this_.Departure end)) as y0_,
    max(this_.Arrival) as y1_,
    this_.Id as y2_
    FROM Voyages this_
    GROUP BY this_.Id;

@p0 = 1/1/1753 12:00:00 AM,
@p1 = 12/31/9999 11:59:59 PM

哪个看起来不错。现在,我说这个应该工作,因为当你插入参数并直接在引擎上运行查询时,它会给出所需的结果。然而,在我的机器上,使用NHibernate,一切都爆发了:

System.Data.SqlClient.SqlException: Incorrect syntax near '?'.

这告诉我SQL Server在CASE语句中没有相似的位置参数。 Braindead如果是真的。这可能不是2008+的问题,虽然我没有测试过 - 我特别提到SQL Server,因为我假设你使用的是自1731年1月1日以来它是允许的最小日期datetime


那么,这会留下什么问题呢?有选择。

      
  1. 修复我的第一段(理想)中提到的数据库架构。请注意,如果架构已规范化,则不必将任何 NULL值添加到数据中。
  2.   
  3. 看看你是否可以使用HQL编写查询(我不是专家,所以我甚至不能说是否可能)。
  4.   
  5. 发现这不是SQL 2008+和/或您的目标数据库引擎中的问题,而且这就是您要瞄准的目标。
  6.   
  7. 重写查询以完全绕过疯狂的值并改为使用OrderIndex列。我用SQL写了这个 - 我不知道怎么用ICriteria来写这个(如果你想要惩罚那个,请帮个忙,把时间花在#1选项上)。请注意,这不到原始查询的一半,这几乎是最佳的AFAIK:
  8. SELECT
        s.Id,
        v1.Departure,
        v2.Arrival
        FROM
        (
            SELECT DISTINCT
                Id,
                MAX(OrderIndex) AS MaxIndex,
                MIN(OrderIndex) AS MinIndex
                FROM Voyages
                GROUP BY Id
        ) s
        INNER JOIN Voyages v1 ON v1.Id = s.Id AND v1.OrderIndex = MinIndex
        INNER JOIN Voyages v2 ON v2.Id = s.Id AND v2.OrderIndex = MaxIndex