0

This is sample data in my table

id_item | qty | t_in | t_out | created_at
 1         5     1       0    2018-07-05 10:41:00
 1         5     1       0    2018-08-03 10:41:00
 1         5     0       1    2018-08-05 10:41:00
 1         5     1       0    2018-09-05 10:41:00
 1         5     1       0    2018-09-20 10:41:00
 1         5     0       1    2018-10-31 10:41:00

My expected result will be

id_item | qty | year | month
 1         5    2018   07
 1         5    2018   08
 1         15   2018   09
 1         10   2018   10

What i have tried it works, but not desired output when want to group by montly

$date = '2018-10-31'
$test = Model::whereDate('created_at','<=',$date)->select(DB::raw('(SUM(CASE T_IN WHEN 1 THEN qty ELSE qty * - 1 END)) as total'))->groupBy('id_item')->get();

Raw queries to get the quantity for one month

Select id_item, 
       (SUM(CASE T_IN WHEN 1 THEN qty ELSE qty * - 1 END)) as total 
from transactions 
where DATE(created_at) <= 2018-10-31 
group by id_item

Worst case

$last_day_of_month = [//list of last day of each month]
//then using loop to get qty of each month refer to the raw queries above

From the query above, i only able to get one line of record. I also tried to group by month and year but incorrect result caused of the date condition. How can i include multiple <= $date condition and group it accordingly to get desired output?

Any idea or is that possible to make it? Thanks.

Crazy
  • 847
  • 1
  • 18
  • 41
  • add `->groupBy('YEAR(created_at)')->groupBy('MONTH(created_at)')` – Madhur Bhaiya Nov 07 '18 at 07:23
  • check this question https://stackoverflow.com/questions/937652/mysql-select-sum-group-by-date – Saurabh Gujarani Nov 07 '18 at 07:24
  • What's your PRIMARY KEY? – Strawberry Nov 07 '18 at 07:26
  • You can simplify your `SUM` to `SUM(qty * t_in - qty * t_out)` – Nick Nov 07 '18 at 07:28
  • @MadhurBhaiya I just want to display this year and qty for 12 month of this year. If using group by then it will show all different month and year. I am using <= each month to get past data which included other month and year as well. – Crazy Nov 07 '18 at 07:43
  • @Crazy try to prepare a raw query first; you shall understand. – Madhur Bhaiya Nov 07 '18 at 07:43
  • @Strawberry primary key just a simple increment id column, i didnt show it heree. – Crazy Nov 07 '18 at 07:44
  • @MadhurBhaiya Yes, i am trying to get the desired output. This is what i can get now. The worst case is looping 12 times with every last day of each month to get desired output which i am trying to avoid. – Crazy Nov 07 '18 at 07:47
  • @Crazy edit the question to add raw query (without eloquent syntax) that you would be using. There is no need of looping. Depending on the raw query you give, I can try to fix it. – Madhur Bhaiya Nov 07 '18 at 07:48
  • @MadhurBhaiya I don't have the exact query for my expected output, still working on it. I just have the query which only can get the total qty for <= each month which return only 1 record for the month passed in only. – Crazy Nov 07 '18 at 08:00
  • @Crazy are you using MySQL version 8.0.2 and above ? Do you have window functions available ? – Madhur Bhaiya Nov 07 '18 at 08:04
  • `qty` for August month should be 5. As `in` and `out` are balancing each other. – Madhur Bhaiya Nov 07 '18 at 08:07
  • @MadhurBhaiya sorry, my mistake. I am using phpMyAdmin MariaDB v10.1.31, i think syntax same as MySQL. I able to do it if you could guide me or provide similar solution for the output i am expected. I can explain it further if my question unclear. – Crazy Nov 07 '18 at 08:15
  • @Crazy posted the answer. It is in raw mysql query. Writing it using your ORM will be another battle though! – Madhur Bhaiya Nov 07 '18 at 09:04

2 Answers2

1

It is a Rolling Sum problem. In newer versions of Mariadb/MySQL, it can be solved using Window Functions with Frames. However, you don't have that available.

We can rather solve this using user-defined variables. In a Derived table, we first determine the total change in qty for a month. Then, we use this result-set to calculate "final qty" at the end of a month, by adding up the previous month (row)'s qty with current month (row)'s qty_change.

I have also extended the query to consider the cases when there are more than one id_item values.

Try the following Raw query:

SELECT 
  @roll_qty := CASE WHEN @id_itm = dt.id_item 
                    THEN @roll_qty + dt.qty_change 
                    ELSE dt.qty_change  
               END AS qty, 
  @id_itm := dt.id_item AS id_item, 
  dt.year, 
  dt.month 
FROM 
(
 SELECT 
   t.id_item, 
   SUM(t.qty * t.t_in - t.qty * t.t_out) AS qty_change, 
   YEAR(t.created_at) AS `year`, 
   LPAD(MONTH(t.created_at), 2, '0') AS `month`
 FROM your_table AS t 
 GROUP BY t.id_item, `year`, `month`
 ORDER BY t.id_item, `year`, `month` 
) AS dt 
CROSS JOIN (SELECT @roll_qty := 0, 
                   @id_itm := 0
           ) AS user_init_vars;

| id_item | year | month | qty |
| ------- | ---- | ----- | --- |
| 1       | 2018 | 07    | 5   |
| 1       | 2018 | 08    | 5   |
| 1       | 2018 | 09    | 15  |
| 1       | 2018 | 10    | 10  |

View on DB Fiddle

Madhur Bhaiya
  • 28,155
  • 10
  • 49
  • 57
  • I have tried my own query which select 12 end day of each month of this year then left join to my transaction table, and it looks fine.E.g. SELECT '2018-01-31' as month_date UNION SELECT '2018-02-28' as month_date and so on... then left join to my transaction table and use <= condition after join to get my desired output. Will this caused any performance issue or any better suggestion? If based on your code it's a bit complex for my situation to make use of it. – Crazy Nov 07 '18 at 09:12
  • @Crazy for now if you can make your own query with eloquent, go for it. Performance won't be an issue in smaller data-set; once you start hitting performance bottleneck, by that time , you would have hopefully upgraded your mariadb version. In that case , you would simply use window functions. This query will be more efficient than using union and left join. But differences will become visible when data become bigger – Madhur Bhaiya Nov 07 '18 at 09:17
  • Yes, the table itself already hit 6 digit of records. I think the performance will be slow. I think need to find other way to achieve it. – Crazy Nov 07 '18 at 09:31
0

If you are going to use variables, you need to do it correctly. MySQL does not guarantee the order of evaluation of expressions in a SELECT. So, a variable should not be assigned in one expression and then used in another.

This makes for complicated expressions, but it is possible:

select yyyy, mm, total,
       (@t := if(@ym = concat_ws('-', yyyy, mm), @t + total,
                 @ym := concat_ws('-', yyyy, mm), total
                )
       ) as running_total                 
from (select year(created_at) as yyyy, month(created_at) as mm,
             id_item, 
             sum(case T_IN when 1 then qty else - qty end) as total 
      from transactions 
      where created_at < '2018-11-01'
      group by id_item
      order by id_item, min(created_at)
     ) i cross join
     (select @ym := '', @n := 0);
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786