Более быстрый способ извлечения средних значений из результатов запросов MySQL, вложенных в циклы - PullRequest
0 голосов
/ 02 апреля 2019

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

Я работаю в PHP 7, phpMyAdmin.

$currentDate = date('Y-m-d');
$twoYearsAgo = Date('Y-m-01', strtotime($currentDate . " -2 years + 1 month"));
$customers = "SELECT DISTINCT customer_name FROM `wo_main_alldata` WHERE ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'";
$customerResult = mysqli_query($conn, $customers);
$customerList= array();
while ($row = mysqli_fetch_array($customerResult)) {
    $customerList[] = $row[0];
}
$lanesArray = array();
foreach ($customerList as $customerName){

    $laneData = "SELECT DISTINCT type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
    FROM wo_main_alldata
    WHERE customer_name = '$customerName' 
    AND pickup_city != ''";
    $lanesResult = mysqli_query($conn, $laneData);
    while ($row2 = mysqli_fetch_array($lanesResult)){
        $equipment = $row2[0];
        $pu_city = $row2[1];
        $pu_state = $row2[2];
        $dest_city = $row2[3];
        $dest_state = $row2[4];
        $laneAverages = "SELECT AVG(proj_revenue), AVG(proj_gross_profit), COUNT(pro_num) FROM wo_main_alldata WHERE type_of_shipment = '$equipment' AND pickup_city = '$pu_city' AND pickup_state = '$pu_state' AND consignee_city = '$dest_city' AND consignee_state = '$dest_state'";
        $lanesAverageResult = mysqli_query($conn, $laneAverages);
        while ($row3 = mysqli_fetch_array($lanesAverageResult)){

        }
        mysqli_free_result($lanesAverageResult);
    }
}

Мне еще предстоит реализовать специфику для передачи данных в массив для вывода, но в настоящее время этот файл может занятьдо 10 минут для полной обработки, поскольку у некоторых клиентов за последние два года было более 2000 маршрутов доставки.

Можно ли как-нибудь ускорить выполнение этой заготовки?

РЕДАКТИРОВАТЬ: ЯПомните, что мне нужно использовать подготовленные операторы, потому что сейчас все это делается локально, и я просто пытаюсь оптимизировать время выполнения.

РЕДАКТИРОВАТЬ 2: РЕШЕНИЕ!

SELECT customer_name, type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state, AVG( proj_revenue ) , AVG( proj_gross_profit ) , COUNT( pro_num ) 
FROM wo_main_alldata
WHERE ship_date
BETWEEN  '$twoYearsAgo'
AND  '$currentDate'
AND pickup_city !=  ''
GROUP BY customer_name, type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state

Ответы [ 2 ]

4 голосов
/ 02 апреля 2019

Я действительно не решаюсь публиковать ответ, так как SQL здесь вызывает все виды дополнительной работы, которую вы делаете в PHP.Без схемы БД и некоторых примеров данных я чувствую, что просто ослеп.Например, что все эти DISTINCT вещи делают здесь, может быть, они нужны, может быть, они лишние ... У меня нет способа узнать.

То, что здесь сказано,


Это все одна и та же таблица, поэтому нет смысла снова запрашивать одни и те же данные. Возьмем, к примеру, эти 2 запроса

$customers = "SELECT DISTINCT customer_name FROM `wo_main_alldata` WHERE ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'";

$laneData = "SELECT DISTINCT type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
FROM wo_main_alldata
WHERE customer_name = '$customerName' 
AND pickup_city != ''";

. Вы можете объединить их примерно так:

$laneData = "SELECT DISTINCT
    customer_name,  -- From the first query
    type_of_shipment,
    pickup_city,
    pickup_state,
    consignee_city,
    consignee_state
FROM 
    wo_main_alldata
WHERE 
    ship_date BETWEEN '$twoYearsAgo' AND '$currentDate' 
    AND
    pickup_city != ''
";

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

Этот поиск аннулирует то, что вы использовали в первом, но (да) он у вас есть и здесь.Это аннулирует это, потому что второй запрос говорит: «Дайте мне все записи, где customer_name = что-то», так что, если имя присутствует там многократно, вы найдете все те со вторым запросом.Различия, возможно, были важны, когда они были отдельными, чтобы контролировать цикл foreach ($customerList as $customerName){.Но нам больше не нужен этот цикл.

Когда мы объединяем их, мы добавляем customer_name к выбору второго, а также добавляем биты WHERE. Затем мы можем удалить условие, чтосвязали их вместе customer_name = '$customerName', потому что он нам больше не нужен, так как он становится customer_name=customer_name, который является просто "этим рядом".

Теперь это выглядит не намного чище.Также устраняется весь этот код:

$customers = "SELECT DISTINCT customer_name FROM `wo_main_alldata` WHERE ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'";
$customerResult = mysqli_query($conn, $customers);
$customerList= array();
while ($row = mysqli_fetch_array($customerResult)) {
    $customerList[] = $row[0];
}
$lanesArray = array();
foreach ($customerList as $customerName){

    $laneData = "SELECT DISTINCT type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
    FROM wo_main_alldata
    WHERE customer_name = '$customerName' 
    AND pickup_city != ''";

Оба выбирают данные из одной таблицы, вторая (в цикле) просто берет имя пользователя из первого запроса и снова ищет его в той же таблице.,

В последнем запросе вы (опять) просто снова ссылаетесь на те же данные

 $equipment = $row2[0]; //from previous query on same table
 WHERE type_of_shipment = '$equipment' 

Все содержимое условия where напрямую связано с результатами другого запроса, поэтому выможет просто устранить это.И это оставляет нас с этим:

SELECT
   AVG(f.proj_revenue),
   AVG(f.proj_gross_profit),
   COUNT(f.pro_num)
FROM (
  SELECT DISTINCT
    customer_name,
    type_of_shipment,
    pickup_city,
    pickup_state,
    consignee_city,
    consignee_state
  FROM 
    wo_main_alldata
  WHERE 
    ship_date BETWEEN '$twoYearsAgo' AND '$currentDate' -- From the first query
    AND
    pickup_city != ''
) as f

Я не могу на самом деле проверить это, поэтому вам, возможно, придется внести некоторые коррективы, я просто чувствую свой путь, хотя логика этого.Я почти уверен, что столбцы в запросе верхнего уровня также должны быть во внутреннем подзапросе.В частности f.proj_revenue, f.proj_gross_profit и f.pro_num.Вероятно, вы получите что-то вроде Unknown column 'f.proj_gross_profit' in 'field list'

Есть несколько способов исправить это, снова присоединившись к столу.

SELECT
   AVG(m.proj_revenue),
   AVG(m.proj_gross_profit),
   COUNT(m.pro_num)
FROM 
    wo_main_alldata AS m
JOIN
    (
      SELECT DISTINCT
        id,  //<--- id is an issue
        customer_name, 
        type_of_shipment,
        pickup_city,
        pickup_state,
        consignee_city,
        consignee_state
      FROM 
        wo_main_alldata
      JOIN
      WHERE 
        ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'
        AND
        pickup_city != ''
    ) as f
ON f.id = m.id

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

SELECT DISTINCT
    customer_name, 
    type_of_shipment,
    pickup_city,
    pickup_state,
    consignee_city,
    consignee_state,
    AVG(m.proj_revenue),
    AVG(m.proj_gross_profit),
    COUNT(m.pro_num)
  FROM 
    wo_main_alldata
  WHERE 
    ship_date BETWEEN '$twoYearsAgo' AND '$currentDate'
    AND
    pickup_city != ''

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

В любом случае, если вы обнаружите, что совершаете обходы в БД для тех же данных, скорее всего, вы можете это сделатьв одном немного более сложном запросе.Заманчиво, если вы плохо знаете SQL, но в PHP справедливо просто делать простые запросы и работать с ним в PHP.

Сначала это кажется «легким» способом, но каждая часть работы, которую вы можете выполнять с БД, экономит 2 или 3 части работы в PHP.Ваш код будет меньше, легче, проще и легче для чтения.Например (при условии, что вы можете комбинировать их, как предложено), ваш код становится таким:

$lanesAverageResult = mysqli_query($conn, $laneAverages); //our new query
while ($row3 = mysqli_fetch_array($lanesAverageResult)){

}

Таким образом, мы просто исключили 25+ строк PHP с помощью немного более сложного запроса.

PS извините, это так долго ..

Надеюсь, это поможет!

2 голосов
/ 02 апреля 2019

Одна из основных причин, по которой это занимает так много времени, заключается в том, что он делает много отдельных вызовов в базу данных.Из того, что я понимаю, в некоторых случаях вы делаете более 2000+ отдельных SQL-запросов для каждого клиента.Вы хотите изучить их, используя подзапросы и / или объединения .

https://www.guru99.com/sub-queries.html

Edit Пример использованияподзапросы для сокращения выборки маршрутов доставки для каждого клиента были бы чем-то вроде этого.(Непроверенный запрос, но он дает приблизительное представление о том, как реализовать подзапросы)

SELECT AVG(final.proj_revenue), AVG(final.proj_gross_profit), COUNT(final.pro_num) FROM 
    (SELECT proj_revenue, proj_gross_profit, pro_num FROM
        (SELECT DISTINCT 
            type_of_shipment, pickup_city, pickup_state, consignee_city, consignee_state
            FROM wo_main_alldata WHERE customer_name = '$customername' AND pickup_city != ''
        ) as subquery
            WHERE type_of_shipment = subquery.type_of_shipment 
            AND pickup_city = subquery.pickup_city 
            AND pickup_state = subquery.pickup_state 
            AND consignee_city = subquery.consignee_city 
            AND consignee_state = subquery.consignee_state
    ) as final
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...