Home > Enterprise >  List Roles that sum of expenses are larger than average
List Roles that sum of expenses are larger than average

Time:10-05

Sorry for asking this simple question(Maybe?). I am a newbie in SQL and I am trying to figure out how to solve this problem on my own.

I have three different tables but they share the same primary IDs.

L Table looks like this

L_Id  L_Nam
L1     GA
L2     FL
L3     NC

The R Table looks like this

R_Id  R_Name
R1     Same
R2     Steve
R3     Tony
R4     Roy
R5     Carol
R6     Jane
R7     Emma

The E Table looks like this

L_Id  R_Id      E_Date          E_Amt
L1     R1     2019-08-02         100
L1     R1     2019-08-05         400
L1     R1     2019-08-11         50
L2     R2     2019-08-02         150
L2     R2     2019-08-01         400
 :      :         :               :
L3     R6     2019-08-21         1020
L3     R7     2019-08-29         2000

There are two goals that I am trying to solve.

  1. Calculate the sum of E_Amt from the E table base on the month from E_Date and L_Name from the L table then list the R_Name
  2. Only list the R_Name that is greater or equal to the Average Monthly expense of any location they had expenses in that month.

I am still stuck on my first task. Here is my approach

Select 
R.R_Id,R.R_Name,
SUM(E.E_Amt),
month(E.E_Date)
from L inner join E on
L.L_Id = E.L_Id
inner join R on
R.R_Id = E.R_Id
GROUP By E.R_Id, month(E.E_Date);

But my query isn't working so I am wondering what did I do wrong and what approach I need to take in order to solve the goals.

Thanks!

CodePudding user response:

For the first question,

Calculate the sum of E_Amt from the E table base on the month from E_Date and L_Name from the L table then list the R_Name

Try:

select 
           r.Role_Name,
           a.month_of_date,
           sum(a.sum_expenses) as total_sum
    from (       
      select l.Location_Name,
             e.Location_Id,
             month(e.Transaction_Date) as month_of_date,
             e.Role_Id,
             sum(Expense_Amt)  as sum_expenses
    from Expenses e 
    inner join Locations l
    on e.Location_Id = l.Location_Id
    group by month_of_date,e.Location_Id,l.Location_Name,e.Role_Id 
      ) as a inner join Roles r on a.Role_Id=r.Role_Id
      group by r.Role_Name,a.month_of_date ;

or

select r.Role_Id,
       r.Role_Name,
       a.month_of_date,
       a.sum_expenses
from (       
  select l.Location_Name,
         e.Location_Id,
         month(e.Transaction_Date) as month_of_date,
         e.Role_Id,
         sum(Expense_Amt)  as sum_expenses
from Expenses e 
inner join Locations l
on e.Location_Id = l.Location_Id
group by month_of_date,e.Location_Id,l.Location_Name,e.Role_Id 
  ) as a inner join Roles r on a.Role_Id=r.Role_Id;

Demo: https://www.db-fiddle.com/f/pB6b5xrgPKCivFWcpQHsyE/3

Demo: https://www.db-fiddle.com/f/pB6b5xrgPKCivFWcpQHsyE/2

As per second question, if I understood correctly, try:

Only list the R_Name that is greater or equal to the Average Monthly expense of any location they had expenses in that month.

select r.Role_Name 
from Roles r
inner join 
(
  select 
       Role_Id,
       avg(Expense_Amt) as average_per_month,
       sum(Expense_Amt) as total_sum_per_month,
       Locations.Location_Id          
from Expenses 
inner join Locations  on Expenses.Location_Id=Locations.Location_Id
group by month(Transaction_Date),Role_Id,Locations.Location_Id
) as a on r.Role_Id=a.Role_Id
where average_per_month<=total_sum_per_month
group by r.Role_Name ;

Demo: https://www.db-fiddle.com/f/pB6b5xrgPKCivFWcpQHsyE/7

CodePudding user response:

You have a query that gives you the sums per month and role. This is the base you build your query on that compares month's role sum with the month's average sum.

with my_base as
(
  select 
    r.role_id, r.role_name,
    sum(e.expense_amt) as sum_expense_amt,
    date_format(e.transaction_date, '%Y-%m') as month
  from locations l
  inner join expenses e on e.location_id = l.location_id
  inner join roles r on r.role_id = e.role_id
  group by r.role_id, date_format(e.transaction_date, '%Y-%m')
)
select *
from my_base
where sum_expense_amt >=
(
  select avg(sum_expense_amt)
  from my_base all_roles
  where all_roles.month = my_base.month
);

Or make this two subqueries in the WITH clause. Here is an example with a join. (I don't like the join much, I prefer a subquery like above, because conditions belong in the WHERE clause in my opinion, not in the FROM clause. But why not show alternatives ;-)

with my_base as
(
  select 
    r.role_id, r.role_name,
    sum(e.expense_amt) as sum_expense_amt,
    date_format(e.transaction_date, '%Y-%m') as month
  from locations l
  inner join expenses e on e.location_id = l.location_id
  inner join roles r on r.role_id = e.role_id
  group by r.role_id, date_format(e.transaction_date, '%Y-%m')
)
, averages as
(
  select month, avg(sum_expense_amt) as avg_sum_expense_amt
  from my_base
  group by month
)
select my_base.*
from my_base
join averages on averages.month = my_base.month
             and averages.avg_sum_expense_amt <= my_base.sum_expense_amt
;

Or use a window function. This is my preferred way of writing the query.

select role_id, role_name, sum_expense_amt, avg_sum_expense_amt, month
from
(
  select 
    r.role_id, r.role_name,
    sum(e.expense_amt) as sum_expense_amt,
    avg(sum(e.expense_amt)) over (partition by date_format(e.transaction_date, '%Y-%m'))
      as avg_sum_expense_amt,
    date_format(e.transaction_date, '%Y-%m') as month
  from locations l
  inner join expenses e on e.location_id = l.location_id
  inner join roles r on r.role_id = e.role_id
  group by r.role_id, date_format(e.transaction_date, '%Y-%m')
) aggregated
where sum_expense_amt >= avg_sum_expense_amt;

Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=c4de13ea1946c6542e5ca4631d5d658c

All of these queries require at least MySQL 8, which should not be a problem because that version is aready three years old. In older versions neither CTEs (the WITH clause) nor analytic functions are featured. (In those old versions you'd have to write the base subquery twice.)

  • Related