Calculate and display % change for 2 rows in 3rd row - sql-server

I have a table to compare Monthly Sales data on the Year-to-Year basis. This table will get updated every month by replacing the records with sales data for last month-same year and the year before.
Month_Year || Sale_Vol || #Sales || Average_Price
Mar-17 || 1250 || 25 || 50
Mar-18 || 900 || 20 || 45
I need to display % change ((Mar-18)/(Mar-17) - 1)*100 in an additional row as shown below
Month_Year || Sale_Vol || #Sales || Average_Price
Mar-17 || 1250 || 25 || 50
Mar-18 || 900 || 15 || 60
Prcnt_Chng || -28 || -40 || 20
I am not able to figure out how to achieve this. This is what I have written so far but it's not elegant.
SELECT 'Percentage Change',NULL,
100.0 * ((select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)),112),6))-(select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE())-12,0)),112),6)))/(select * from Tbl where [Month_Year] = left(CONVERT(VARCHAR(8),DATEADD(mm,-1,DATEADD(mm,DATEDIFF(mm,0,GETDATE()),0)),112),6));
And also, I get the error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.

first of all, reason of your error is, counts are mismatched. i.e 100 is single substance, which is out of subquery. But in-side of subuquery, you return more than one column. if you correct that, you'll you get table(may be or not which will your resulting table)
I trying to query for you..!
Edited:
select * from yourtable -- get previous table
union
select 'Prcnt_Chng ',( -- to get calculated row
((((select Sale_Vol from yourtable where Month_Year = 'Mar-18')/ --value of Sale_Vol of mar 18
(select Sale_Vol from yourtable where Month_Year = 'Mar-17'))-1) --value of Sale_Vol of mar 18. divided and sub by 1
100), -- mul by 100
((((select [#Sales] from yourtable where Month_Year = 'Mar-18')/ --value of [#Sales] of mar 18
(select [#Sales] from yourtable where Month_Year = 'Mar-17'))-1) --value of [#Sales] of mar 18. divided and sub by 1
100), -- mul by 100
((((select Average_Price from yourtable where Month_Year = 'Mar-18')/ --value of Average_Price of mar 18
(select Average_Price from yourtable where Month_Year = 'Mar-17'))-1) --value of Average_Price of mar 18. divided and sub by 1
100)) -- mul by 100

Considering that you have only two rows in your table, you could try this query
;with cte as (
select
*, rn = row_number() over (order by Month_Year)
from
myTable
)
select * from myTable
union all
select
'Prcnt_Chng', 100 * b.Sale_Vol / a.Sale_Vol - 100
, 100 * b.[#Sales] / a.[#Sales] - 100
, 100 * b.Average_Price / a.Average_Price - 100
from
cte a
join cte b on a.rn + 1 = b.rn
where a.rn = 1

Related

I want to get the accumulated amount every hour in Oracle

`select
order_price,
To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd hh24') as order_date,
SUM(order_price) OVER(ORDER BY To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd
hh24')) as "bth"
from order_tbl
where seller_no=100
order by order_date;`
I got this result.
But the data I want to get is as follows.
20000 / 20220524 15 / 52500
13000 / 20220524 15 / 52500
19500 / 20220524 15 / 52500
19600 / 20220524 16 / 72100
222000 / 20220524 17 / 738700
and even if there is no data...
0 / 20220524 18 / 738700
0 / 20220524 19 / 738700
0 / 20220524 20 / 738700
.
.
.
.
0 / 20220525 10 / 738700
13600 / 20220525 11 / 787300
like this.
I want to get the order_date and bth for every hour even if there is no data.
It's too difficult for me, but how can I do?
i will remove order_price and add distinct later.
You can try this: (read the guide below)
db<>fiddle
WITH all_hour AS (
SELECT LEVEL AS hour
FROM dual
CONNECT BY LEVEL <= 24
),
all_date AS (
SELECT TO_CHAR(DATE'2022-05-24' + LEVEL - 1, 'YYYYMMDD') AS dt
FROM dual
CONNECT BY LEVEL <= (DATE'2022-05-27' - DATE'2022-05-24' + 1)
),
all_date_hour AS (
SELECT dt || ' ' || (CASE WHEN hour < 10 THEN '0' || TO_CHAR(hour) ELSE TO_CHAR(hour) END) AS order_date
FROM all_date
CROSS JOIN all_hour
),
your_order AS (
SELECT
order_price,
TO_CHAR(TO_DATE(order_date,'YYYY-MM-DD HH24:MI:SS'),'YYYYMMDD HH24') AS order_date,
seller_no
FROM order_tbl
),
your_sum AS (
SELECT adh.order_date, SUM(CASE WHEN yo.seller_no = 100 THEN yo.order_price ELSE 0 END) AS bth
FROM all_date_hour adh
LEFT JOIN your_order yo ON adh.order_date = yo.order_date
GROUP BY adh.order_date
)
SELECT order_date, SUM(bth) OVER(ORDER BY order_date) AS bth
FROM your_sum
ORDER BY order_date;
Summary:
(1) Table 1 : all_hour
includes numbers ranging from 1 to 24
(2) Table 2 : all_date
includes dates from '2022-05-24' to '2022-05-27'.
if your prefer range is '2022-01-01' to '2022-12-31', just simply change '2022-05-24'(Line 7 & 9) -> '2022-01-01', and '2022-05-27'(Line 9) -> '2022-12-31'
(3) Table 3 : all_date_hour
includes dates in the format, 'YYYYMMDD HH24', e.g. '20220524 01'
it is a result from cross joining the first and second table
(4) Table 4: your_order
same as your sample table, order_tbl, just reformatting the order_date in the format, 'YYYYMMDD HH24', e.g. '20220524 01'
(5) Table 5: your_sum (NOT ACCUMULATED YET)
simple summation of order_price, group by the order_date
left join is use here so that all dates from all_date_hour is included
any additional conditions can be added inside the case statement (Line 24)
for example, see (Line 24) SUM(CASE WHEN yo.seller_no = 100 AND youradditionalcondition = somecondition THEN yo.order_price ELSE 0 END)
(6) Final Select Query:
accumulated sum is done here using SUM() OVER(ORDER BY *yourexpr*)
You can use this query - explained later:
select
to_date('01/01/2022 00:00:00','dd/mm/yyyy hh24:mi:ss') + all_hours.sequence_hour/24 order_date_all,
order_price,
To_char(order_date, 'yyyymmdd hh24') as order_date_real,
SUM(order_price) OVER(ORDER BY To_char(order_date, 'yyyymmdd hh24')) as "bth"
from order_tbl ,
(SELECT LEVEL sequence_hour
FROM dual
CONNECT BY LEVEL <= 10) all_hours
where trunc((order_date(+)-to_date('01/01/2022 00:00:00','dd/mm/yyyy hh24:mi:ss'))*24) = all_hours.sequence_hour
and seller_no(+)=100
order by 1;
Explanation:
you don't need to perform To_char(to_date(order_date,'YYYY-MM-DD HH24:MI:SS'),'yyyymmdd hh24') - it means that you take "order_date" which is already date and convert it again to date. It is not needed
I have added maximum number of hours - it is the subquery all_hours. Just for the example it is limited to 10, you can change it to any other value
I have added starting point of time from which you want to display the data "01/01/2022" - change it if you want. Pay attention that it appears in 2 places.
I have added an outer join with order_tbl - pay attention for "(+)" in where conditions. If you want to add additional conditions on order_tbl - remember to add the (+) on the right side of the column as I did with "seller_no"

where clause based on a select statement from another table

I need help with a query I am writing. I basically want to select all GRNID's from one table but they have to be between dates in another table.
So I want all GRN's between two dates found in the ABSPeriodEndDate table. To find out the start date for the between clause I need to find the MAX Period then minus 1 and the max year.
To find the end date of the between clause I want I need to find both the max period and year. But I want the DateStamp column to return the results for the between clause.
Any ideas on how to resolve this I can't seem to get it working as I want to.
My query is below:
SELECT tblGRNItem.GRNID
FROM tblGRNItem
INNER JOIN ABSPeriodEndDates ON tblGRNItem.DateCreated = ABSPeriodEndDates.DateStamp
WHERE tblGRNItem.DateCreated BETWEEN
SELECT ABSPeriodEndDates.DateStamp FROM ABSPeriodEndDates WHERE ABSPeriodEndDates.DateStamp = (SELECT MAX(ABSPeriodEndDates.Period)-1 FROM ABSPeriodEndDates)
AND ABSPeriodEndDates.Year = (SELECT MAX(ABSPeriodEndDates.Year)))
AND
SELECT ABSPeriodEndDates.DateStamp FROM ABSPeriodEndDates WHERE ABSPeriodEndDates.DateStamp = (SELECT MAX(ABSPeriodEndDates.Period) FROM ABSPeriodEndDates)
AND ABSPeriodEndDates.Year = (SELECT MAX(ABSPeriodEndDates.Year)))
NOTE: DataStamp in ABSPeriodEndDates is a datetime2 data type and the DateCreated in tblGRNItem is a datetime data type.
Example data:
tblGRNItem ABSPeriodEndDates
GRNID || DateCreated Year || Period || DateStamp
1 || 01/01/2015 2015 || 1 || 01/01/2015 00:00:01
2 || 05/01/2015 2015 || 1 || 01/01/2015 00:00:01
3 || 06/02/2015 2015 || 2 || 01/02/2015 00:00:01
4 || 09/02/2015 2015 || 2 || 01/02/2015 00:00:01
5 || 19/02/2015 2015 || 2 || 01/02/2015 00:00:01
6 || 16/03/2015 2015 || 3 || 01/03/2015 00:00:01
So because the greatest period minus one is 2 and the greatest period is 3 I want to get all GRNID's from tblGRNItem between the datestamp 01/02/2015 00:00:01 and 01/03/2015 00:00:01 bearing in mind the datatype's are datetime in tblGRNitem and datetime2 in ABSPeriodEndDates, so the results should be:
3 || 06/02/2015
4 || 09/02/2015
5 || 19/02/2015
Thanks in advance
Thanks Shnugo that worked but I also resolved it by:
declare #StartDate datetime;
set #StartDate= (SELECT Max(DateStamp)
from ABSPeriodEndDates as P
where P.Period = (SELECT Max(Period) -1 from ABSPeriodEndDates)
and P.Year = (SELECT Max(Year) from ABSPeriodEndDates));
declare #EndDate datetime;
set #EndDate= (SELECT top (1) DateStamp
from ABSPeriodEndDates
order by Period desc, DateStamp desc);
SELECT GRNID
from tblGRNItem
where DateCreated between #StartDate and #EndDate;
Without knowing your data it is impossible to give you the answer.
As it seems there is no connection between ABSPeriodEndDates and tblGrnItem. You just want to find the range of the latest periode, true?
You could do something like this
WITH MaxDat AS
(
SELECT MAX(DateStamp) AS MaxDatFound
FROM ABSPeriodEndDates
)
,MinDat AS
(
SELECT TOP 1 DateStamp AS MinDatFound
FROM ABSPeriodEndDates
WHERE DateStamp<(SELECT MaxDatFound FROM MaxDat)
ORDER BY DateStamp DESC
)
SELECT *
FROM tblGRNItem
WHERE DateCreated BETWEEN (SELECT MinDatFound FROM MinDat) AND (SELECT MaxDatFound FROM MaxDat)

Find the n highest consecutive values in a set of rows

I have some data in a table as follows:
FileDate SumAmount
20150401 90.99
20150401 313
20150403 481.2
20150404 321.27
20150405 103
20150406 25
20150407 180.5
20150408 319.91
20150409 688
20150411 69
20150412 65
20150413 322
20150414 100
20150415 111.97
20150416 979.15
20150417 655.4
20150418 124
20150419 30
20150420 457
20150421 192.6
20150422 191.96
20150423 220
20150424 252.5
20150425 109.1
20150426 135.25
20150427 648.08
20150428 692
20150429 410.99
20150430 170
20150501 166.19
20150502 92
20150503 100
20150504 59
20150505 124.01
20150506 44.5
20150507 331.64
20150508 299.8
I am trying to devise a query that will find the highest 4 consecutive days values in the data.
Essentially, I think I need to partition by date and perform a row numbering over it but I can't seem to get the syntax right to evaluate the values.
So I use -3 in the join conditions since the day itself counts as one. Let me know what you think. Also I use day of year(DY) to ensure that it's only consecutive days and so I don't have to rank the dates manually. Hope this helps!
DECLARE #yourTable TABLE(FileDate DATE ,SumAmount FLOAT);
INSERT INTO #yourTable
VALUES ('20150401',90.99),
('20150402',313),
('20150403',481.2),
('20150404',321.27),
('20150405',103),
('20150406',25),
('20150407',180.5),
('20150408',319.91),
('20150409',688),
('20150411',69),
('20150412',65),
('20150413',322),
('20150414',100),
('20150415',111.97),
('20150416',979.15),
('20150417',655.4),
('20150418',124),
('20150419',30),
('20150420',457),
('20150421',192.6),
('20150422',191.96),
('20150423',220),
('20150424',252.5),
('20150425',109.1),
('20150426',135.25),
('20150427',648.08),
('20150428',692),
('20150429',410.99),
('20150430',170),
('20150501',166.19),
('20150502',92),
('20150503',100),
('20150504',59),
('20150505',124.01),
('20150506',44.5),
('20150507',331.64),
('20150508',299.8);
WITH CTE
AS
(
SELECT YEAR(FileDate) yr,DATEPART(DY,FileDate) dy,fileDate,SumAmount
FROM #yourTable
),
CTE_Max_Sum
AS
(
SELECT TOP 1 A.yr,A.dy,A.FileDate,SUM(B.SumAmount) consec4DaySum
FROM CTE A
INNER JOIN CTE B
ON B.dy BETWEEN A.dy - 3 AND A.dy
AND A.yr = B.yr
GROUP BY A.yr,A.dy,A.FileDate
ORDER BY SUM(B.SumAmount) DESC
)
SELECT A.*,B.consec4DaySum
FROM CTE A
INNER JOIN CTE_Max_Sum B
ON A.dy BETWEEN B.dy - 3 AND B.dy
AND A.yr = B.yr
Results:
yr dy fileDate SumAmount consec4DaySum
----------- ----------- ---------- ---------------------- ----------------------
2015 117 2015-04-27 648.08 1921.07
2015 118 2015-04-28 692 1921.07
2015 119 2015-04-29 410.99 1921.07
2015 120 2015-04-30 170 1921.07
You can use a CTE for that, joining every row with its three following rows (day-wise) and summing up. This Fiddle sadly does not work for me, it runs on my sql server and work for you. Watch out for recursion depth, without WHERE cte.Consecutive < 4 you quickly run into an error.
WITH cte (StartDate, EndDate, Consecutive, SumAmount)
AS (
SELECT t.FileDate, t.FileDate, 1, t.SumAmount FROM dbo.table30194903 t
UNION ALL
SELECT cte.StartDate, t.FileDate, cte.Consecutive + 1, cte.SumAmount + t.SumAmount
FROM dbo.table30194903 t INNER JOIN cte ON DATEADD(DAY, 1, cte.EndDate) = t.FileDate
WHERE cte.Consecutive < 5
)
SELECT *
FROM cte
WHERE cte.Consecutive = 4
ORDER BY cte.SumAmount DESC
EDIT: Had two errors in my query, it summed up wrong rows and showd the last day in the series.
I would like to add an answer using a subquery, however it does take more time compared to my cte...
SELECT t.FileDate, SUM(s.SumAmount)
FROM dbo.table30194903 t
LEFT JOIN dbo.table30194903 s ON t.FileDate <= s.FileDate AND DATEDIFF(DAY, t.FileDate, s.FileDate) < 4
GROUP BY t.FileDate
HAVING COUNT(s.SumAmount) = 4
ORDER BY SUM(s.SumAmount) DESC
I think the simplest way to get this is to use an APPLY to get the number of records in the n days following each row, and then limit this to where there are n dates, this ensures you have consecutive days. You can then just order by the sum and select the top 1:
DECLARE #n INT = 4;
SELECT TOP 1
FirstDate = t.FileDate,
FourDaySum = t2.Amount
FROM dbo.T
CROSS APPLY
( SELECT Amount = SUM(t2.SumAmount),
Dates = COUNT(DISTINCT t2.FileDate)
FROM dbo.T AS t2
WHERE t2.FileDate >= t.FileDate
AND t2.FileDate < DATEADD(DAY, #n, t.FileDate)
) AS t2
WHERE t2.Dates = #n
ORDER BY t2.Amount DESC;
Example on SQL Fiddle
How about a simply while block and sum the values of a range of dates?
DECLARE #startingDate DATETIME, #searchDate DATETIME;
DECLARE #maxSoFar INT, #sum INT, #daysRange INT;
SET #startingDate = convert(datetime, '20150401', 110)
SET #searchDate = #startingDate;
SET #daysRange = 3;
SET #maxSoFar = 0;
WHILE GETDATE()> #searchDate
BEGIN
--PRINT #searchDate
--PRINT DATEADD(DAY,#daysRange,#searchDate)
SELECT #sum = SUM(SumAmount) FROM MyTable WHERE FileDate >= #searchDate AND FileDate <= DATEADD(DAY,#daysRange,#searchDate)
IF #sum > #maxSoFar
BEGIN
SET #maxSoFar = #sum;
END
SET #searchDate = DATEADD(DAY,1,#searchDate)
END

grouping in sql server with for xml

I have a table in the following format
reporting_date interest_payment balance
200401 10 10
200402 20 15
200403 30 20
200404 40 30
200405 50 40
200406 60 50
200407 70 60
i wanted to generate an OUTPUT in the following format :
The output of the query should look like this :
reporting_date interest_payment balance
Q1 -2004 60 10
Q2 -2004 150 30
Q3 -2004 70 60
Q4 -2004 0 0
i.e i wanted to represent data by quarter and year and group by quarter and year for interest_payment column but for balance i need to pick up the value from the first reporting date in that quarter ,so as you can see q1-2004 has 10,15 and 20 but only 10 is accounted as that was the first reporting date in that quarter
I have my query working for interest payment but i am not sure how do i pickup the first reporting value for balance in a quarter
SELECT report_year as "#date",'Q'+CAST(report_quarter+1 as varchar(1)) as "#quarter", SUM(a.balance) as "#balance", SUM(a.interest_payment) as "#interest_payment"
FROM (SELECT *,
(reporting_date%100 - 1)/3 as report_quarter,
reporting_date/100 as report_year
FROM employee) a
GROUP by report_year, report_quarter
order by report_year, report_quarter
First, create all combination of years and quarters. Then use window functions such AS SUM OVER() and ROW_NUMBER() for the interest_payment and balance.
CREATE TABLE #temp(
reporting_date INT,
interest_payment INT,
balance INT
)
INSERT INTO #temp VALUES
(200401, 10, 10), (200402, 20, 15), (200403, 30, 20),
(200404, 40, 30), (200405, 50, 40), (200406, 60, 50),
(200407, 70, 60);
;WITH quarters(yr, qtr) AS(
SELECT
y.N, q.N
FROM(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
) q(N)
CROSS JOIN(
SELECT DISTINCT reporting_date / 100 FROM #temp
) y(N)
)
,GroupedByQtr AS(
SELECT
*,
qtr = (reporting_date % 100 -1) / 3 + 1,
yr = reporting_date / 100,
ss = SUM(interest_payment) OVER(PARTITION BY (reporting_date / 100), ((reporting_date % 100 -1) / 3 + 1)),
rn = ROW_NUMBER() OVER(PARTITION BY (reporting_date / 100), ((reporting_date % 100 -1) / 3 + 1) ORDER BY reporting_date)
FROM #temp
)
SELECT
reporting_date = 'Q' + CONVERT(VARCHAR(1), q.qtr) + ' - ' + CONVERT(VARCHAR(4), q.yr),
interest_payment = ISNULL(g.ss, 0),
balance = ISNULL(g.balance, 0)
FROM quarters q
LEFT JOIN GroupedByQtr g
ON g.qtr = q.qtr
AND g.yr = q.yr
AND g.rn = 1
RESULT
reporting_date interest_payment balance
-------------- ---------------- -----------
Q1 - 2004 60 10
Q2 - 2004 150 30
Q3 - 2004 70 60
Q4 - 2004 0 0

Group by month in 6 month range sql server

I have a query that groups data by month, but there are some months that do not display simply because there is no data to display/group by. Is it possible to return the months and 0s for those months?
Here is my query
DECLARE #IMPORT_DATE AS DATETIME
SET #IMPORT_DATE = GETDATE()
SELECT COALESCE(COUNT(*),0), RIGHT(YEAR_MONTH_VALUE,2)
FROM VW_CALLS
WHERE CLIENT_ID = 2
AND START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE
ORDER BY YEAR_MONTH_VALUE
And it returns this:
(No column name) (No column name)
740 11
1929 12
3864 01
But I would like this:
(No column name) (No column name)
0 08
0 09
0 10
740 11
1929 12
3864 01
You can use a recursive CTE like this:
DECLARE #MonthStart CHAR(2) = '08'
;WITH MonthsRange AS (
SELECT m = #MonthStart, rn = 1
UNION ALL
SELECT m = (CASE WHEN m = '12' THEN CAST('01' AS CHAR(2))
ELSE CAST(REPLICATE('0', 2 - LEN(CAST((CAST(m AS INT) + 1) AS CHAR(2)))) +
CAST((CAST(m AS INT) + 1) AS CHAR(2)) AS CHAR(2))
END),
rn = rn + 1
FROM MonthsRange
WHERE rn < 6
)
SELECT *
FROM MonthsRange
in order to get all months you want to include in your final result set:
m rn
=======
08 1
09 2
10 3
11 4
12 5
01 6
Now you can left join the above CTE against you query to get the result set you want:
;WITH MonthsRange AS (
... cte statements here
)
SELECT COALESCE(vw.cnt, 0) AS cnt, mr.m AS [Month]
FROM MonthsRange AS mr
LEFT JOIN (
SELECT COALESCE(COUNT(*),0) AS cnt, RIGHT(YEAR_MONTH_VALUE,2) AS m,
YEAR_MONTH_VALUE AS ym
FROM VW_CALLS
WHERE CLIENT_ID = 2 AND
START_DATETIME BETWEEN DATEADD("m", -5, #IMPORT_DATE) AND #IMPORT_DATE
GROUP BY YEAR_MONTH_VALUE) AS vw ON mr.m = vw.m
ORDER BY vw.ym

Resources