SQL subtract from different table - sql

I have two different queries from two tables. The first query I have is:
select sum(total_amount) as total_amount, supplier_name
from tbL_supplierAccountLedger
where DATE >= '2017-01-01' and DATE <= '2017-12-31' group by supplier_name
The output of this is
Total Amount | Supplier name
4000 A
5000 B
8000 C
9000 D
Here is my another query with different tablename
SELECT SUM(RET_AMOUNT)as returnamount, SUPPLIER_NAME
FROM tbl_PurchaseReturns
where CAST(date as DATE) >= '2017-01-01' and
CAST(date as DATE) <= '2017-12-31'
group by SUPPLIER_NAME
The output of this is
Return Amount | Supplier name
1000 A
2000 B
500 C
I want a query that automatically subtracts table B from table A.
Below is the expected output.
total amount | Supplier Name
3000 A
3000 B
7500 C
9000 D

use derived query and union both result, with the RET_AMOUNT of tbl_PurchaseReturns as negative value. And finally group by supplier_name
SELECT SUM(total_amount), supplier_name
FROM
(
SELECT sum(total_amount) as total_amount, supplier_name
from tbL_supplierAccountLedger
where DATE >= '2017-01-01' and DATE <= '2017-12-31'
group by supplier_name
UNION ALL
SELECT SUM(-RET_AMOUNT) as returnamount, supplier_name
FROM tbl_PurchaseReturns
where CAST(date as DATE) >= '2017-01-01'
and CAST(date as DATE) <= '2017-12-31'
group by supplier_name
) AS D
GROUP BY supplier_name

Do the JOINs
SELECT s.supplier_name,
r.total_amount - coalesce(returnamount, 0) as amount from
(
SELECT supplier_name , SUM(total_amount) as total_amount
FROM tbL_supplierAccountLedger
WHERE ...
GROUP BY supplier_name
)s LEFT JOIN (
SELECT SUPPLIER_NAME , SUM(RET_AMOUNT)as returnamount
FROM tbl_PurchaseReturns
WHERE ...
GROUP BY SUPPLIER_NAME
) r on r.SUPPLIER_NAME= s.supplier_name

Related

Presto SQL to find number of transactions in the year before the current transaction

I have a (simplified) transaction table of customer and order date. For each row/order I want to find the number of orders the year before the current order. I can do this with a self join, but when my transactions table is far bigger, it gets inefficient. I think I really want to use a window function with range between on the date field, but this isn't implemented in Presto yet. Any ideas of how I can do this more efficiently?
with
transactions as (
select
1 as customer,
date '2020-01-01' as order_date
union all
select
1 as customer,
date '2020-01-26' as order_date
union all
select
1 as customer,
date '2020-02-01' as order_date
union all
select
1 as customer,
date '2020-02-02' as order_date
)
select
t1.*,
count(case when t2.order_date between date_add('day', -14, t1.order_date) and date_add('day', -1, t1.order_date) then t2.order_date else null end) as orders_14_days_before
from
transactions t1
left join
transactions t2 on t1.customer = t2.customer
group by
t1.customer,
t1.order_date
Result:
customer order_date orders_14_days_before
1 2020-01-01 0
1 2020-01-26 0
1 2020-02-01 1
1 2020-02-02 2
Presto does not seem to fully support the range window specification. So you can do this another way . . . by doings ins-and-outs:
with cd as (
select customer, order_date as dte, 1 as inc
from transactions
union all
select customer, order_date + interval '1' year, -1 inc
from transactions
)
select t.*, cd.one_year_count
from (select customer, dte,
sum(sum(inc)) over (partition by customer order by dte) as one_year_count
from cd
group by customer, date
) cd join
transactions t
on cd.dte = t.order_date;
You should find that this is much faster.
Thanks to Gordon Linoff's answer above, I tweaked it to get the correct answer (at least in Athena). You don't need the sum(sum()) over ..., just sum() over ... is sufficient.
with
transactions as (
select
1 as customer,
date '2020-01-01' as order_date
union all
select
1 as customer,
date '2020-01-26' as order_date
union all
select
1 as customer,
date '2020-02-01' as order_date
union all
select
1 as customer,
date '2020-02-02' as order_date
),
cd as (
select
customer,
order_date as dte,
1 as inc
from
transactions
union all
select
customer,
order_date + interval '13' day,
-1 inc
from
transactions
),
cd2 as (
select
customer,
dte,
inc,
sum(inc) over (partition by customer order by dte rows between unbounded preceding and 1 preceding) as one_year_count
from
cd
)
select
t.*,
coalesce(cd2.one_year_count, 0) as one_year_count
from
cd2
inner join
transactions t
on cd2.dte = t.order_date
where
cd2.inc = 1
order by
2 asc

sql get balance at end of year

I have a transactions table for a single year with the amount indicating the debit transaction if the value is negative or credit transaction values are positive.
Now in a given month if the number of debit records is less than 3 or if the sum of debits for a month is less than 100 then I want to charge a fee of 5.
I want to build and sql query for this in postgre:
select sum(amount), count(1), date_part('month', date) as month from transactions where amount < 0 group by month;
I am able get records per month level, I am stuck on how to proceed further and get the result.
You can start by generating the series of month with generate_series(). Then join that with an aggregate query on transactions, and finally implement the business logic in the outer query:
select sum(t.balance)
- 5 * count(*) filter(where coalesce(t.cnt, 0) < 3 or coalesce(t.debit, 0) < 100) as balance
from generate_series(date '2020-01-01', date '2020-12-01', '1 month') as d(dt)
left join (
select date_trunc('month', date) as dt, count(*) cnt, sum(amount) as balance,
sum(-amount) filter(where amount < 0) as debit
from transactions t
group by date_trunc('month', date)
) t on t.dt = d.dt
Demo on DB Fiddle:
| balance |
| ------: |
| 2746 |
How about this approach?
SELECT
SUM(
CASE
WHEN usage.amount_s > 100
OR usage.event_c > 3
THEN 0
ELSE 5
END
) AS YEAR_FEE
FROM (SELECT 1 AS month UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12
) months
LEFT OUTER JOIN
(
SELECT
sum(amount) AS amount_s,
count(1) event_c,
date_part('month', date) AS month
FROM transactions
WHERE amount < 0
GROUP BY month
) usage ON months.month = usage.month;
First you must use a resultset that returns all the months (1-12) and join it with a LEFT join to your table.
Then aggregate to get the the sum of each month's amount and with conditional aggregation subtract 5 from the months that meet your conditions.
Finally use SUM() window function to sum the result of each month:
SELECT DISTINCT SUM(
COALESCE(SUM(t.Amount), 0) -
CASE
WHEN SUM((t.Amount < 0)::int) < 3
OR SUM(CASE WHEN t.Amount < 0 THEN -t.Amount ELSE 0 END) < 100 THEN 5
ELSE 0
END
) OVER () total
FROM generate_series(1, 12, 1) m(month) LEFT JOIN transactions t
ON m.month = date_part('month', t.date) AND date_part('year', t.date) = 2020
GROUP BY m.month
See the demo.
Results:
> | total |
> | ----: |
> | 2746 |
I think you can use the hanving clause.
Select ( sum(a.total) - (12- count(b.cnt ))*5 ) as result From
(Select sum(amount) as total , 'A' as name from transactions ) as a left join
(Select count(amount) as cnt , 'A' as name
From transactions
where amount <0
group by month(date)
having not(count(amount) <3 or sum(amount) >-100) ) as b
on a.name = b.name
select
sum(amount) - 5*(12-(
select count(*)
from(select month, count(amount),sum(amount)
from transactions
where amount<0
group by month
having Count(amount)>=3 And Sum(amount)<=-100))) as balance
from transactions ;

How to select rows where numbers have increased from previous record in SQL

I have a table with 2 columns date and sales, from this I need to pick up the dates on which sales have increased from previous date. Below is a sample table
Date Sales
-------------------
1/8/2020 10
1/9/2020 12
1/10/2020 8
1/11/2020 7
1/12/2020 13
Output should be as below:
Date
---------
1/9/2020
1/12/2020
Query:
Select data
from table
where sales > sales of previous day
You can use LAG to calculate this:
with cte
as (select date_c
, sales
, lag(sales) over (order by date_c) sales2
from Test)
select date_c, sales from cte
where sales > sales2;
Here is a DEMO
If you have gaps in days, you can consider this following logic with sub query-
DEMO HERE
WITH CTE AS
(
SELECT Date,Sales,
(
SELECT Sales
FROM your_table
WHERE Date = (SELECT MAX(Date) FROM your_table WHERE Date < A.Date)
) Last_day_sales
FROM your_table A
)
SELECT Date,Sales
FROM CTE
WHERE Sales > Last_day_sales*emphasized text*
You can use self join for this requirement.
select
A.date
from TableA as A
inner join TableA as B on B.date = (A.date - interval '1 day')
and A.sales > B.sales;

Except does not make correct intersection between 2 sets

I want to get best sellers from march 2019, while excluding the top 3 sellers of january. I tried using except where first SELECT gives best sellers of march (all of them) and the second SELECT gives top 3 of january.
SELECT * FROM (SELECT fullname, SUM(sale) sales
FROM mytable
WHERE oredrdate BETWEEN '2019-03-01' AND '2019-03-31'
GROUP BY fullname
ORDER BY sales DESC) X
EXCEPT
SELECT * FROM (SELECT fullname, SUM(sale) sales
FROM mytable
WHERE oredrdate BETWEEN '2019-01-01' AND '2019-01-31'
GROUP BY fullname
ORDER BY sales DESC
LIMIT 3) Y;
The problem is that EXCEPT does not intersect as I wished it would. What each SELECT returns and my desired output with data:
First SELECT returns:
fullname sales
Tommy Williams 8320
Ryan Atkinson 7310
Petey Cruiser 6200
Anna Mull 5840
Gail Forcewind 4120
Paige Turner 3300
Bob Frapples 2100
... ...
Seconds SELECT returns:
fullname sales
Tommy Williams 9220
Anna Mull 8100
Greta Life 7891
Desired OUTPUT:
fullname sales
Ryan Atkinson 7310
Petey Cruiser 6200
Gail Forcewind 4120
Paige Turner 3300
Bob Frapples 2100
... ...
How should I change my code to achieve this?
This can be done with a LEFT JOIN where you exclude the matching rows:
SELECT X.*
FROM (
SELECT fullname, SUM(sale) sales
FROM mytable
WHERE oredrdate BETWEEN '2019-03-01' AND '2019-03-31'
GROUP BY fullname
) X LEFT JOIN (
SELECT fullname, SUM(sale) sales
FROM mytable
WHERE oredrdate BETWEEN '2019-01-01' AND '2019-01-31'
GROUP BY fullname
ORDER BY sales DESC
LIMIT 3
) Y ON Y.fullname = X.fullname
WHERE Y.fullname IS NULL
ORDER BY X.sales DESC
You could use:
SELECT fullname, SUM(sales) AS total
FROM mytable
WHERE oredrdate BETWEEN '2019-03-01' AND '2019-03-31'
AND fullname NOT IN (SELECT fullname, SUM(sales) AS total
FROM mytable
WHERE oredrdate BETWEEN '2019-01-01' AND '2019-01-31'
AND fullname IS NOT NULL
GROUP BY fullname
ORDER BY total DESC LIMIT 3)
GROUP BY fullname
ORDER BY total DESC;
I would group by some kind of unique column like employee_id, there is possibility that two persons could have the same name.
The problem is that EXCEPT is considering both the name and the amount columns. It is unlikely that the second would match.
One way to write this is:
WITH jan3 as (
SELECT TOP (3) fullname, SUM(sale) as sales
FROM mytable
WHERE orderdate >= '2019-01-01' AND
orderdate < '2019-02-01'
GROUP BY fullname
ORDER BY sales DESC
)
SELECT m.fullname, SUM(m.sale) as sales
FROM mytable m
WHERE m.orderdate >= '2019-03-01' AND
m.orderdate < '2019-04-01' AND
NOT EXISTS (SELECT 1
FROM jan3
WHERE jan3.fullname = m.fullname
)
GROUP BY fullname
ORDER BY sales DESC;
Note that this changes the date comparisons to use >= and <. This is considered a best practice, because it works for dates and datetime (timestamp) values.
There are other ways of writing this using only a single aggregation. For instance:
WITH s as (
SELECT m.fullname,
SUM(CASE WHEN m.orderdate < '2019-02-01' THEN m.sale END) as sales_jan,
SUM(CASE WHEN m.orderdate >= '2019-03-01' THEN m.sale END) as sales_mar
FROM mytable m
WHERE m.orderdate >= '2019-01-01' AND
m.orderdate < '2019-04-01'
)
SELECT s.*
FROM (SELECT s.*,
ROW_NUMBER() OVER (ORDER BY sales_jan DESC) as seqnum_jan
FROM s
) s
WHERE seqnum_jan > 3
ORDER BY s.sales_mar;

get max date when sum of a field equals a value

I have a problem with writing a query.
Row data is as follow :
DATE CUSTOMER_ID AMOUNT
20170101 1 150
20170201 1 50
20170203 1 200
20170204 1 250
20170101 2 300
20170201 2 70
I want to know when(which date) the sum of amount for each customer_id becomes more than 350,
How can I write this query to have such a result ?
CUSTOMER_ID MAX_DATE
1 20170203
2 20170201
Thanks,
Simply use ANSI/ISO standard window functions to calculate the running sum:
select t.*
from (select t.*,
sum(t.amount) over (partition by t.customer_id order by t.date) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
If for some reason, your database doesn't support this functionality, you can use a correlated subquery:
select t.*
from (select t.*,
(select sum(t2.amount)
from t t2
where t2.customer_id = t.customer_id and
t2.date <= t.date
) as running_amount
from t
) t
where running_amount - amount < 350 and
running_amount >= 350;
ANSI SQL
Used for the test: TSQL and MS SQL Server 2012
select
"CUSTOMER_ID",
min("DATE")
FROM
(
select
"CUSTOMER_ID",
"DATE",
(
SELECT
sum(T02."AMOUNT") AMOUNT
FROM "TABLE01" T02
WHERE
T01."CUSTOMER_ID" = T02."CUSTOMER_ID"
AND T02."DATE" <= T01."DATE"
) "AMOUNT"
from "TABLE01" T01
) T03
where
T03."AMOUNT" > 350
group by
"CUSTOMER_ID"
GO
CUSTOMER_ID | (No column name)
----------: | :------------------
1 | 03/02/2017 00:00:00
2 | 01/02/2017 00:00:00
db<>fiddle here
DB-Fiddle
SELECT
tmp.`CUSTOMER_ID`,
MIN(tmp.`DATE`) as MAX_DATE
FROM
(
SELECT
`DATE`,
`CUSTOMER_ID`,
`AMOUNT`,
(
SELECT SUM(`AMOUNT`) FROM tbl t2 WHERE t2.`DATE` <= t1.`DATE` AND `CUSTOMER_ID` = t1.`CUSTOMER_ID`
) AS SUM_UP
FROM
`tbl` t1
ORDER BY
`DATE` ASC
) tmp
WHERE
tmp.`SUM_UP` > 350
GROUP BY
tmp.`CUSTOMER_ID`
Explaination:
First I select all rows and subselect all rows with SUM and ID where the current row DATE is smaller or same as all rows for the customer. From this tabe i select the MIN date, which has a current sum of >350
I think it is not an easy calculation and you have to calculate something. I know It could be seen a little mixed but i want to calculate step by step. As fist step if we can get success for your scenario, I believe it can be made better about performance. If anybody can make better my query please edit my post;
Unfortunately the solution that i cannot try on computer is below, I guess it will give you expected result;
-- Get the start date of customers
SELECT MIN(DATE) AS DATE
,CUSTOMER_ID
INTO #table
FROM TABLE t1
-- Calculate all possible date and where is sum of amount greater than 350
SELECT t1.CUSTOMER_ID
,SUM(SELECT Amount FROM TABLE t3 WHERE t3.DATE BETWEEN t1.DATE
AND t2.DATE) AS total
,t2.DATE AS DATE
INTO #tableCalculated
FROM #table t1
INNER JOIN TABLE t2 ON t.ID = t2.ID
AND t1.DATE != t2.DATE
WHERE total > 350
-- SELECT Min amount and date for per Customer_ID
SELECT CUSTOMER_ID, MIN(DATE) AS DATE
FROM #tableCalculated
GROUP BY ID
SELECT CUSTOMER_ID, MIN(DATE) AS GOALDATE
FROM ( SELECT cd1.*, (SELECT SUM(AMOUNT)
FROM CustData cd2
WHERE cd2.CUSTOMER_ID = cd1.CUSTOMER_ID
AND cd2.DATE <= cd1.DATE) AS RUNNINGTOTAL
FROM CustData cd1) AS custdata2
WHERE RUNNINGTOTAL >= 350
GROUP BY CUSTOMER_ID
DB Fiddle