check whether values lies between or not in SQL - sql

These are my 2 tables
table 2:
date_val
yr_num
yr_wk_num
day_wk_num
yr_wk_nm
day
mo_num
20200808
2020
32
6
202032
Saturday
08
20200809
2020
32
7
202032
Sunday
08
20200810
2020
33
1
202033
Monday
08
20200811
2020
33
2
202033
Tuesday
08
20200812
2020
33
3
202033
Wednesday
08
table1:
sku_id
dateval
sales
ab124
20210603
10
ab124
20210502
20
ab123
20210606
30
Need to check sales is with in + or - 30% of 2 month avg sales
with CTE
as
(
select * from table1 where dateval >= dateadd(mm, -2, dateval)
)
select dateval, sum(sales) as [Total Sales], avg(sales) as [Average Sales] from CTE group by dateval order by 1
I tried below also...
with CTE
as
(
select * from table1 t1 left join table2 t2 on t1.dateval = t2.date_val where t2.date_val >= dateadd(mm, -2, t1.dateval)
)
select dateval,sum(sales) as [Total Sales], avg(sales) as [Average Sales] from CTE group by dateval order by 1
here am doing filtering within table1 but i need to use table 2 to get filtered for past two months and get avg sales.
Next, i need to do +30% to that result avg and -30% result avg and check whether my sales is withn avg sales( avg30% above or below) or not if yes '1' if not '0'
For Ex:
Historic 2 month avg sales 100.
(+30% of avg sales is 130)
(-30% of avg sales is 70)
if sales is 120. i need to check 120 lies between 70 to 130.
please suggest me how to achieve

Related

SQL query to compare budget to actual expenses

I'm struggling to build a query comparing budget to actual expense items.
The budget table has a single record per month/category on the first day of the month whereas the expense table has many records throughout the days of the month.
My desired result:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
49
2021
1
monthly
42
87
2021
2
daily
101
36
2021
2
monthly
55
82
What I'm getting:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
85
2021
1
monthly
42
169
2021
2
daily
101
85
2021
2
monthly
55
169
The amounts in "sumExpenseAmt" are wrong AND they're repeating.
(85 is the sum of all expense-daily items (jan + feb): 40 + 9 + 32 + 4)
(169 is the sum of all expense-monthly items (jan + feb): 83 + 4 + 75 +7)
MY SQL:
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category"
, "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
table: budget
date
category
budgetAmt
2021-01-01
daily
100
2021-01-01
monthly
42
2021-02-01
daily
101
2021-02-01
monthly
55
table: expense
date
category
expenseAmt
2021-01-04
daily
40
2021-01-07
daily
9
2021-01-08
monthly
83
2021-01-25
monthly
4
2021-02-01
daily
32
2021-02-05
daily
4
2021-02-15
monthly
75
2021-02-20
monthly
7
I've tried aggregating the expense table with a query and feeding the result into my initial SQL query, but that gives me the same result.
query: qry_summary_expense
date
category
budgetAmt
2021-01-01
daily
49
2021-01-01
monthly
87
2021-02-01
daily
36
2021-02-01
monthly
82
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category", "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "qry_summary_expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
I'd join on both month and category
select year(b.date) as year,
month(b.date) as month,
b.category,
avg(budgetAmt) as budgetAmt,
sum(expenseAmt) as expenseAmt
from expense e
join budget b
on (month(b.date) = month(e.date)
and b.category = e.category)
group by year(b.date), month(b.date), b.category
You need to join the two tables on category AND month (using eomonth (end of month) does the trick).
SELECT Year(Eomonth(e.date)) AS year,
Month(Eomonth(e.date)) AS month,
e.category,
Avg(budgetamt) AS budgetAmt,
Sum(expenseamt) AS sumExpenseAmt
FROM expense e
INNER JOIN budget b
ON Eomonth(e.date) = Eomonth(b.date)
AND e.category = b.category
GROUP BY e.category,
Eomonth(e.date);
Fiddle
Alternatively you can use left or right joins based on your need.
select b.Year,b.Month,b.category,Budget, Expenses
from(
Select year(date) [Year] ,month(date) [Month] ,category,sum(budgetAmt) Budget
from budget
group by year(date),month(date),category
) b
Join
(
Select year(date) [Year] ,month(date) [Month] ,category,sum(expenseAmt) Expenses
from expense
group by year(date),month(date),category
) e
on b.Month = e.Month and b.Year = e.Year and b.category = e.category
Another Approach Using EOMONTH Function:
select Year(a.dates) Year ,month(a.dates) Month,a.category,sum(Budget) Budget, sum(Expenses) Expenses
from(
Select EOMONTH(date) dates,category,sum(budgetAmt) Budget
from #budget
group by EOMONTH(date),category
) a
Join
(
Select EOMONTH(date) dates,category,sum(expenseAmt) Expenses
from #expense
group by EOMONTH(date),category
) b
on a.dates = b.dates and a.category = b.category
group by Year(a.dates),month(a.dates),a.category

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 to Get Monthly/Quarterly Sales Percent

I Have a table of Monthly Sales
Quarter Month Monthly Sales
1 Jan 586
1 Feb 204
1 Mar 465
2 Apr 684
2 May 756
2 Jun 97
Now I want a result a below
Quarter Month Monthly Sales Total Sales% Quarterly Sales%
1 Jan 586 20.98% 45.25%
1 Feb 204 7.30% 16.25%
1 Mar 465 16.65% 37.05%
2 Apr 684 24.49% 44.50%
2 May 756 27.07% 49.18%
2 Jun 97 3.47 6.31%
Total Sales% = Monthly Sales/Sum (Monthly Sales)
Quarterly Sales% = Monthly Sales/ Quarterly Sales
How Do i Get this output in SQL?
Using CROSS APPLY:
SELECT
s.*,
[Total Sales %] = CAST(CAST(s.MonthlySales/t.Total * 100.00 AS DECIMAL(5, 2)) AS VARCHAR(5)) +'%',
[Quarterly Sales %] = CAST(CAST(s.MonthlySales/q.QtrTotal* 100.00 AS DECIMAL(5, 2)) AS VARCHAR(5)) +'%'
FROM Sales s
CROSS APPLY(
SELECT Total = SUM(MonthlySales) * 1.0
FROM Sales
) t
CROSS APPLY(
SELECT QtrTotal = SUM(MonthlySales) * 1.0
FROM Sales
WHERE Quarter = s.Quarter
GROUP BY Quarter
)q
If you are using Oracle you should be using the ratio_to_report analytic function. (http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions124.htm)
select quarter,
month,
monthly_sales,
round(ratio_to_report(monthly_sales) over() * 100, 2) as total_sales_pct,
round(ratio_to_report(monthly_sales) over(partition by quarter) * 100, 2) as qtr_sales_pct
from monthly_sales;
Fiddle: http://sqlfiddle.com/#!4/71aeb7/1/0
The above assumes you're selecting data for just one year as the analytic function will interpret the given result set. If your query spans multiple years you need to additionally partition by whatever column represents the year in your real table.

SQL spread month value into weeks

I have a table where I have values by month and I want to spread these values by week, taking into account that weeks that spread into two month need to take part of the value of each of the month and weight on the number of days that correspond to each month.
For example I have the table with a different price of steel by month
Product Month Price
------------------------------------
Steel 1/Jan/2014 100
Steel 1/Feb/2014 200
Steel 1/Mar/2014 300
I need to convert it into weeks as follows
Product Week Price
-------------------------------------------
Steel 06-Jan-14 100
Steel 13-Jan-14 100
Steel 20-Jan-14 100
Steel 27-Jan-14 128.57
Steel 03-Feb-14 200
Steel 10-Feb-14 200
Steel 17-Feb-14 200
As you see above, the week that overlaps between Jan and Feb needs to be calculated as follows
(100*5/7)+(200*2/7)
This takes into account tha the week of the 27th has 5 days that fall into Jan and 2 into Feb.
Is there any possible way to create a query in SQL that would achieve this?
I tried the following
First attempt:
select
WD.week,
PM.PRICE,
DATEADD(m,1,PM.Month),
SUM(PM.PRICE/7) * COUNT(*)
from
( select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE
)PM
join
( select '2014-1-20' as week
union
select '2014-1-27' as week
union
select '2014-2-3' as week
)WD
ON WD.week>=PM.Month
AND WD.week < DATEADD(m,1,PM.Month)
group by
WD.week,PM.PRICE, DATEADD(m,1,PM.Month)
This gives me the following
week PRICE
2014-1-20 100 2014-02-01 00:00:00.000 14
2014-1-27 100 2014-02-01 00:00:00.000 14
2014-2-3 200 2014-03-01 00:00:00.000 28
I tried also the following
;with x as (
select price,
datepart(week,dateadd(day, n.n-2, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from
(select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE) t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from
(select '2014-1-1' as Month, 100 as PRICE
union
select '2014-2-1' as Month, 200 as PRICE)
t
where t1.month = t.month) ndm
inner join
(SELECT (a.Number * 256) + b.Number AS N FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)) n --numbers
on n.n <= ndm.nd
)
select min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by wk
having count(*) = 7
order by wk
This gimes me the following
week price
2014-01-07 00:00:00.000 100.00
2014-01-14 00:00:00.000 100.00
2014-01-21 00:00:00.000 100.00
2014-02-04 00:00:00.000 200.00
2014-02-11 00:00:00.000 200.00
2014-02-18 00:00:00.000 200.00
Thanks
If you have a calendar table it's a simple join:
SELECT
product,
calendar_date - (day_of_week-1) AS week,
SUM(price/7) * COUNT(*)
FROM prices AS p
JOIN calendar AS c
ON c.calendar_date >= month
AND c.calendar_date < DATEADD(m,1,month)
GROUP BY product,
calendar_date - (day_of_week-1)
This could be further simplified to join only to mondays and then do some more date arithmetic in a CASE to get 7 or less days.
Edit:
Your last query returned jan 31st two times, you need to remove the =from on n.n < ndm.nd. And as you seem to work with ISO weeks you better change the DATEPART to avoid problems with different DATEFIRST settings.
Based on your last query I created a fiddle.
;with x as (
select price,
datepart(isowk,dateadd(day, n.n, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from
(select '2014-1-1' as Month, 100.00 as PRICE
union
select '2014-2-1' as Month, 200.00 as PRICE) t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from
(select '2014-1-1' as Month, 100.00 as PRICE
union
select '2014-2-1' as Month, 200.00 as PRICE)
t
where t1.month = t.month) ndm
inner join
(SELECT (a.Number * 256) + b.Number AS N FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)) n --numbers
on n.n < ndm.nd
) select min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by wk
having count(*) = 7
order by wk
Of course the dates might be from multiple years, so you need to GROUP BY by the year, too.
Actually, you need to spred it over days, and then get the averages by week. To get the days we'll use the Numbers table.
;with x as (
select product, price,
datepart(week,dateadd(day, n.n-2, t1.month)) wk,
dateadd(day, n.n-1, t1.month) dt
from #t t1
cross apply (
select datediff(day, t.month, dateadd(month, 1, t.month)) nd
from #t t
where t1.month = t.month and t1.product = t.product) ndm
inner join numbers n on n.n <= ndm.nd
)
select product, min(dt) as week, cast(sum(price)/count(*) as decimal(9,2)) as price
from x
group by product, wk
having count(*) = 7
order by product, wk
The result of datepart(week,dateadd(day, n.n-2, t1.month)) expression depends on SET DATEFIRST so you might need to adjust accordingly.

sql server calculate cumulative number per month for different year

I have a table with "date" column. Each row represents a survey.
date
11/19/2013 5:51:41 PM
11/22/2013 1:30:38 PM
11/23/2013 3:09:17 PM
12/2/2014 5:24:17 PM
12/25/2014 11:42:56 AM
1/6/2014 2:24:49 PM
I want to count the number of survey per month cumulatively. As you see from the above table, there are 3 surveys for Nov 2013, 2 surveys for Dec 2013, 1 survey for Jan 2014. The cumulative number of survey per month would be:
month | year | number_of_survey
11 | 2013 | 3
12 | 2013 | 5
1 | 2014 | 6
I have this query which shows correct number of surveys for 2013, and number of survey for 2014 is not cumulative.
with SurveyPerMonth as -- no of Survey per month
(
select datepart(month, s.date) as month,
datepart(year, s.date) as year,
count(*) as no_of_surveys
from myTable s
group by datepart(year, s.date), datepart(month, s.date)
)
select p1.month, p1.year, sum(p2.no_of_surveys) as surveys -- cumulatively
from SurveyPerMonth p1
inner join SurveyPerMonth p2 on p1.month >= p2.month and p1.year>=p2.year **-- the problem is probably comes from this line of code**
group by p1.month, p1.year
order by p1.year, p1.month;
This query returns:
month | year | surveys
11 | 2013 | 3
12 | 2013 | 5
1 | 2014 | 1 // 2014 is not cumulative
How can I calculate cumulative number of surveys per month for 2014 as well?
Something like this ?
SELECT date = create_date INTO #myTable FROM master.sys.objects
;WITH perMonth ( [year], [month], [no_of_surveys])
AS (SELECT DatePart(year, s.date) ,
DatePart(month, s.date),
COUNT(*)
FROM #myTable s
GROUP BY datepart(year, s.date),
datepart(month, s.date))
SELECT [year],
[month],
[no_of_surveys] = ( SELECT SUM([no_of_surveys])
FROM perMonth agg
WHERE (agg.[year] < pm.[year])
OR (agg.[year] = pm.[year] AND agg.[month] <= pm.[month]))
FROM perMonth pm
ORDER BY [year], [month]
Edit: seems I missed the ball with < and >, fixed it and added small example
'--This should work.I have added a new column 'monthyear'
with surveypermonth as -- no of survey per month
(
select datepart(month, s.date) as month,
datepart(year, s.date) as year,
datepart(year, s.date) *100 + datepart(month, s.date) as monthyear,
count(*) as no_of_surveys
from test s
group by datepart(year, s.date), datepart(month, s.date),datepart(year, s.date)*100 + datepart(month, s.date)
)
select a.month,substring(cast(monthyear as varchar(6)),1,4) as year,surveys from
(
select p1.month, p1.monthyear as monthyear, sum(p2.no_of_surveys) as surveys
from surveypermonth p1
inner join surveypermonth p2 on p1.monthyear>=p2.monthyear
group by p1.month, p1.monthyear
--order by p1.monthyear, p1.month
)a