Выполнить запрос по существующему результату SQL? Найти результат из подмножества результата SQL - PullRequest
0 голосов
/ 30 апреля 2018

У меня есть скрипт, который просматривает всю историю заказов. Печатание результатов занимает несколько минут, но я заметил, что я выполняю несколько достаточно схожих операторов SQL. Интересно, можно ли сделать еще один запрос к существующему результату SQL.

Например:

-- first SQL request
SELECT * FROM orders
WHERE status = 'shipped'

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

-- grabs customer's LTD sales
SELECT SUM(total) FROM orders
WHERE user = :user
AND status = 'shipped'    

-- grabs number of orders customer has made
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total != 0

-- grabs number of giveaways user has won
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total = 0

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

$stmt1 = $db->prepare("
    SELECT * FROM orders
    WHERE status = 'shipped'
");
$stmt1->execute();

foreach($stmt1 as $var) {
    $username = $var['username'];

    $stmt2 = $stmt1->workOn("
        SELECT SUM(total) FROM this
        WHERE user = :user
    ");
    $stmt2->execute(array(
        ':user' => $username
    ));
    $lifesales = $stmt2->fetchColumn();

    $stmt3 = $stmt1->workOn("
        SELECT COUNT(*) FROM this
        WHERE user = :user
        AND total != 0
    ");
    $stmt3->execute(array(
        ':user' => $username
    ));
    $totalorders = $stmt3->fetchColumn();

    $stmt4 = $stmt1->workOn("
        SELECT COUNT(*) FROM this
        WHERE user = :user
        AND total = 0
    ");
    $stmt4->execute(array(
        ':user' => $username
    ));
    $totalgaws = $stmt4->fetchColumn();

    echo "Username: ".$username;
    echo "<br/>Lifetime Sales: ".$lifesales;
    echo "<br/>Total Orders: ".$totalorders;
    echo "<br/>Total Giveaways: ".$totalgaws;
    echo "<br/><br/>";
}

Возможно ли что-то подобное? Это быстрее? Мой существующий метод медленный и уродливый, я бы хотел сделать это быстрее.

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Мы могли бы сделать один проход через таблицу, чтобы получить все три агрегата для всех пользователей:

SELECT s.user
     , SUM(s.total)        AS `ltd_sales`
     , SUM(s.total <> 0)   AS `cnt_prior_sales`
     , SUM(s.total  = 0)   AS `cnt_giveaways`
  FROM orders s
 WHERE s.status = 'shipped'
 GROUP
    BY s.user

Это будет дорого на больших сетах. Но если нам это нужно для всех заказов, для всех пользователей, это, скорее всего, будет быстрее, чем выполнение отдельных коррелированных подзапросов.

Индекс с начальным столбцом user позволит MySQL использовать индекс для операции GROUP BY. Включение в индекс столбцов status и total позволит полностью удовлетворить запрос из индекса. (С предикатом равенства в столбце status мы также можем попробовать индекс с status в качестве ведущего столбца, затем столбец user, а затем total.

Если нам нужен этот результат только для небольшой группы пользователей, например мы выбираем только первые 10 строк из первого запроса, затем выполнение отдельного запроса, скорее всего, будет быстрее. Мы просто включили бы условие WHERE s.user = :user в запрос, как в исходном коде. Но запустите только один запрос, а не три отдельных запроса.


Мы можем объединить это с первым запросом, превратив его во встроенное представление, обернув его в скобки и поместив в предложение FROM в качестве источника строки

SELECT o.*

     , t.ltd_sales
     , t.cnt_prior_sale
     , t.cnt_giveaways

  FROM orders o 

  JOIN ( 
         SELECT s.user
              , SUM(s.total)        AS `ltd_sales`
              , SUM(s.total <> 0)   AS `cnt_prior_sales`
              , SUM(s.total  = 0)   AS `cnt_giveaways`
           FROM orders s
          WHERE s.status = 'shipped'
          GROUP
             BY s.user
      ) t
   ON t.user = o.user 

WHERE o.status = 'shipped'

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


FOLLOWUP

замечая, что вопрос изменен, удаляя условие "status = 'shipped'" из подсчета всех заказов пользователем ...

Замечу, что мы можем переместить условия из предложения WHERE в условные агрегаты.

Не то чтобы все эти результаты нужны ОП, а как демонстрация ...

SELECT s.user
     , SUM(IF(s.status='shipped',s.total,0))       AS `ltd_sales_shipped`
     , SUM(IF(s.status<>'shipped',s.total,0))       AS `ltd_sales_not_shipped`

     , SUM(s.status='shipped' AND s.total <> 0)   AS `cnt_shipped_orders`
     , SUM(s.status='canceled')                   AS `cnt_canceled`

     , SUM(s.status='shipped' AND s.total  = 0)   AS `cnt_shipped_giveaways`
  FROM orders s
 GROUP
    BY s.user
0 голосов
/ 30 апреля 2018

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

https://dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html https://dev.mysql.com/doc/refman/8.0/en/create-table-select.html https://dev.mysql.com/doc/refman/8.0/en/insert-select.html

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

Для вашего конкретного случая вы можете сделать что-то вроде:

select user, (total = 0) as is_total_zero, count(*), sum(total) 
from orders
where status = 'shipped'
group by user, total = 0

Однако вам нужно будет выполнить дополнительное суммирование, чтобы получить результаты второго запроса, который дает вам суммы на пользователя, так как они будут разделены на две разные группы с различным значением is_total_zero.

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