Как использовать Group By и self-join, чтобы вернуть минимальный, максимальный, открытый и закрытый дневной ценовой набор результатов? - PullRequest
2 голосов
/ 20 сентября 2011

решаемые

Всем привет StackOverlow!

Пока меня не было, люди оставили 2 решения (спасибо, ребята, каков протокол раздачи кармы за два рабочих решения?)

Вот решение, к которому я вернулся. он получен из ДРУГОГО решения StackOver:

Как получить первую и последнюю запись сгруппированной записи в запросе MySQL с помощью агрегатных функций?

... и моя адаптация:

SELECT
DATE_FORMAT(`DTE`, "%m/%d/%Y") AS trading_day,
MIN(`PRICE`) AS min_price,
MAX(`PRICE`) AS max_price,
SUBSTRING_INDEX( 
    GROUP_CONCAT(
        PRICE
        ORDER BY DTE ASC
        )
, ',', 1 ) AS opn_price,
SUBSTRING_INDEX( 
    GROUP_CONCAT(
        PRICE
        ORDER BY DTE DESC
        )
, ',', 1 ) AS cls_price 
FROM `CHART_DATA` 
GROUP BY trading_day
;

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

Я готов поспорить, что одно из этих трех решений имеет преимущество в производительности. Если кто-то знает внутреннюю работу MySQL и оптимизацию запросов и хочет порекомендовать «предпочтительное» решение, это будет полезно узнать в будущем.

КОНЕЦ РЕШЕН

ОБНОВЛЕНИЕ № 2

Пытаясь прийти к этому с другого направления, используя это:

http://forums.mysql.com/read.php?65,363723,363723

Я получаю:

SELECT
DATE_FORMAT(`DTE`, "%m/%d/%Y") AS trading_day,
MIN(`PRICE`) AS min_price,
MAX(`PRICE`) AS max_price,
(SELECT opn_price FROM 
    (SELECT 
    DATE_FORMAT(`DTE`, "%m/%d/%Y") AS a_day,    
    PRICE AS opn_price,
    MIN(DTE) AS opn
    FROM `CHART_DATA` 
    GROUP BY a_day
    ORDER BY opn ASC LIMIT 1) AS tblO) AS opnqt,
(SELECT cls_price FROM 
    (SELECT 
    DATE_FORMAT(`DTE`, "%m/%d/%Y") AS a_day,    
    PRICE AS cls_price,
    MIN(DTE) AS cls
    FROM `CHART_DATA` 
    GROUP BY a_day
    ORDER BY cls DESC LIMIT 1) AS tblC) AS clsqt    
FROM `CHART_DATA` cht
GROUP BY trading_day;

Это страдает от такой же дисфункции, как запрос в первом обновлении ниже; возвращаемое значение clsqt (cls_price) является последней ценой закрытия, найденной в данных. Blech.

Кроме того, мы снова попадаем в «ужасно сложное» пространство запросов, и это НЕ МОЖЕТ быть хорошим для производительности.

Но если кто-нибудь увидит исправление для значения 'clsqt', я с радостью приму его и решу проблему производительности позже. :)

КОНЕЦ ОБНОВЛЕНИЯ # 2

UPDATE

Так близко ... вот где я сегодня:

SELECT 
DATE_FORMAT(`cht1`.`DTE`, "%m/%d/%Y") AS trading_day,
MIN(`cht1`.`PRICE`) AS min_price,
MAX(`cht1`.`PRICE`) AS max_price,
    MIN(cht1.DTE) AS opn_date1,
    MIN(cht1.DTE) AS opn_date2,
    `cht2`.`PRICE` AS opn_price,
        MAX(cht1.DTE) AS cls_date1,
        MAX(cht3.DTE) AS cls_date3,
        `cht3`.`PRICE` AS cls_price
FROM `CHART_DATA` cht1
     LEFT JOIN `CHART_DATA` cht2 
     ON cht2.DTE = cht1.DTE
     LEFT JOIN `CHART_DATA` cht3 
     ON cht3.DTE = cht1.DTE
GROUP BY trading_day
HAVING opn_date1 = opn_date2
AND cls_date1 = cls_date3
;

Это корректно все перенастраивает, НО правильный 'cls_price' (он возвращает то же значение для 'cls_price', что и 'opn_price').

Однако 'cls_date1' и 'cls_date3' являются правильными значениями, поэтому я должен быть близко.

Кто-нибудь видит, кем я не являюсь?

КОНЕЦ ОБНОВЛЕНИЯ

Я занимался SO в связи с левыми и собственными соединениями ... и должен признать, что я не ворчу.

Я нашел этот «Q», который кажется очень близким к тому, что я хочу: левое соединение с условием для правой таблицы в mysql

Я пытаюсь получить дни открытия, закрытия, минимальной и максимальной цены из одной таблицы (пример данных ниже).

Мин и Макс легко:

SELECT 
DATE_FORMAT(`DTE`, "%m/%d/%Y") AS trading_day,
MIN(`PRICE`) AS min_price,
MAX(`PRICE`) AS max_price
FROM `CHART_DATA` 
GROUP BY trading_day;

Я хочу, чтобы результаты возвращались по дате, что-то вроде:

'trading_day' 'opn_price' 'min_price' 'max_price' 'cls_price'

Хорошо, так что я пробую "маленькие шаги" всего одним соединением ...

SELECT 
DATE_FORMAT(`cht1`.`DTE`, "%m/%d/%Y") AS trading_day,
MIN(`cht1`.`PRICE`) AS min_price,
MAX(`cht1`.`PRICE`) AS max_price,
`cht2`.`PRICE` AS opn_price
FROM `CHART_DATA` cht1
 LEFT JOIN `CHART_DATA` cht2 
 ON cht2.DTE = MIN(cht1.DTE)
GROUP BY trading_day;

... и я получаю сообщение «Неправильное использование групповой функции»

Конечно, удаление «GROUP BY» не поможет, так как мне нужно возвращать столбцы aggegate.

У меня есть действительно сложное решение, которое получает результаты открытия и закрытия, но не min и max - и они находятся в отдельных наборах результатов. У меня возникает ощущение, что я сделал это более сложным, чем необходимо, и что я мог бы просто понять, что происходит с самосоединениями, упомянутыми в «Q», указанном выше, что мое общее кодирование будет неизмеримо импровизировать. Но на прошедших выходных я потратил на это около 12 часов, и я чувствую себя еще более сбитым с толку.

На этом этапе приветствуются все знания, объяснения и наблюдения ...

/* SAMPLE TABLE AND DATA */

CREATE TABLE `CHART_DATA` (
  `ID` varchar(10) DEFAULT NULL,
  `DTE` datetime DEFAULT NULL,
  `PRICE` double DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

/*Data for the table `CHART_DATA` */

INSERT INTO `chart_data`
            (`id`,`dte`,`price`)
VALUES      ('1','2011-01-01 00:10:00',0.65),
            ('2','2011-01-01 06:10:00',0.92),
            ('3','2011-01-01 12:10:00',0.59),
            ('4','2011-01-01 18:10:00',0.16),
            ('5','2011-01-02 00:10:00',0.28),
            ('6','2011-01-02 06:10:00',0.12),
            ('7','2011-01-02 12:10:00',0.92),
            ('8','2011-01-02 18:10:00',0.1),
            ('9','2011-01-03 00:10:00',0.34),
            ('10','2011-01-03 06:10:00',0.79),
            ('11','2011-01-03 12:10:00',1.23),
            ('12','2011-01-03 18:10:00',1.24),
            ('13','2011-01-04 00:10:00',1.12),
            ('14','2011-01-04 06:10:00',0.8),
            ('15','2011-01-04 12:10:00',0.65),
            ('16','2011-01-04 18:10:00',0.78),
            ('17','2011-01-05 00:10:00',0.65),
            ('18','2011-01-05 06:10:00',1.19),
            ('19','2011-01-05 12:10:00',0.89),
            ('20','2011-01-05 18:10:00',1.05),
            ('21','2011-01-06 00:10:00',0.29),
            ('22','2011-01-06 06:10:00',0.43),
            ('23','2011-01-06 12:10:00',0.26),
            ('24','2011-01-06 18:10:00',0.34),
            ('25','2011-01-07 00:10:00',0.22),
            ('26','2011-01-07 06:10:00',0.37),
            ('27','2011-01-07 12:10:00',1.22),
            ('28','2011-01-07 18:10:00',1.16),
            ('29','2011-01-08 00:10:00',0.3),
            ('30','2011-01-08 06:10:00',1.17),
            ('31','2011-01-08 12:10:00',0.62),
            ('32','2011-01-08 18:10:00',0.86),
            ('33','2011-01-09 00:10:00',0.84),
            ('34','2011-01-09 06:10:00',1.11),
            ('35','2011-01-09 12:10:00',0.92),
            ('36','2011-01-09 18:10:00',1.03),
            ('37','2011-01-10 00:10:00',1.13),
            ('38','2011-01-10 06:10:00',0.58),
            ('39','2011-01-10 12:10:00',1.03),
            ('40','2011-01-10 18:10:00',0.21),
            ('41','2011-01-11 00:10:00',0.12),
            ('42','2011-01-11 06:10:00',1.01),
            ('43','2011-01-11 12:10:00',0.19),
            ('44','2011-01-11 18:10:00',1.14),
            ('45','2011-01-12 00:10:00',0.55),
            ('46','2011-01-12 06:10:00',0.75),
            ('47','2011-01-12 12:10:00',0.66),
            ('48','2011-01-12 18:10:00',1.1),
            ('49','2011-01-13 00:10:00',0.68),
            ('50','2011-01-13 06:10:00',0.3),
            ('51','2011-01-13 12:10:00',0.9),
            ('52','2011-01-13 18:10:00',0.88),
            ('53','2011-01-14 00:10:00',0.64),
            ('54','2011-01-14 06:10:00',1.06),
            ('55','2011-01-14 12:10:00',1.12),
            ('56','2011-01-14 18:10:00',0.76),
            ('57','2011-01-15 00:10:00',0.18),
            ('58','2011-01-15 06:10:00',1.08),
            ('59','2011-01-15 12:10:00',0.66),
            ('60','2011-01-15 18:10:00',0.38),
            ('61','2011-01-16 00:10:00',1),
            ('62','2011-01-16 06:10:00',1.18),
            ('63','2011-01-16 12:10:00',1.15),
            ('64','2011-01-16 18:10:00',0.58),
            ('65','2011-01-17 00:10:00',1.04),
            ('66','2011-01-17 06:10:00',0.81),
            ('67','2011-01-17 12:10:00',0.35),
            ('68','2011-01-17 18:10:00',0.91),
            ('69','2011-01-18 00:10:00',0.14),
            ('70','2011-01-18 06:10:00',0.13),
            ('71','2011-01-18 12:10:00',1.03),
            ('72','2011-01-18 18:10:00',0.16),
            ('73','2011-01-19 00:10:00',1.05),
            ('74','2011-01-19 06:10:00',1.13),
            ('75','2011-01-19 12:10:00',1.21),
            ('76','2011-01-19 18:10:00',0.34),
            ('77','2011-01-20 00:10:00',0.63),
            ('78','2011-01-20 06:10:00',0.62),
            ('79','2011-01-20 12:10:00',0.19),
            ('80','2011-01-20 18:10:00',1.21),
            ('81','2011-01-21 00:10:00',0.83),
            ('82','2011-01-21 06:10:00',0.99),
            ('83','2011-01-21 12:10:00',0.83),
            ('84','2011-01-21 18:10:00',0.21),
            ('85','2011-01-22 00:10:00',0.8),
            ('86','2011-01-22 06:10:00',0.69),
            ('87','2011-01-22 12:10:00',0.87);  

Ответы [ 2 ]

1 голос
/ 21 сентября 2011
SELECT
  a.trading_day, a.min_price, a.max_price, 
  b.price as opn_price, 
  c.price as cls_price
FROM
  (SELECT 
     DATE_FORMAT(`DTE`, "%m/%d/%Y") AS trading_day,
     MIN(`PRICE`) AS min_price,
     MAX(`PRICE`) AS max_price,
     MIN(`dte`) AS open_date,
     MAX(`dte`) AS close_date
   FROM `CHART_DATA`
   GROUP BY trading_day) a
LEFT JOIN
  `CHART_DATA` b ON b.dte = a.open_date
LEFT JOIN
  `CHART_DATA` c ON c.dte = a.close_date

Примечание : это решение может представлять некоторые проблемы, если ваша открывающая или закрывающая запись имеет точно такое же значение даты / времени, что и другая строка (т. Е. Транзакция, которая произошла сразу после открытия или непосредственно передзакрытие).Чтобы решить эту проблему, я бы предложил добавить порядковый номер, который сохраняется таким образом, чтобы гарантировать уникальность и увеличиваться по отношению к времени.Если вы сделаете это, то вы использовали бы значение последовательности вместо dte для замены open_date и close_date, которые я использовал в качестве полей соединения в моем примере.

1 голос
/ 21 сентября 2011

Итак, вы хотите:

  • Псевдоним C, соответствующий всем строкам в группе для данного дня, поэтому вы можете использовать MAX() и MIN() для строк в этомgroup.
  • Псевдоним C2, соответствующий последней строке данного дня.
  • Псевдоним C3, соответствующий строке, следующей за C2 того же дня.Если ничего не найдено, то есть C3.* равно NULL, то C2 является последним в этот день.

Это часто помечается как запрос greatest-n-per-group, и он часто появляется при переполнении стека.,Вот решение, которое я протестировал для ваших тестовых данных, но вы можете следить за тегом, который я добавил к вашему вопросу, для других решений и обсуждений.

edit: Я пропустил требование в отношении цены открытияи цена закрытия.Отредактировано следующее.

SELECT DATE_FORMAT(C.`DTE`, '%m/%d/%Y') AS trading_day, 
  MIN(C.`PRICE`) AS min_price, 
  MAX(C.`PRICE`) AS max_price, 
  Copen.`PRICE` AS opening_price,
  Cclose.`PRICE` AS closing_price 
FROM `CHART_DATA` AS C 
INNER JOIN `CHART_DATA` AS Cclose 
  ON DAY(C.`DTE`) = DAY(Cclose.`DTE`) 
LEFT OUTER JOIN `CHART_DATA` AS Cclose_later 
  ON DAY(C.`DTE`) = DAY(Cclose_later.`DTE`) AND Cclose.`DTE` < Cclose_later.`DTE`
INNER JOIN `CHART_DATA` AS Copen 
  ON DAY(C.`DTE`) = DAY(Copen.`DTE`) 
LEFT OUTER JOIN `CHART_DATA` AS Copen_earlier 
  ON DAY(C.`DTE`) = DAY(Copen_earlier.`DTE`) AND Copen.`DTE` < Copen_earlier.`DTE`
WHERE Cclose_later.`DTE` IS NULL AND Copen_earlier .`DTE` IS NULL 
GROUP BY trading_day;
...