Исключить конкретное значение из агрегатной функции Min/Max с помощью ICriteria

У меня есть таблица расписания (вояжей):

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 не работает.

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

Итак, есть ли способ пропустить это значение при сравнении функции Min (без потери всей строки данных), или есть лучший способ сделать это, возможно, используя OrderIndex, который всегда указывает правильный порядок элементов, возможно, порядок ASC берет 1-й, а затем заказывает DESC и снова берет 1-й, но я не совсем уверен, как это сделать с синтаксисом критериев.


person sparks    schedule 27.04.2010    source источник


Ответы (1)


Лучшим (и самым разумным) способом, конечно, было бы использовать значения 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 принуждает условные результаты true/false к одному и тому же типу, и я не мог понять, как получить NULL в части true.)

Этот код отправляет этот запрос в механизм БД (я использую диалект SQL Server 2005, поддерживаемый SQL Express 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. Безмозглый, если это правда. Это может не быть проблемой в 2008+, хотя я не проверял — я упоминаю SQL Server специально, потому что я предполагаю, что вы используете его, поскольку 01.01.1753 — это минимальная дата, которую он допускает в datetime.


Итак, где же остается эта проблема? Есть варианты.

  1. Исправьте схему базы данных, как указано в моем самом первом абзаце (идеально). Обратите внимание, что вам не нужно будет разрешать какие-либо NULL значения в ваших данных, если схема нормализована.
  2. Посмотрите, можете ли вы вместо этого написать запрос, используя HQL (я не эксперт, поэтому я даже не могу сказать, возможно ли это).
  3. Выясните, что это не проблема в SQL 2008+ и/или вашем целевом механизме(ах) базы данных, и это все, на что вы когда-либо собирались нацеливаться.
  4. Перепишите запрос, чтобы полностью обойти сумасшедшие значения и вместо этого использовать столбец OrderIndex. Я написал это на SQL - я не уверен, как написать это, используя ICriteria (и если вы хотите наказания за это, сделайте себе одолжение и вместо этого потратьте время на вариант № 1). Обратите внимание, что это менее чем в два раза быстрее, чем исходный запрос, который, по AFAIK, почти оптимален:
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
person Jon Seigel    schedule 05.11.2010
comment
вау, это довольно хорошее усилие для вопроса с парой месяцев (почти мертвый) =), я считаю, что лучше выбрать первый вариант и исправить этот беспорядочный дизайн, если вы спросите меня иметь OrderIndex в дополнение к Ordered by прибытия, тогда by Departure кажутся немного запутанными, вы должны вернуться и переосмыслить, действительно ли вы хотите этого 01.01.1753 00:00:00 бездельничать, если порт с прибытием, но без отправления (и наоборот) действительно подходит для вашей модели? , с моей точки зрения, не имеет смысла, если вы, возможно, могли бы предположить дату, если пользователь этого не предоставил. - person JOBG; 12.11.2010
comment
@ Омар: Да. Если бы я возился с дизайном, я бы нормализовал таблицу отправлений и таблицу прибытия, а затем, вероятно, создал индексированное представление для рейсов. Тогда не было бы необходимости делать сумасшедший запрос на уровне приложения! - person Jon Seigel; 12.11.2010