How to get balance from two SQL table debit and credit dynamically - sql

Customer table
id | name | customerid
1 | Philip James | ac1001
2 | Frank Mathew | ac1002
Credit table
id| year | customer | amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 1000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
Debit table
id| year | customer| amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 2000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
I am trying to get the balance for each customer dynamically in respect to the year, i tried using this;
SELECT debit.year,customers.name,customers.customerid,SUM(debit.amount),SUM(credit.amount),
SUM(COALESCE((debit.amount),0)-COALESCE((credit.amount),0))AS balance FROM
customers
RIGHT JOIN credit ON customers.customerid=credit.customer
RIGHT JOIN debit ON customers.customerid=debit.customer GROUP BY customers.customerid,debit.year
Query Result
year| customer | sum(debit)| sum(credit)| Balance
2020 | ac1001 | 48000 | 42000 | 6000
2020 | ac1002 | 8000 | 6000 | 2000
But what i need is this table below, thank you
Expected Result
year| customer | sum(debit)| sum(credit)| Balance
2019 | ac1001 | 4000 | 3000 | 1000
2020 | ac1001 | 4000 | 4000 | 0
2020 | ac1002 | 4000 | 4000 | 0

union the two tables and then aggregate. You can use a cumulative sum to calculate the balance:
select year, customer, sum(amount) as amount_in_year,
sum(sum(amount)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount
from credit
) union all
(select id, year, customer, - amount
from debit
)
) cd
group by year, customer;
EDIT:
For the revised question:
select year, customer, sum(credit) as sum_credit, sum(debit) as sum_debit,
sum(sum(credit - debit)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount as credit, 0 as debit
from credit
) union all
(select id, year, customer, 0 as credit, amount as debit
from debit
)
) cd
group by year, customer;

Related

Get rowset based on distinct combination of columns

Given this dataset, each stock has a yearly snapshot of value.
+----+------+------+-------+-------+
| ID | Name | Year | Stock | Value |
+----+------+------+-------+-------+
| 1 | John | 2019 | ABC | 123 |
| 1 | John | 2020 | ABC | 123 |
| 1 | John | 2021 | ABC | 123 |
| 1 | John | 2021 | XYZ | 200 |
| 1 | John | 2022 | ABC | 123 |
| 1 | John | 2022 | XYZ | 200 |
| 1 | John | 2023 | ABC | 630 |
| 1 | John | 2023 | XYZ | 200 |
+----+------+------+-------+-------+
In 2019, John only holds ABC with a value of 123
In 2020, John also only holds ABC, with a value of 123 (has not changed)
In 2021, John holds ABC but has also acquired XYZ, with a value of 200
in 2022, John holds ABC and XYZ, both of which values haven't changed.
In 2023, John holds ABC and XYZ, with ABC's value increasing to 630 and XYZ's value remaining at 200.
I would like to return rows so that
Per year, if nothing of John's portfolio has changed SINCE THE LAST YEAR, no rows are returned
If anything in John's portfolio has changed SINCE THE LAST YEAR, all his current holdings are listed
For example,
+----+------+------+-------+-------+
| ID | Name | Year | Stock | Value |
+----+------+------+-------+-------+
| 1 | John | 2019 | ABC | 123 |
| 1 | John | 2021 | ABC | 123 |
| 1 | John | 2021 | XYZ | 200 |
| 1 | John | 2023 | ABC | 630 |
| 1 | John | 2023 | XYZ | 200 |
+----+------+------+-------+-------+
How would I do this, whether it be through functions in PL/SQL or in pure SQL?
If there are not too many rows per user, then listagg() provides a convenient solution:
select ny.*
from (select name, year,
listagg(stock || ':' || value, ',') within group (order by stock) as stocks,
lag(listagg(stock || ':' || value, ',') within group (order by stock)) as prev_stocks,
lag(year) over (partition by name order by year) as prev_year
from t
group by name, year
) ny
where prev_year is null or prev_year <> year - 1 or prev_stocks <> stocks;
Alternatively, you can check each row individually and use an analytic function to project the information over all rows in a name/year:
select t.*
from (select t.*,
sum(case when prev_nsv_year = year then 0 else 1 end) over (partition by name, year) as num_diff,
lag(cnt) over (partition by name order by year) as prev_cnt
from (select t.*,
lag(year) over (partition by name, stock, value over order by year) as prev_nsv_year,
count(*) over (partition by name, year) as cnt
from t
) t
) t
where cnt <> prev_cnt or prev_cnt is null or
num_diff > 0;

Summing Sales Based On Different Year Cases

| Product ID | YearBought | Sales | Min_Year | Max_Year |
| 1 | 2016 | $20 | 2011 | 2016 |
| 2 | 2016 | $10 | 2016 | 2018 |
| 2 | 2017 | $30 | 2016 | 2018 |
| 3 | 2017 | $5 | 2015 | 2019 |
| 3 | 2018 | $10 | 2015 | 2019 |
| 3 | 2018 | $20 | 2015 | 2019 |
| 3 | 2019 | $5 | 2015 | 2019 |
| 3 | 2019 | $30 | 2015 | 2019 |
| 4 | 2018 | $5 | 2018 | 2020 |
| 4 | 2019 | $10 | 2018 | 2020 |
| 4 | 2020 | $20 | 2018 | 2020 |
Min_Year = the year the product was first introduced
Max_Year + 1 = Product drop off year
Above is a sample of the table I'm working with. Trying to find:
the sum of sales new products brought in the year they were first introduced
the sum of "dropped sales" aka the sum of sales from products the year after they dropped off (had no sales). (Ex. Product brought in $15 in 2018 but had no sales in 2019, want to show $15 as dropped sales in 2019)
Expected Output:
| YearBought | New Product Sales | Dropped Product Sales |
| 2016 | $10 | |
| 2017 | | $20 |
| 2018 | $5 | |
| 2019 | | |
| 2020 | | $35 |
was thinking something like this but it's not working. any help would be appreciated!
select
YearBought,
sum(case when yearbought=min_year then sales else 0 end) as NewSales,
sum(case when yearbought=max_year+1 then sales else 0 end) as DropSales
from
#t
group by
yearbought
Aggregate the dropped product sales separately from the new product sales, then join the aggregations. You can do this with subqueries, or, as I have done below, with common table expressions.
with
droppedProds as (
select droppedYear = yearBought + 1,
foregoneSales = sum(sales)
from #t t
where YearBought = Max_Year
group by YearBought
),
newSales as (
select YearBought,
sales = sum(sales),
newSales = sum(case when yearBought = min_year then sales end)
from #t t
group by YearBought
)
select YearBought,
n.sales,
n.newSales,
d.foregoneSales
from newSales n
left join droppedProds d on n.yearBought = d.droppedYear
order by YearBought;
Results in:
+------------+-------+----------+---------------+
| YearBought | sales | newSales | foregoneSales |
|------------|-------|----------|---------------|
| 2016 | 30 | 10 | |
| 2017 | 35 | | 20 |
| 2018 | 35 | 5 | |
| 2019 | 45 | | |
| 2020 | 20 | | 35 |
+------------+-------+----------+---------------+
You could list the dates first, then join it with the original table and do conditional aggregation:
select
y.yearbought,
sum(case when t.yearbought = t.min_year then sales end) new_product_sales,
sum(case when t.yearbought = t.max_year then sales end) dropped_product_sales
from (select distinct yearbought from #t) y
inner join #t on y.yearbought in (t.min_year, t.max_year + 1)
group by y.yearbought
I think your problem is that the years are staggered.
with n as (
select year_bought as y, sum(sales) as sales,
sum(case when year_bought = min_year then sales end) as ns
from T group by year_bought
), d as (
select year_bought + 1 as y,
sum(case when year_bought = max_year then sales end) as ds
from T group by year_bought
)
select y, sales,
ns as newSales, coalesce(dropped, 0) as foregoneSales
from n left outer join p on p.y = n.y;
or use lead()
select year_bought, sum(Sales) as Sales,
sum(case when year_bought = min_year then sales end) as newSales,
coalesce(lead(sum(case when year_bought = max_year then sales end))
over (order by year_bought), 0) as foregoneSales
from T
group by year_bought;

Join two columns as a date in sql

I am currently working with a report through Microsoft Query and I ran into this problem where I need to calculate the total amount of money for the past year.
The table looks like this:
Item Number | Month | Year | Amount |
...........PAST YEARS DATA...........
12345 | 1 | 2019 | 10 |
12345 | 2 | 2019 | 20 |
12345 | 3 | 2019 | 15 |
12345 | 4 | 2019 | 12 |
12345 | 5 | 2019 | 11 |
12345 | 6 | 2019 | 12 |
12345 | 7 | 2019 | 12 |
12345 | 8 | 2019 | 10 |
12345 | 9 | 2019 | 10 |
12345 | 10 | 2019 | 10 |
12345 | 11 | 2019 | 10 |
12345 | 12 | 2019 | 10 |
12345 | 1 | 2020 | 10 |
12345 | 2 | 2020 | 10 |
How would you calculate the total amount from 02-2019 to 02-2020 for the item number 12345?
Assuming that you are running SQL Server, you can recreate a date with datefromparts() and use it for filtering:
select sum(amount)
from mytable
where
itemnumber = 12345
and datefromparts(year, month, 1) >= '20190201'
and datefromparts(year, month, 1) < '20200301'
You can use this also
SELECT sum(amount) as Amount
FROM YEARDATA
WHERE ( Month >=2 and year = '2019')
or ( Month <=2 and year = '2020')
and ItemNumber = '12345'

SELECTing monthly order amounts and item subtotals in a single query with two tables

I have an Orders table in the form:
| id | service_fee_cents | grand_total_cents | created_at |
|----|-------------------|-------------------|---------------|
| 1 | 1400 | 10000 | Jan 21 2018 |
| 2 | 1000 | 10000 | Feb 16 2018 |
| 3 | 500 | 10000 | March 21 2018 |
| 4 | 500 | 10000 | March 20 2018 |
And an Items table in the form
| id | order_id | title | price_cents | quantity |
|----|----------|--------|-------------|----------|
| 1 | 1 | lorem | 2000 | 2 |
| 2 | 1 | ipsum | 2030 | 1 |
| 3 | 2 | pie | 4000 | 4 |
| 4 | 3 | cheese | 6000 | 2 |
| 5 | 3 | burger | 7000 | 1 |
| 6 | 4 | custar | 1000 | 1 |
And I'm trying to run a SQL query to get a result in the form
| month | total_service_fee | total_grand_total | total_subtotal |
|-----------|-------------------|-------------------|----------------|
|2017-11-01 | 42 | 1,610 | 610 |
|2017-12-01 | 30 | 19,912 | 1,912 |
|2018-01-01 | 179 | 1,413 | 413 |
|2018-02-01 | 165 | 2,910 | 910 |
|2018-03-01 | 1,403 | 10,727 | 1,727 |
I've managed to get the first three columns using this query:
SELECT
date_trunc('month', created_at)::date AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total
FROM orders
GROUP BY month ORDER BY month
How do I get the last one? In the app, I get the sum via the following Ruby code:
order_subtotal = order.items.map{|item| item.price * item.quantity}.reduce(:+)
Which basically takes all the order's items, multiplies price by quantity and adds the results.
This should be a good start:
SELECT Date_trunc('month', created_at) :: DATE AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total,
SUM(total_subtotal) / 100 AS total_subtotals
FROM orders o
join (SELECT order_id,
SUM(price_cents * quantity) total_subtotal
FROM items i
GROUP BY order_id) i
ON o.id = i.order_id
GROUP BY month
ORDER BY month
You can get there by just joining the Orders table to the Items table and generating a SUM of subtotals by month. This may however be a somewhat expensive query to run if there are thousands of items in each order like you said.
SELECT
date_trunc('month', created_at)::date AS month,
SUM(service_fee_cents) / 100 AS total_service_fee,
SUM(grand_total_cents) / 100 AS total_grand_total,
SUM(price_cents * quantity) / 100 AS sub_total
FROM Orders o
JOIN Items i ON i.order_id = o.id
GROUP BY month ORDER BY month
http://sqlfiddle.com/#!15/555a2/1

Get data for fiscal year from table without date columns

I'm trying to create a query (purpose: manual DB testing) that would get the rows of the previous/current/next Fiscal Year and then the SUM(turnover)
Given
(1) the below table,
and
(2) Fiscal Year (FY) = March to February
When Previous FY -- Then 2 rows: 2016/1 to 2016/2
When Current FY -- Then 12 rows: from 2016/3 to 2017/2 (year/month)
When Future FY -- Then 1 row: 2017/3
+--------------+---------------+----------+
| Year (num) | Month (num) | Turnover |
+--------------+---------------+----------+
| 2016 | 1 | 1000 |
+--------------+---------------+----------+
| 2016 | 2 | 2000 |
+--------------+---------------+----------+
| 2016 | 3 | 3000 |
+--------------+---------------+----------+
| 2016 | 4 | 4000 |
+--------------+---------------+----------+
| 2016 | 5 | 2000 |
+--------------+---------------+----------+
| 2016 | 6 | 1000 |
+--------------+---------------+----------+
| 2016 | 7 | 2000 |
+--------------+---------------+----------+
| 2016 | 8 | 1000 |
+--------------+---------------+----------+
| 2016 | 9 | 2000 |
+--------------+---------------+----------+
| 2016 | 10 | 3000 |
+--------------+---------------+----------+
| 2016 | 11 | 4000 |
+--------------+---------------+----------+
| 2016 | 12 | 5000 |
+--------------+---------------+----------+
| 2017 | 1 | 6000 |
+--------------+---------------+----------+
| 2017 | 2 | 2000 |
+--------------+---------------+----------+
| 2017 | 3 | 1000 |
+--------------+---------------+----------+
The best solution I came up with is the below query and change the Year values to switch between years. It feels hacky to me because of creating an extra solumn with sysdate and checking for NOT NULL. Is there a more elegant way?
WITH CTE AS (
SELECT
CASE
WHEN Month BETWEEN 3 AND 12 AND Year = 2016
THEN sysdate
WHEN Month BETWEEN 1 AND 2 AND Year = 2017
THEN sysdate
END case_statement_date,
year, month, turnover, FROM Table
)
SELECT sum(turnover) FROM CTE
WHERE case_statement_date IS NOT NULL
;
Is this what you want?
select year + (case when month >= 3 then 0 else -1 end) as fiscal_year,
sum(turnover)
from t
group by year + (case when month >= 3 then 0 else -1 end) ;