Как я могу создать диапазон дат периода из таблицы MySQL, группирующей каждую общую последовательность значений в столбце - PullRequest
1 голос
/ 26 июня 2010

Моя цель - вернуть даты начала и окончания, имеющие одинаковое значение в столбце.Вот мой стол.(*) Помечены, чтобы дать вам представление о том, как я хочу получить «EndDate» для каждого аналогичного значения последовательности столбцов A & B

ID | DayDate   |  A  |  B
-----------------------------------------------
1  | 2010/07/1 | 200 |  300
2  | 2010/07/2 | 200 |  300 *
3  | 2010/07/3 | 150 |  250
4  | 2010/07/4 | 150 |  250 *
8  | 2010/07/5 | 150 |  350 *
9  | 2010/07/6 | 200 |  300
10 | 2010/07/7 | 200 |  300 *
11 | 2010/07/8 | 100 |  200
12 | 2010/07/9 | 100 |  200 *

, и я хочу получить следующую таблицу результатов изтаблица выше

| DayDate   |EndDate   |  A  |  B
-----------------------------------------------
| 2010/07/1 |2010/07/2 | 200 |  300
| 2010/07/3 |2010/07/4 | 150 |  250
| 2010/07/5 |2010/07/5 | 150 |  350
| 2010/07/6 |2010/07/7 | 200 |  300
| 2010/07/8 |2010/07/9 | 100 |  200

ОБНОВЛЕНИЕ:

Спасибо, Майк, Ваш подход, похоже, работает с вашей точки зрения, рассматривая следующую строку как ошибку.

8  | 2010/07/5 | 150 |  350 * 

Однако это не ошибка.Проблема, с которой я сталкиваюсь с данными такого типа, похожа на сценарий регистрации изменения рыночной цены с указанием даты.Реальная проблема в mycase состоит в том, чтобы выбрать все строки с начальной и конечной датой, если оба A и B совпадают во всех этих строках.Также, чтобы выбрать строки, которые находятся рядом с ранее выбранными, и так далее, чтобы в таблице не осталось никаких данных.

Я могу объяснить реальный сценарий.В отеле с номерами A и B указана стоимость номеров за каждый день, указанный в моем вопросе.Теперь отель должен получить отчет, чтобы показать календарь цен более коротким способом, используя даты начала и окончания, а не перечислять все введенные даты.Например, с 2010/07/01 по 2010/07/02 цена А составляет 200, а В - 300. Эта цена изменяется с 3 на 4, а 5-го существует другая цена только для того дня, когда Комната ВЭто цена изменена на 350. Таким образом, это рассматривается как разница в один день, поэтому даты начала и окончания совпадают.

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

Ответы [ 3 ]

4 голосов
/ 28 июня 2010

Создание связанных таблиц дает вам гораздо большую свободу запрашивать и извлекать соответствующую информацию.Вот несколько ссылок, которые могут оказаться полезными:

Вы можете начать с этих уроков:
http://dev.mysql.com/tech-resources/articles/intro-to-normalization.html
http://net.tutsplus.com/tutorials/databases/sql-for-beginners/

Здесь также есть пара вопросовstackoverflow, который может быть полезен:
Нормализация на простом английском языке
Что именно делает нормализация базы данных?

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

Сначала создайте таблицу для хранения информации о номерах отеля.Эта таблица содержит только номер комнаты и ее название, но вы можете хранить здесь другую информацию, такую ​​как тип комнаты (одноместный, двухместный, двухместный), ее вид (вид на океан, вид на океан, город, бассейн) ии так далее:

CREATE TABLE `room` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `name_UNIQUE` (`name` ASC) )
ENGINE = InnoDB;

Теперь создайте таблицу для хранения изменяющихся цен на номера.Эта таблица ссылается на таблицу room через столбец room_id.Ограничение внешнего ключа не позволяет вставлять записи в таблицу rate, которые относятся к несуществующим комнатам:

CREATE TABLE `rate` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
  `room_id` INT UNSIGNED NOT NULL,
  `date` DATE NOT NULL,
  `rate` DECIMAL(6,2) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`),
  INDEX `fk_room_rate` (`room_id` ASC),
  CONSTRAINT `fk_room_rate`
    FOREIGN KEY (`room_id` )
    REFERENCES `room` (`id` )
    ON DELETE CASCADE
    ON UPDATE CASCADE)
ENGINE = InnoDB;

Создайте две комнаты и добавьте некоторую информацию о суточной ставке для каждой комнаты:

INSERT INTO `room` (`id`, `name`) VALUES (1, 'A'), (2, 'B');

INSERT INTO `rate` (`id`, `room_id`, `date`, `rate`) VALUES
( 1, 1, '2010-07-01', 200),
( 2, 1, '2010-07-02', 200),
( 3, 1, '2010-07-03', 150),
( 4, 1, '2010-07-04', 150),
( 5, 1, '2010-07-05', 150),
( 6, 1, '2010-07-06', 200),
( 7, 1, '2010-07-07', 200),
( 8, 1, '2010-07-08', 100),
( 9, 1, '2010-07-09', 100),
(10, 2, '2010-07-01', 300),
(11, 2, '2010-07-02', 300),
(12, 2, '2010-07-03', 250),
(13, 2, '2010-07-04', 250),
(14, 2, '2010-07-05', 350),
(15, 2, '2010-07-06', 300),
(16, 2, '2010-07-07', 300),
(17, 2, '2010-07-08', 200),
(18, 2, '2010-07-09', 200);

С сохранением этой информации простой запрос SELECT с JOIN покажет вам все дневные тарифы на номера:

SELECT
    room.name,
    rate.date,
    rate.rate
FROM room
JOIN rate
ON rate.room_id = room.id;

+------+------------+--------+
| A    | 2010-07-01 | 200.00 |
| A    | 2010-07-02 | 200.00 |
| A    | 2010-07-03 | 150.00 |
| A    | 2010-07-04 | 150.00 |
| A    | 2010-07-05 | 150.00 |
| A    | 2010-07-06 | 200.00 |
| A    | 2010-07-07 | 200.00 |
| A    | 2010-07-08 | 100.00 |
| A    | 2010-07-09 | 100.00 |
| B    | 2010-07-01 | 300.00 |
| B    | 2010-07-02 | 300.00 |
| B    | 2010-07-03 | 250.00 |
| B    | 2010-07-04 | 250.00 |
| B    | 2010-07-05 | 350.00 |
| B    | 2010-07-06 | 300.00 |
| B    | 2010-07-07 | 300.00 |
| B    | 2010-07-08 | 200.00 |
| B    | 2010-07-09 | 200.00 |
+------+------------+--------+

Чтобы найти даты начала и окончания каждой комнатыОцените, вам нужен более сложный запрос:

SELECT 
    id,
    room_id,
    MIN(date) AS start_date,
    MAX(date) AS end_date,
    COUNT(*) AS days,
    rate
FROM (
    SELECT
        id,
        room_id,
        date,
        rate, 
        (
            SELECT COUNT(*)
            FROM rate AS b
            WHERE b.rate <> a.rate
            AND b.date <= a.date
            AND b.room_id = a.room_id
        ) AS grouping
    FROM rate AS a
    ORDER BY a.room_id, a.date
) c
GROUP BY rate, grouping
ORDER BY room_id, MIN(date);

+----+---------+------------+------------+------+--------+
| id | room_id | start_date | end_date   | days | rate   |
+----+---------+------------+------------+------+--------+
|  1 |       1 | 2010-07-01 | 2010-07-02 |    2 | 200.00 |
|  3 |       1 | 2010-07-03 | 2010-07-05 |    3 | 150.00 |
|  6 |       1 | 2010-07-06 | 2010-07-07 |    2 | 200.00 |
|  8 |       1 | 2010-07-08 | 2010-07-09 |    2 | 100.00 |
| 10 |       2 | 2010-07-01 | 2010-07-02 |    2 | 300.00 |
| 12 |       2 | 2010-07-03 | 2010-07-04 |    2 | 250.00 |
| 14 |       2 | 2010-07-05 | 2010-07-05 |    1 | 350.00 |
| 15 |       2 | 2010-07-06 | 2010-07-07 |    2 | 300.00 |
| 17 |       2 | 2010-07-08 | 2010-07-09 |    2 | 200.00 |
+----+---------+------------+------------+------+--------+

Хорошее объяснение методики, использованной в приведенном выше запросе, можно найти здесь:
http://www.sqlteam.com/article/detecting-runs-or-streaks-in-your-data

1 голос
/ 27 июня 2010
  • Мой общий подход состоит в том, чтобы объединить таблицу на себя, основываясь на DayDate = DayDate + 1 и значениях A или B, не равных
  • Это позволит найти даты окончания для каждого периода (гдезначение будет другим на следующий день)
  • Единственная проблема заключается в том, что не будет найдена дата окончания для последнего периода.Чтобы обойти это, я выбираю максимальную дату из таблицы и объединяю ее в мой список дат окончания
  • Как только вы определили список дат окончания, вы можете присоединить их к исходной таблице на основе концадата больше или равна исходной дате
  • Из этого окончательного списка выберите минимальную дату дня, сгруппированную по другим полям

    select
    min(DayDate) as DayDate,EndDate,A,B from
    (SELECT DayDate, A, B, min(ends.EndDate) as EndDate
    FROM yourtable
    LEFT JOIN
    (SELECT max(DayDate) as EndDate FROM yourtable UNION
    SELECT t1.DayDate as EndDate 
    FROM yourtable t1
    JOIN yourtable t2
    ON date_add(t1.DayDate, INTERVAL 1 DAY) = t2.DayDate 
    AND (t1.A<>t2.A OR t1.B<>t2.B)) ends
    ON ends.EndDate>=DayDate
    GROUP BY DayDate, A, B) x
    GROUP BY EndDate,A,B
    
0 голосов
/ 28 июня 2010

Я думаю, что нашел решение, которое дает желаемую таблицу.

SELECT  
  a.DayDate AS StartDate,  

  ( SELECT b.DayDate  
    FROM Dates AS b  
    WHERE b.DayDate > a.DayDate AND (b.B = a.B OR b.B IS NULL)  
    ORDER BY b.DayDate ASC LIMIT 1 
  ) AS StopDate,
a.A as A,
    a.B AS B

FROM Dates AS a 
WHERE Coalesce( 
               (SELECT c.B  
                FROM Dates AS c  
                WHERE c.DayDate <= a.DayDate  
                ORDER BY c.DayDate DESC LIMIT 1,1  
               ), -99999  
              ) <> a.B 
  AND a.B IS NOT NULL 
ORDER BY a.DayDate ASC; 

может генерировать следующий результат таблицы

StartDate   StopDate    A   B
2010-07-01  2010-07-02  200 300
2010-07-03  2010-07-04  150 250
2010-07-05  NULL        150 350
2010-07-06  2010-07-07  200 300
2010-07-08  2010-07-09  100 200

Но мне нужен способ заменить NULL на ту же дату, что и дата начала.

...