Сложные запросы используемые в MySQL

Всего лишь пару лет назад, в проектах, которые предусматривали работу с базами данных и построением статистики, основным изобилием используемых SQL-запросов, преобладало в основном множество запросов, ориентированных на стандартную выборку данных и нечасто можно было увидеть другие, которые безо всяких сомнений можно было бы отнести к “эксклюзиву”. Хотя сложность запроса и зависит от количества используемых таблиц, но если мы всего лишь возьмем и выведем данные полей трех или более таблиц имеющих стандартное объединение, то явная сложность такого запроса не выйдет за пределы стандартной.

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

Сравнение данных за две даты

Хотя данная статистика из рода задач довольно редко встречаемых, но все-таки необходимость в ее получении иногда существует. И получить такую статистику ничуть не сложнее других.

Работать мы будем с двумя таблицами, структура которых представлена ниже:

Структура таблицы products

CREATE TABLE IF NOT EXISTS `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ShopID` int(11) NOT NULL,
  `Name` varchar(150) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ;

Структура таблицы statistics

CREATE TABLE IF NOT EXISTS `statistics` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ProductID` bigint(20) NOT NULL,
  `Orders` int(11) NOT NULL,
  `Date` date NOT NULL DEFAULT '0000-00-00',
  PRIMARY KEY (`id`),
  KEY `ProductID` (`ProductID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=20 ;

Дело в том, что стандарт языка SQL допускает использование вложенных запросов везде, где разрешается использование ссылок на таблицы. Здесь вместо явно указанных таблиц, благодаря использованию псевдонимов, будут применяться результирующие таблицы вложенных запросов с имеющейся связью один – к – одному. Результатом каждой результирующей таблицы будут данные о количестве произведенных заказов некоего товара за определенную дату, полученные путем выполнения запроса на выборку данных из таблицы statistics по требуемым критериям. Иными словами мы свяжем таблицу statistics саму с собой. Пример запроса:

SELECT stat1.Name, stat1.Orders, stat1.Date, stat2.Orders, stat2.Date FROM 
(SELECT statistics.ProductID, products.Name, statistics.Orders, statistics.Date 
FROM products JOIN statistics ON products.id = statistics.ProductID WHERE 
DATE(statistics.date) = '2014-09-04') AS stat1 JOIN (SELECT statistics.ProductID, 
statistics.Orders, statistics.Date FROM statistics WHERE DATE(statistics.date) = 
'2014-09-12') AS stat2 ON stat1.ProductID = stat2.ProductID

В итоге имеем такой результат:

+------------------------+----------+------------+----------+------------+
| Name                   | Orders1  | Date1      | Orders2  | Date2      |
+------------------------+----------+------------+----------+------------+
| Процессоры Pentium II  |        1 | 2014-09-04 |        1 | 2014-09-12 |
| Процессоры Pentium III |        1 | 2014-09-04 |       10 | 2014-09-12 |
| Оптическая мышь Atech  |       10 | 2014-09-04 |        3 | 2014-09-12 |
| DVD-R                  |        2 | 2014-09-04 |        5 | 2014-09-12 |
| DVD-RW                 |       22 | 2014-09-04 |       18 | 2014-09-12 |
| Клавиатура MS 101      |        5 | 2014-09-04 |        1 | 2014-09-12 |
| SDRAM II               |       26 | 2014-09-04 |       12 | 2014-09-12 |
| Flash RAM 8Gb          |        8 | 2014-09-04 |        7 | 2014-09-12 |
| Flash RAM 4Gb          |       18 | 2014-09-04 |       30 | 2014-09-12 |
+------------------------+----------+------------+----------+------------+

Подстановка нескольких значений из другой таблицы

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

Демонстрация данного запроса будет происходить на другом примере, а не на примере сетевой игры. Это чтобы не создавать заново все необходимые таблицы. В качестве данных возьмем таблицу products из примера “сравнение данных за две даты” и создадим еще одну недостающую таблицу replace_com, структура которой представлена ниже:

CREATE TABLE IF NOT EXISTS `replace_com` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sProductID` int(11) NOT NULL,
  `rProductID` int(11) NOT NULL,
  `Date` date NOT NULL DEFAULT '0000-00-00',
  PRIMARY KEY (`id`),
  KEY `sProductID` (`sProductID`,`rProductID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;

Предположим, что у нас есть некий компьютерный салон и мы проводим модификации некоторых компьютерных составляющих, а все операции по замене комплектующих заносим в базу данных. В таблице replace_com интересующими нас полями являются: sProductID и rProductID. Где sProductID – идентификатор заменяемого модуля, а rProductID – идентификатор заменяющего модуля. Запрос, реализующий вывод данных о совершенных операциях выглядит следующим образом:

SELECT sProducts.Name AS sProduct, rProducts.Name AS rProduct, 
replace_com.Date FROM replace_com JOIN products AS sProducts ON 
sProducts.id = replace_com.sProductID JOIN products AS rProducts ON 
rProducts.id = replace_com.rProductID

Результирующая таблица данных:

+-----------------------+------------------------+------------+
| sProduct              | rProduct               | Date       |
+-----------------------+------------------------+------------+
| Процессоры Pentium II | Процессоры Pentium III | 2014-09-15 |
| Flash RAM 4Gb         | Flash RAM 8Gb          | 2014-09-17 |
| DVD-R                 | DVD-RW                 | 2014-09-18 |
+-----------------------+------------------------+------------+

Вывод статистики с накоплением по дате

Предположим, что у нас имеется склад с некими товарами. Товары периодически поступают, и нам бы хотелось видеть в отчете остатки товаров по дням. Поскольку данные о наличии товаров необходимо накапливать, то мы введем пользовательскую переменную. Но есть одно небольшое “но”. Мы не можем использовать в запросе переменные пользователя и группировку данных одновременно (вернее можем, но в итоге получим, не то, что ожидаем), но мы можем использовать вложенный запрос, вместо явно указанной таблицы. Данные в таблице будут предварительно сгруппированы по дате. И уже затем на основе этих данных мы произведем расчет статистики с накоплением.

На первом этапе требуется установить переменную и присвоить ей нулевое значение:

SET @cvalue = 0

В следующем запросе, мы созданную ранее переменную и применим:

SELECT products.Name AS Name, (@cvalue := @cvalue + Orders) as Orders, 
Date FROM (SELECT ProductID AS ProductID, SUM(Orders) AS Orders, 
DATE(date) AS Date FROM statistics WHERE ProductID = '1' GROUP BY date) 
AS statistics JOIN products ON statistics.ProductID = products.id

Итоговый отчет:

+-----------------------+--------+------------+
| Name                  | Orders | Date       |
+-----------------------+--------+------------+
| Процессоры Pentium II |      1 | 2014-09-04 |
| Процессоры Pentium II |      2 | 2014-09-12 |
| Процессоры Pentium II |      4 | 2014-09-14 |
| Процессоры Pentium II |      6 | 2014-09-15 |
+-----------------------+--------+------------+

Получить используемую в примерах базу данных можно здесь.