Работа с диапазонами дат (классический ASP и SQL) - PullRequest
0 голосов
/ 01 декабря 2011

Я должен реализовать решение, в котором два диапазона дат могут перекрывать друг друга. в перекрывающиеся даты я должен посчитать, сколько дней перекрывают друг друга. После того, как я узнаю о перекрывающихся днях, я смогу рассчитать итоговую цифру на основе цены за день.

Сценарий будет таким:

Клиент бронирует отель

Даты бронирования клиентов - с 17.02.2011 по 26.02.2011

Обычная цена (в течение всего года) - 01.01.2011 - 31.12.2011 (цена за день: $ 30,00)

Специальное предложение 1 с 01.01.2011 по 19.02.2011 (цена за день: 20,00 $)

Специальное предложение на 2 даты - с 17.02.2011 по 24.02.2011 (цена за день: 10,00 $)

В приведенном выше сценарии предложенный алгоритм должен выработать самое дешевое предложение, в котором диапазоны дат перекрываются, и определить цену для бронирования. Если нет специального предложения, он использует обычную цену.

Таким образом, в течение первых двух дней система должна получать цену из «специального предложения 1», поскольку это самая дешевая из доступных цен. Следующие 5 дней должны быть "Специальное предложение 2 цена", а в течение следующих 2 дней это будет обычная цена.

Буду признателен за ответы как на SQL (с использованием MS-SQL Server), так и на базовый код для получения представлений diffrenet.

Надеюсь, вопрос ясен, и я смотрю вперед, чтобы увидеть ответы.

Большое спасибо заранее

Ответы [ 4 ]

1 голос
/ 01 декабря 2011

Используя стандартный прием вспомогательной календарной таблицы , это просто случай объединений и группировки для получения лучшей цены каждый день:

SELECT C.dt, MIN(price) AS best_price
  FROM Prices P
       INNER JOIN Calendar C
          ON C.dt >= P.price_start_date 
             AND C.dt < P.price_end_date 
       INNER JOIN CustomerBooking B
          ON C.dt >= B.booking_start_date 
             AND C.dt < B.booking_end_date
 GROUP 
    BY C.dt;

Тот же запрос, что и выше, включая примеры данных с использованием CTE:

WITH Prices (price_start_date, price_end_date, narrative, price)
     AS 
     (
      SELECT CAST(start_date AS Date), CAST(end_date AS Date), narrative, price
        FROM (
              VALUES ('2011-01-01T00:00:00', '2011-12-31T00:00:00', 'Normal price', 30), 
                     ('2011-01-01T00:00:00', '2011-02-21T00:00:00', 'Special Offer 1', 20),
                     ('2011-02-19T00:00:00', '2011-02-24T00:00:00', 'Special Offer 2', 10)
             ) AS T (start_date, end_date, narrative, price)
     ), 
     CustomerBooking (booking_start_date, booking_end_date)
     AS 
     (
      SELECT CAST(start_date AS Date), CAST(end_date AS Date)
        FROM (
              VALUES ('2011-02-17T00:00:00', '2011-02-26T00:00:00')
             ) AS T (start_date, end_date)
     )     
SELECT C.dt, MIN(price) AS best_price
  FROM Prices P
       INNER JOIN Calendar C
          ON C.dt >= P.price_start_date 
             AND C.dt < P.price_end_date 
       INNER JOIN CustomerBooking B
          ON C.dt >= B.booking_start_date 
             AND C.dt < B.booking_end_date
 GROUP 
    BY C.dt;
0 голосов
/ 01 декабря 2011

Я использовал только ASP.net 4.0, но могу предложить, что SQL даст вам цену за определенную дату:

SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
FROM       Offers
WHERE     (StartDate <= '18/2/11') AND (EndDate >= '18/2/11')

Из вашего приложения вы можете построить запрос примерно так:

SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
FROM       Offers
WHERE     (StartDate <= '17/2/11') AND (EndDate >= '17/2/11');
SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
FROM       Offers
WHERE     (StartDate <= '18/2/11') AND (EndDate >= '18/2/11');
SELECT     ISNULL(MIN(PricePerDay), 0) AS MinPricePerDay
FROM       Offers
WHERE     (StartDate <= '19/2/11') AND (EndDate >= '19/2/11');

Это вернет набор данных таблиц, содержащий одно значение для минимальной цены на эту дату (в том же порядке, что и ваш запрос)

Звучит как хорошая работа для хранимой процедуры ...

0 голосов
/ 01 декабря 2011

Ваша проблема в том, что у вас есть несколько перекрывающихся периодов времени.Вам нужно либо немного ограничить проблему, либо слегка переделать данные.(Чтобы получить желаемую производительность.)

Вариант 1 - Ограничения

  • Набор данных «нормальных» цен - которые никогда не пересекаются друг с другом
  • Набор данных «специальных» цен, которые также никогда не пересекаются друг с другом
  • Каждая бронируемая дата имеет «нормальную» цену
  • Каждая бронируемая дата имеет «специальную» цену (ДАЖЕ, если значение NULL означает «без специальной цены»)

Последнее ограничение является самым странным.Но это необходимо, чтобы сделать простое соединение.При сравнении диапазонов дат много проще формировать запрос, если два набора диапазонов не имеют промежутков и не имеют перекрытий внутри них.

Это означает, что теперь вы сможете его обработать.с несколькими соединениями ...

SELECT
  CASE WHEN [sp].started > [np].started THEN [sp].started ELSE [np].started END AS [started]
  CASE WHEN [sp].expired < [np].expired THEN [sp].expired ELSE [np].expired END AS [expired]
  CASE WHEN [sp].price   < [np].price   THEN [sp].price   ELSE [np].price   END AS [price]
FROM
  normal_prices          AS [np]
LEFT JOIN
  special_prices         AS [sp]
    ON  [sp].started <  [np].expired
    AND [sp].expired >  [np].started
    AND [sp].started >= (SELECT ISNULL(MAX(started),0) FROM special_prices WHERE started <= [np].started)
    --  The third condition is an optimisation for large data-sets.
WHERE
      [np].started < @expired
  AND [np].expired > @started

-- Note: Inclusive StartDates, Exlusive EndDate
--       For example, "all of Jan" would be "2011-01-01" to "2011-02-01"

Вариант 2 - Перемодель

Этот часто самый быстрый в моем опыте;Вы увеличиваете объем используемого пространства и получаете более простой и быстрый запрос ...

Table Of Prices, stored by DAY rather than period...  
- calendar_date  
- price_code  
- price  

SELECT
  calendar_date,
  MIN(price)
FROM
  prices
WHERE
    calendar_date >= @started
AND calendar_date <  @expired

Или, если вам также нужен код_ цены ...

WITH
  ordered_prices AS
(
  SELECT
    ROW_NUMBER() OVER (PARTITION BY calendar_date ORDER BY price ASC, price_code) AS price_rank,
    *
  FROM
    prices
)
SELECT
  calendar_date,
  price_code,
  price
FROM
  ordered_prices
WHERE
    calendar_date >= @started
AND calendar_date <  @expired
0 голосов
/ 01 декабря 2011

Предположим, что на каждый день вы должны применять самую низкую цену.

create function price ( @fromDate date, @toDate date) returns money
as
begin
 declare @iterator_day date
 declare @total money
 set @total = 0
 set @iterator_day = @fromDate
 WHILE @iterator_day < = @toDate
 begin
    select @total = @total + min( price )
    from offers
    where @iterator_day  between offers.fromFay and offers.toDay
    set @iterator_day = DATEADD (day , 1 , @iterator_day )
 end
 return @total
end

тогда вы можете вызвать функцию в вашем запросе:

select 
   b.fromDay, b.toDay, dbo.price( b.fromDay, b.toDay )
from 
   booking b
...