Home > Back-end >  Get COUNT/SUM of records based on another column in SQL
Get COUNT/SUM of records based on another column in SQL

Time:12-12

How do I get the count/sum of the rows (COUNT () or SUM ()) based on another column (of the Type: weekly or yearly)? I have two tables:

  1. Stores:
Id Name Type
1 Store 1 Weekly
2 Store 2 Yearly
3 Store 3 Weekly
4 Store 4 Weekly
  1. Orders:
Id StoreId OrderDate Qty
1 1 2022-01-31 2
2 1 2022-12-31 5*
3 2 2022-01-28 30*
4 2 2022-06-30 50*
5 2 2022-12-31 70*
6 3 2022-06-15 8
7 3 2022-12-27 9*
8 3 2022-12-31 3*

a) If I pass the date range (by weekly,2022-12-26 ~ 2023-01-01), the expected result should look like this:

Id Name Count of orders Total Qty
1 Store 1 1 5
2 Store 2 3 150 (sum by the year when the store's type equals "Yearly": 30 50 70)
3 Store 3 2 12 (sum by the selected week: 9 3)
4 Store 4 0 0

If the Store type is Yearly then all orders will be summed up based on StoreId & year of OrderDate, if Weekly then based on StoreId & selected OrderDate.

b) I tried using CASE in SELECT statement, but no luck, here are part of my codes:

SELECT s.Id,
       s.Name,
       COUNT(o.Id) AS 'Count of orders',
       sum(o.Qty) AS 'Total Qty'
  FROM Stores AS s
  LEFT JOIN Orders AS o
    ON o.StoreId = s.id
   AND (OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01')
 GROUP BY s.Id, OrderDate
 ORDER BY OrderDate DESC

CodePudding user response:

You could use conditional aggregation as the following:

SELECT s.Id,
       s.Name,
       COUNT(CASE
               WHEN s.Type = 'Yearly' THEN
                o.Id
               ELSE
                CASE
                  WHEN OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01' THEN
                   o.Id
                END
             END) As 'Count of orders',
       SUM(CASE
             WHEN s.Type = 'Yearly' THEN
              o.Qty
             ELSE
              CASE
                WHEN OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01' THEN
                 o.Qty
                ELSE
                 0
              END
           END) AS 'Total Qty'
  FROM Stores AS s
  LEFT JOIN Orders AS o
    ON o.StoreId = s.id
 GROUP BY s.Id, s.Name
 ORDER BY MAX(OrderDate) DESC

See demo.

CodePudding user response:

You can do in this way. Please take note that, type is a keyword in MySQL.

SELECT s.id,
       s.name,
       s.type,
       COUNT(s.name) AS total_count,
       SUM(o.qty) AS total_qty
  FROM stores s
  LEFT JOIN orders o
    ON s.id = o.storeid
 WHERE (o.orderdate >= '2022-12-26' AND o.orderDate <= '2023-01-01' 
   AND  s.type = 'Weekly')
    OR  s.type = 'Yearly'
 GROUP BY s.id, s.name, s.type

CodePudding user response:

From the description, calculate count(Orders.Id) and sum(Orders.Qty)

  1. Stores.Type = 'Weekly': Orders.OrderDate between @start_date and @end_date

  2. Stores.Type = 'Yearly': Orders.OrderDate in the year of @start_date (...all orders will be summed up based on StoreId & year of OrderDate.)

Thus, the first step is to have where clause to filter out Orders and then aggregate to Store.Id level. Then, 2nd step is to left join from Stores table to the result of first step so that stores without sales in specified date ranges are reported.

set @start_date = '2022-12-26', @end_date = '2023-01-01';

with cte_store_sales as (
select s.Id,
       count(o.Id) as order_count,
       sum(o.Qty)  as total_qty
  from stores s
  left 
  join orders o
    on s.Id = o.StoreId
 where (s.type = 'Weekly' and o.OrderDate between @start_date and @end_date)
    or (s.type = 'Yearly' and o.OrderDate between makedate(year(@start_date),1)
                                              and date_sub(date_add(makedate(year(@start_date),1), interval 1 year), interval 1 day))
 group by s.Id)
select s.Id,
       s.Name,
       coalesce(ss.order_count, 0) as "Count of Orders",
       coalesce(ss.total_qty, 0)   as "Total Qty"
  from stores s
  left
  join cte_store_sales ss
    on s.Id = ss.Id
 order by s.Id;

Output:

Id|Name   |Count of Orders|Total Qty|
-- ------- --------------- --------- 
 1|Store 1|              1|        5|
 2|Store 2|              3|      150| <-- Store sales in year 2022
 3|Store 3|              2|       12|
 4|Store 4|              0|        0| <-- Report stores without sales 

CodePudding user response:

First of all, we shall extract the raw data matching the orderdate table condition, which can be used for the sake of aggregation later. Note,here I treat the date range as inclusive. Therefore, it shall be year 2022 and 2023 for 2022-12-26 ~ 2023-01-01 if the type is yearly.

    select s.id id, name,
        (case when type='weekly' and orderdate between '2022-12-26' and '2023-01-01' then qty
         when type='yearly' and year(orderdate) between year('2022-12-26') and year('2023-01-01') then qty
        end) as qt
    from Stores s
    left join Orders o
    on s.id=o.storeid;
-- result set:
# id, name, qt
1, Store 1, 5
2, Store 2, 30
2, Store 2, 50
2, Store 2, 70
3, Store 3, 
3, Store 3, 9
3, Store 3, 3
4, Store 4, 

The rest is to do the summarisation job using the derived table. Note: Since the column name is not in the group by list, but it's actually unique for a specific storeid, we can use the any_value function to bypass the restriction which might be enforced due to the SQL_MODE system variable.

select id,any_value(name) as'Name',count(qt) as 'Count of orders', ifnull(sum(qt),0) as 'Total Qty'
from
    (select s.id id, name,
        (case when type='weekly' and orderdate between '2022-12-26' and '2023-01-01' then qty
         when type='yearly' and year(orderdate) between year('2022-12-26') and year('2023-01-01') then qty
        end) as qt
    from Stores s
    left join Orders o
    on s.id=o.storeid) tb
group by id
order by id
;
-- result set:
# id, Name, Count of orders, Total Qty
1, Store 1, 1, 5
2, Store 2, 3, 150
3, Store 3, 2, 12
4, Store 4, 0, 0
  • Related