Справка по MySQL: как работать с данными по самым последним строкам в день из большого набора данных - PullRequest
4 голосов
/ 13 июня 2009

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

Инфраструктура Фон:

  1. БД - MySQL5
  2. Мы получаем доступ к этим данным через Hibernate, используя Java
  3. Большинство этих таблиц относительно статичны, за исключением таблицы «salesperson-hourly-performance», которая содержит строку для каждого часа каждого дня, в течение которого данный продавец является активным (например, совершил или получил вызов ) с подсчетом производительности этого продавца за весь день. Учитывая количество продавцов в рассматриваемых компаниях, эта таблица может расти на 20 000+ строк в день.

Объекты данных

Я создал упрощенную версию настройки таблицы, которая включает в себя соответствующие данные. В «настоящих» таблицах содержится около 20 компаний, 300 отделов, 20 000 продавцов и миллионы записей данных о производительности продавцов.

CREATE TABLE  `so_test`.`company` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;

INSERT INTO company VALUES (7, 'CompanyXX');

CREATE TABLE  `so_test`.`division` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(45) NOT NULL,
  `campanyId` int(10) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1;

INSERT INTO division VALUES (17, 'APAC #1');

CREATE TABLE  `so_test`.`salesperson` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `divisionId` int(10) unsigned NOT NULL,
  `name` varchar(45) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=213860 DEFAULT CHARSET=latin1;

INSERT INTO salesperson VALUES (213859, 'bob jones');

CREATE TABLE  `so_test`.`salesperson_hourly_performance` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `timestamp` DATETIME NOT NULL,
  `salesPersonId` int(10) unsigned NOT NULL,
  `callsInBound` int(10) unsigned NOT NULL,
  `callsOutBound` int(10) unsigned NOT NULL,
  `issuedOrders` int(10) unsigned NOT NULL,
  `salesRevenue` decimal(10,4) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=552395 DEFAULT CHARSET=latin1;

INSERT INTO salesperson_hourly_performance VALUES (552394, '2009-05-03 22:00:00', 213859, 15, 17, 14, 10798.0478),
(551254, '2009-05-03 21:00:00', 213859, 14, 16, 13, 9802.3620),
(551115, '2009-05-03 20:00:00', 213859, 13, 14, 12, 9183.8250),
(550072, '2009-05-03 19:00:00', 213859, 11, 13, 11, 8490.8678),
(549613, '2009-05-03 18:00:00', 213859, 10, 11, 9, 7230.1125),
(549389, '2009-05-03 17:00:00', 213859, 9, 10, 8, 6486.2173),
(548861, '2009-05-03 16:00:00', 213859, 7, 9, 7, 5537.8553),
(548059, '2009-05-03 15:00:00', 213859, 6, 8, 6, 4663.8469),
(547466, '2009-05-03 14:00:00', 213859, 5, 7, 5, 4082.6388),
(546729, '2009-05-03 13:00:00', 213859, 4, 6, 4, 3057.7368),
(546611, '2009-05-03 12:00:00', 213859, 3, 5, 2, 1751.6135),
(545642, '2009-05-03 11:00:00', 213859, 2, 4, 2, 1751.6135),
(545558, '2009-05-03 10:00:00', 213859, 1, 3, 0, 0.0000),
(545072, '2009-05-03 09:00:00', 213859, 1, 2, 0, 0.0000),
(565071, '2009-05-04 13:00:00', 213859, 19, 17, 6, 4200.1710),
(575070, '2009-05-06 14:00:00', 213859, 0, 2, 1, 120.0000);

Бизнес-требования:

  1. Заполните набор веб-интерфейсов «панели мониторинга» эффективности продаж, которые предоставляют отдельный обзор производительности для компаний, подразделений и отдельных специалистов по продажам.
  2. Интерфейсы пользователя в значительной степени похожи друг на друга, за исключением набора данных: панель мониторинга «company» объединяет все данные всех продавцов в каждом из подразделений компании и выводит по одной строке для каждой компании, тогда как панель мониторинга для подразделения конкретная компания агрегирует данные о каждом из продавцов в этом отделе и по строке на подразделение.
  3. Интерфейсы позволяют пользователю выбирать диапазон дат для панели мониторинга отчета и сортировать по любому из столбцов. Отображаемые столбцы:

    (Company | Division | Sales Person) Имя, Всего выпущенных заказов, Общий доход от продаж, Общее количество входящих вызовов, Общее количество исходящих вызовов.

Моя проблема / просьба к SO:

«Унаследованный» подход (который был позорным, но в некотором смысле довольно приемлемым, когда выходной был для ежедневного журнала) заключался в программной итерации данных о производительности для каждого из соответствующих объектов (например, каждого продавца в подразделение в компании), найдите «последний» в каждый из указанных дней в указанном диапазоне дат и суммируйте данные. Однако, учитывая массивный набор данных и необходимость представления этих данных «вживую» в пользовательском интерфейсе, мне нужны рекомендации / примеры того, как построить эффективные запросы SQL к этому набору данных, которые позволят разбивать на страницы и сортировать.

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

  2. Запрос, который выполняет запрос № 1 для ряда продавцов (например, для всех продавцов в данной компании) с поддержкой нумерации страниц и заказов в определенном столбце?

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

Большое спасибо ТАК Боги SQL!

UPDATE:

Добавлены недостающие ключи от SalesPerson -> Division & from Division -> company. Также исправлен тип данных «timestamp», который будет DATETIME вместо VARCHAR.

Ответы [ 2 ]

1 голос
/ 13 июня 2009

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

select
    c.name as company,
    d.name as division,
    s.name as salesperson,
    sum(h.callsinbound) as callsinboundsum,
    sum(h.callsoutbound) as callsoutboundsum,
    sum(h.issuedorders) as issuedorderssum,
    sum(h.revenue) as revenuesum
from
    sales_history_performance h
    inner join
        (select
            th.salespersonid,
            date(th.timestamp) as my_date,
            max(th.timestamp) as max_time
        from
            sales_history_performance th
            inner join salesperson ts on
                th.salespersonid = ts.id
        where
            th.timestamp between '5/1/2009' and '5/3/2009' --inclusive in MySQL
        group by
            th.salespersonid,
            date(th.timestamp)
        ) t on
      h.salespersonid = t.salespersonid
      and h.timestamp = t.max_time
    inner join salesperson s on
        h.salespersonid = s.id
    inner join division d on
        s.divisionid = d.id
    inner join company c on
        d.companyid = c.id
group by
    c.name,
    d.name,
    s.name
order by 1,2,3

Вы можете отредактировать закомментированную строку and sp.name like '%', добавив в нее необходимый вам торговый агент.

Таким образом, это происходит следующим образом: он выходит и строит таблицу из верхней отметки времени за каждый день. Если идентификатор в sales_history_performance надежно больше для более поздних записей, используйте его, так как у вас меньше шансов получить дубликаты. Во всяком случае, затем он присоединяет это к таблице, суммируя все столбцы метрики для каждого продавца. Вы можете убрать продавца из внешнего запроса, если хотите получить номер компании. Как это было, он вернет всех продавцов.

Обновление : я добавил в компанию и подразделение. Это довольно общий запрос. Если вы хотите ограничить подразделение / компанию / продавца, вы можете сделать это в предложении WHERE внешнего запроса, хотя, возможно, вы сможете добиться некоторого прироста производительности, выполняя его во внутреннем запросе - это всего лишь немного сложнее в обслуживании.

0 голосов
/ 13 июня 2009

с учетом того, что для каждого дня строка, используемая для суммы, является последней по дате для> того дня, для этого продавца)

Эту информацию трудно проглотить. Мне было интересно, говорили ли вы, что сумма за день хранится в таблице salesperson_hourly_performance, смешивая итоги за день и почасовые сводки в одной таблице.

В вашем примере нет никакого отношения к подразделению и компании. Но чтобы разбить продажи на человека в день для данного диапазона дат:

select s.name,substring(timestamp,1,11) as day,sum(callsInBound),sum(callsOutBound),sum(issuedOrders),sum(salesRevenue) 
from salesperson_hourly_performance facts , salesperson s  
where facts.salesPersonId = s.id and  timestamp >= "2009-05-03 00:00:00" and timestamp < "2009-05-07 00:00:00" 
group by s.name,day 
order by day asc;
+-----------+-------------+-------------------+--------------------+-------------------+-------------------+
| name      | day         | sum(callsInBound) | sum(callsOutBound) | sum(issuedOrders) | sum(salesRevenue) |
+-----------+-------------+-------------------+--------------------+-------------------+-------------------+
| bob jones | 2009-05-03  |               101 |                125 |                93 |        72836.7372 |
| bob jones | 2009-05-04  |                19 |                 17 |                 6 |         4200.7100 |
| bob jones | 2009-05-06  |                 0 |                  2 |                 1 |          120.0000 |
+-----------+-------------+-------------------+--------------------+-------------------+-------------------+

Хранение отметки времени в качестве фактической отметки времени / типа даты / времени даст вам более гибкую возможность работать с датами и временем. Есть функции mysql для преобразования строк в datetime, которые, вероятно, могут помочь вашим запросам, если это действительно столбец varchar

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

если вам нужна строка с самой большой датой в день, используйте, например,

SELECT   p.name,
         Substring(TIMESTAMP,1,11) AS DAY,
         Sum(callsinbound),
         Sum(callsoutbound),
         Sum(issuedorders),
         Sum(salesrevenue)
FROM     (SELECT   sh.salespersonid,
                   Substring(sh.TIMESTAMP,1,11) AS DAY,
                   Max(TIMESTAMP)               AS max_ts
          FROM     salesperson_hourly_performance sh
          GROUP BY sh.salespersonid,
                   DAY) t
         INNER JOIN salesperson_hourly_performance shp
           ON t.salespersonid = shp.salespersonid
              AND t.max_ts = shp.TIMESTAMP
         INNER JOIN salesperson p
           ON shp.salespersonid = p.id
GROUP BY p.name,
         DAY; 

Добавьте предложения where, где вам нужно, например, согласно первому запросу

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...