累积总和作为从属子查询

时间:2018-12-05 07:06:09

标签: mysql sql

我有一个名为transactions的表,其中包含卖方及其交易:销售,废物以及何时收到产品。结构基本上如下:

seller_id    transaction_date    quantity    reason    product    unit_price
---------    ----------------    --------    ------    -------    ----------
1            2018-01-01                10    import          1         100.0
1            2018-01-01                -5    sale            1         100.0
1            2018-01-01                -1    waste           1         100.0
2            2018-01-01                -3    sale            4          95.5

我需要每个卖方的每日摘要,包括其销售,浪费和开始存货的价值。问题在于,初始库存是直到给定日期为止的累计数量总和(也包括给定日期的进口量)。我有以下查询:

SELECT
    t.seller_id,
    t.transaction_date,
    t.SUM(quantity * unit_price) as amount,
    t.reason as reason,
    (
        SELECT SUM(unit_price * quantity) FROM transactions
        WHERE seller_id = t.seller_id
            AND (transaction_date <= t.transaction_date)
            AND (
                transaction_date < t.transaction_date
                OR reason = 'import'
            ) 
    ) as opening_balance
FROM transactions t
GROUP BY 
    t.transaction_date,
    t.seller_id
    t.reason

查询有效,我得到了所需的结果。但是,即使在为外部查询和子查询创建索引之后,它也花费了太多时间(约30秒),因为opening_balance查询是一个依赖子查询,需要对每一行进行一次又一次的计算。 / p>

如何优化或重写此查询?

编辑:子查询有一个缺少WHERE条件的小错误,我对其进行了修复,但问题的实质是相同的。我用示例数据创建了一个小提琴来玩:

https://www.db-fiddle.com/f/ma7MhufseHxEXLfxhCtGbZ/2

3 个答案:

答案 0 :(得分:2)

使用用户定义的变量的以下方法可能比使用“相关子查询”更有效。在您的情况下,使用了一个temp变量来说明计算逻辑,该变量也将被输出。您可以忽略它。

您可以尝试以下查询(如果需要,可以添加更多说明):

查询

SELECT dt.reason,
       dt.amount,
       @bal := CASE
                 WHEN dt.reason = 'import'
                      AND @sid <> dt.seller_id THEN dt.amount
                 WHEN dt.reason = 'import' THEN @bal + @temp + dt.amount
                 WHEN @sid = 0
                       OR @sid = dt.seller_id THEN @bal
                 ELSE 0
               end                AS opening_balance,
       @temp := CASE
                  WHEN dt.reason <> 'import'
                       AND @sid = dt.seller_id
                       AND @td = dt.transaction_date THEN @temp + dt.amount
                  ELSE 0
                end               AS temp,
       @sid := dt.seller_id       AS seller_id,
       @td := dt.transaction_date AS transaction_date
FROM   (SELECT seller_id,
               transaction_date,
               reason,
               Sum(quantity * unit_price) AS amount
        FROM   transactions
        WHERE  seller_id IS NOT NULL
        GROUP  BY seller_id,
                  transaction_date,
                  reason
        ORDER  BY seller_id,
                  transaction_date,
                  Field(reason, 'import', 'sale', 'waste')) AS dt
       CROSS JOIN (SELECT @sid := 0,
                          @td := '',
                          @bal := 0,
                          @temp := 0) AS user_vars;

结果(请注意,我先按seller_id的顺序排序,然后按transaction_date的顺序排序)

| reason | amount | opening_balance | temp  | seller_id | transaction_date |
| ------ | ------ | --------------- | ----- | --------- | ---------------- |
| import | 1250   | 1250            | 0     | 1         | 2018-12-01       |
| sale   | -850   | 1250            | -850  | 1         | 2018-12-01       |
| waste  | -100   | 1250            | -950  | 1         | 2018-12-01       |
| import | 950    | 1250            | 0     | 1         | 2018-12-02       |
| sale   | -650   | 1250            | -650  | 1         | 2018-12-02       |
| waste  | -450   | 1250            | -1100 | 1         | 2018-12-02       |
| import | 2000   | 2000            | 0     | 2         | 2018-12-01       |
| sale   | -1200  | 2000            | -1200 | 2         | 2018-12-01       |
| waste  | -250   | 2000            | -1450 | 2         | 2018-12-01       |
| import | 750    | 1300            | 0     | 2         | 2018-12-02       |
| sale   | -600   | 1300            | -600  | 2         | 2018-12-02       |
| waste  | -450   | 1300            | -1050 | 2         | 2018-12-02       |

View on DB Fiddle

答案 1 :(得分:1)

这样的事情吗?

SELECT s.* ,@balance:=@balance+(s.quantity*s.unit_price) AS opening_balance FROM (
    SELECT t.* FROM transactions t
    ORDER BY t.seller_id,t.transaction_date,t.reason
) s
CROSS JOIN ( SELECT @balance:=0) AS INIT
GROUP BY s.transaction_date, s.seller_id, s.reason;

示例

MariaDB [test]> select * from transactions;
+----+-----------+------------------+----------+------------+--------+
| id | seller_id | transaction_date | quantity | unit_price | reason |
+----+-----------+------------------+----------+------------+--------+
|  1 |         1 | 2018-01-01       |       10 |        100 | import |
|  2 |         1 | 2018-01-01       |       -5 |        100 | sale   |
|  3 |         1 | 2018-01-01       |       -1 |        100 | waste  |
|  4 |         2 | 2018-01-01       |       -3 |       99.5 | sale   |
+----+-----------+------------------+----------+------------+--------+
4 rows in set (0.000 sec)

MariaDB [test]> SELECT s.* ,@balance:=@balance+(s.quantity*s.unit_price) AS opening_balance FROM (
    ->     SELECT t.* FROM transactions t
    ->     ORDER BY t.seller_id,t.transaction_date,t.reason
    -> ) s
    -> CROSS JOIN ( SELECT @balance:=0) AS INIT
    -> GROUP BY s.transaction_date, s.seller_id, s.reason;
+----+-----------+------------------+----------+------------+--------+-----------------+
| id | seller_id | transaction_date | quantity | unit_price | reason | opening_balance |
+----+-----------+------------------+----------+------------+--------+-----------------+
|  1 |         1 | 2018-01-01       |       10 |        100 | import |            1000 |
|  2 |         1 | 2018-01-01       |       -5 |        100 | sale   |             500 |
|  3 |         1 | 2018-01-01       |       -1 |        100 | waste  |             400 |
|  4 |         2 | 2018-01-01       |       -3 |       99.5 | sale   |           101.5 |
+----+-----------+------------------+----------+------------+--------+-----------------+
4 rows in set (0.001 sec)

MariaDB [test]>

答案 2 :(得分:0)

SELECT
    t.seller_id,
    t.transaction_date,
    SUM(quantity) as amount,
    t.reason as reason,
    quantityImport
FROM transaction t
inner join
(
 select sum(ifnull(quantityImport,0)) quantityImport,p.transaction_date,p.seller_id from 
 ( /* subquery get all the date and seller distinct row */
  select transaction_date  ,seller_id ,reason
  from transaction 
  group by seller_id, transaction_date
 )
 as p
 left join 
 (  /* subquery get all the date and seller and the import quantity */
   select sum(quantity) quantityImport,transaction_date  ,seller_id
   from transaction
   where reason='Import'
   group by seller_id, transaction_date
 ) as n
 on
   p.seller_id=n.seller_id
 and
   p.transaction_date>=n.transaction_date
 group by
   p.seller_id,p.transaction_date
) as q
where
  t.seller_id=q.seller_id
and
  t.transaction_date=q.transaction_date
GROUP BY 
    t.transaction_date,
    t.seller_id,
    t.reason;