Count records that span multiple date range - sql

ACCOUNT Amount DATE
1 50 01-2010
1 100 03-2010
1 100 02-2011
2 100 01-2011
2 50 05-2011
2 50 09-2011
3 100 03-2012
3 100 03-2013
Is there a query structure that will allow me to count distinct accounts that has spanned current and past year? For example, account 1 has amounts in 2011 and 2010 so it should be counted once under 2011. Account 2 only has amounts in 2011 so it doesn't get counted while account 3 has amounts in 2013 and 2012, so it gets counted as 1 under 2013:
2010 2011 2012 2013
0 1 0 1

First, you need to know the years where you have data for an account:
select account, year(date) as yr
from t
group by account, year(date)
Next, you need to see if two years are in sequence. You can do this in 2012 with lag/lead. Instead, we'll just use a self join:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
Next, if you want to count the number of accounts by year, just put this into an aggregation:
with ay as (
select account, year(date) as yr
from t
group by account, year(date)
)
select yr, count(*) as numaccounts
from (select ay.account, ay.yr
from ay join
ay ayprev
on ay.account = ayprev.account and
ay.yr = ayprev.yr + 1
) ayy
group by yr

Assuming you have a record id (call this ID)
SELECT COUNT(*),Year FROM Table t3
INNER JOIN (
SELECT record_id, Year(t1.Date) as Year FROM Table t1
INNER JOIN Table t2
WHERE Year(t1.Date)-1=Year(t2.Date) AND t1.Account == t2.Account
) x ON x.record_id = t3.record_id
GROUP BY Year

Use Below Query :
SELECT YEAR(T1.Date) AS D, COUNT(*) AS C
FROM YourTable AS T1
INNER JOIN YourTable T2 ON T2.Account = T1.Account AND YEAR(T2)=YEAR(T1)+1
GROUP BY T1.Account, YEAR(T1.Date)

Related

What to use in place of union in above query i wrote or more optimize query then my given query without union and union all

I am counting the birthdays , sales , order in all 12 months from customers table in SQL server like these
In Customers table birth_date ,sale_date, order_date are columns of the table
select 1 as ranking,'Birthdays' as Type,[MONTH],TOTAL
from ( select DATENAME(month, birth_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, birth_date)
)x
union
select 2 as ranking,'sales' as Type,[MONTH],TOTAL
from ( select DATENAME(month, sale_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, sale_date)
)x
union
select 3 as ranking,'Orders' as Type,[MONTH],TOTAL
from ( select DATENAME(month, order_date) AS [MONTH],count(*) TOTAL
from customers
group by DATENAME(month, order_date)
)x
And the output is like these(just dummy data)
ranking
Type
MONTH
TOTAL
1
Birthdays
January
12
1
Birthdays
April
6
1
Birthdays
May
10
2
Sales
Febrary
8
2
Sales
April
14
2
Sales
May
10
3
Orders
June
4
3
Orders
July
3
3
Orders
October
6
3
Orders
December
17
I want to find count of these all these three types without using UNION and UNION ALL, means I want these data by single query statement (or more optimize version of these query)
Another approach is to create a CTE with all available ranking values ​​and use CROSS APPLY for it, as shown below.
WITH ranks(ranking) AS (
SELECT * FROM (VALUES (1), (2), (3)) v(r)
)
SELECT
r.ranking,
CASE WHEN r.ranking = 1 THEN 'Birthdays'
WHEN r.ranking = 2 THEN 'Sales'
WHEN r.ranking = 3 THEN 'Orders'
END AS Type,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END) AS MONTH,
COUNT(*) AS TOTAL
FROM customers c
CROSS APPLY ranks r
GROUP BY r.ranking,
DATENAME(month, CASE WHEN r.ranking = 1 THEN c.birth_date
WHEN r.ranking = 2 THEN c.sale_date
WHEN r.ranking = 3 THEN c.order_date
END)
ORDER BY r.ranking, MONTH

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 ;

Query two unbalanced tables

Sum across two tables returns unwanted Sum from one table multiplied by the number of rows in the other
I have 1 table with Actual results recorded by date and the other tables contains planned results recorded by month.
Table 1(Actual)
Date Location Amount
01/01/2019 Loc1 1000
01/02/2019 Loc1 700
01/01/2019 Loc2 7500
01/02/2019 Loc2 1000
02/01/2019 Loc1 500
Table 2(Plan)
Year Month Location Amount
2019 1 Loc1 1500
2019 1 Loc2 8000
2019 2 Loc1 800
I have tried various differed Joins using YEAR(Table1.date) and Month(table1.date) and grouping by
Month(Table1.Date) but I keep running into the same problem where the PlanAmount is multiplied by however many rows in the Actual table...
in the example of loc1 for Month 1 below I get
Year Month Location PlanAmount ActualAmount
2019 1 Loc1 3000 1700
I would like to return the below
Year Month Location PlanAmount ActualAmount
2019 1 Loc1 1500 1700
2019 1 Loc2 8000 8500
2019 2 Loc1 800 500
Thanks in advance for any help
D
You can do this with a full join or union all/group by:
select yyyy, mm, location,
sum(actual_amount) as actual_amount,
sum(plan_amount) as plan_amount
from ((select year(date) as yyyy, month(date) as mm, location,
amount as actual_amount, 0 as plan_amount
from actual
group by year(date) as yyyy, month(date) as mm, location
) union all
(select year, month, location,
0 as actual_amount, amount as plan_amount
from actual
group by year, month, location
)
) ap
group by yyyy, mm, location;
This ensures that you have rows, even when there are no matches in the other table.
To get the required results you need to group the first table on year of date, month of date and location and need to select the columns year, month, location and sum of amount from group after that you need to join that resultant r
SELECT
plans.year,
plans.month,
plans.location,
plans.plan_amount,
grouped_results.actual_amount
FROM plans
INNER JOIN (
SELECT
datepart(year, date) AS year,
datepart(month, date) AS month,
location,
SUM(amount) AS actual_amount
FROM actuals
GROUP BY datepart(year, date), datepart(month, date), location
) as grouped_results
ON
grouped_results.year = plans.year AND
grouped_results.month = plans.month AND
grouped_results.location = plans.location
I think the problem is that you are using sum(PlanTable.Amount) when grouping. Try using max(PlanTable.Amount) instead.
select
p.Year,
p.Month,
p.Location,
sum(a.Amount) as actual_amount,
max(p.Amount) as plan_amount
from
[Plan] p left join Actual a
on year(a.date) = p.year
and month(a.date) = p.Month
and a.Location = p.Location
group by
p.year,
p.month,
p.Location
SQL Fiddle
get year and month from date and use them in join , most dbms has year and month functions you can use according to your DBMS
select year(t1.date) yr,month(t1.date) as monthofyr ,t1.Location,
sum(t1.amount) as actual_amoun,
sum(t2.amount) as planamount
from table1 t1 left join table2 t2 on
month(t1.date)= t2.Month and t1.Location=t2.Location
and year(t1.date)=t2.year
group by year(t1.date) ,month(t1.date),Location

How do I write a query that will only return accounts that have a sale date less than today?

On the surface this seems a simple where clause but given the data below I only want my query to return account B's sales transactions, with a condition stating show me only those accounts that don't have any sales date beyond 2015.
Acct Sale Date
A 2030
A 2018
A 2015
B 2015
B 2014
B 2013
C 2020
C 2015
C 2012
If you need every transaction in your result, you can use NOT EXISTS:
SELECT *
FROM YourTable as T
WHERE NOT EXISTS(SELECT 1 FROM YourTable
WHERE SaleDate >= 2015
AND Acct = T.Acct);
Do a GROUP BY. Use HAVING to only return acct's with last SaleDate <= 2015:
select acct
from tablename
group by acct
having max(SaleDate) <= 2015
If you want all columns, and all rows, for these accounts:
select t.*
from tablename t
join (select acct, max(SaleDate) as maxSaleDate
from tablename
group by acct) t2
on t.acct = t2.acct
where t2.maxSaleDate <= 2015

Aggregation per Date

I have thousands of companies listed but for illustration; I cited 2 companies. I need to produce the column TotalSales in which values are the sum of sales per company , a year prior to its corresponding actual year & quarter.
Company Sales Quarter Year TotalSales QtrYr_Included
ABC Inc. 10,000 1 2010 null Q12009 - Q42009
ABC Inc. 50,000 2 2010 10,000 Q22009 - Q12010
ABC Inc. 35,000 3 2010 60,000 Q32009 - Q22010
ABC Inc. 15,000 4 2010 95,000 Q42009 - Q32010
ABC Inc. 5,000 1 2011 110,000 Q12010 - Q42010
ABC Inc. 10,000 2 2011 105,000 Q22010 - Q12011
SoKor Group 50,000 1 2009 null Q12008 - Q42008
SoKor Group 10,000 2 2009 50,000 Q22008 - Q12009
SoKor Group 10,000 3 2009 60,000 Q32008 - Q22009
SoKor Group 5,000 4 2009 70,000 Q42008 - Q32009
SoKor Group 15,000 1 2010 . Q12009 - Q42009
SoKor Group 20,000 3 2010 . Q22009 - Q12010
Thank you so much.
Here is one way to do it using Sum Over window aggregate
SELECT *,
Sum(sales)
OVER(
partition BY Company
ORDER BY [Year], [Quarter] ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING)
FROM Yourtable
for Older versions
;WITH cte
AS (SELECT Row_number()OVER(partition BY Company ORDER BY [Year], [Quarter]) rn,*
FROM Yourtable a)
SELECT *
FROM cte a
CROSS apply (SELECT Sum (sales) Total_sales
FROM (SELECT TOP 4 sales
FROM cte b
WHERE a.Company = b.Company
AND b.rn < a.rn
ORDER BY [Year] DESC,
[Quarter] DESC)a) cs
#Prdp's solution is valid. However, it would show incorrect results when there are quarters missing for a given company as it will consider whatever row was available before the missing row. A way to avoid such situation is using derived tables to generate all combinations of year,quarter and company. Left joining the original table on to this result would generate 0 sales for the missing quarters. Then use the sum window function to get the sum of sales for the last 4 quarters for each row.
SELECT *
FROM
(SELECT C.COMPANY,
Y.[YEAR],
Q.[QUARTER],
T.SALES,
SUM(COALESCE(T.SALES,0)) OVER(PARTITION BY C.COMPANY
ORDER BY Y.[YEAR], Q.[QUARTER]
ROWS BETWEEN 4 PRECEDING AND 1 PRECEDING) AS PREV_4QTRS_TOTAL
FROM
(SELECT 2008 AS [YEAR]
UNION ALL SELECT 2009
UNION ALL SELECT 2010
UNION ALL SELECT 2011
UNION ALL SELECT 2012
UNION ALL SELECT 2013) Y --Add more years as required or generate them using a recursive cte or a tally table
CROSS JOIN
(SELECT 1 AS [QUARTER]
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4) Q
CROSS JOIN
(SELECT DISTINCT COMPANY
FROM T) C
LEFT JOIN T ON Y.[YEAR]=T.[YEAR]
AND Q.[QUARTER]=T.[QUARTER]
AND C.COMPANY=T.COMPANY
) X
WHERE SALES IS NOT NULL --to filter the result to include only rows from the original table
ORDER BY 1,2,3
Sample Demo