Add extra column in sql to show ratio with previous row - sql

I have a SQL table with a format like this:
SELECT period_id, amount FROM table;
+--------------------+
| period_id | amount |
+-----------+--------+
| 1 | 12 |
| 2 | 11 |
| 3 | 15 |
| 4 | 20 |
| .. | .. |
+-----------+--------+
I'd like to add an extra column (just in my select statement) that calculates the growth ratio with the previous amount, like so:
SELECT period_id, amount, [insert formula here] AS growth FROM table;
+-----------------------------+
| period_id | amount | growth |
+-----------+-----------------+
| 1 | 12 | |
| 2 | 11 | 0.91 | <-- 11/12
| 3 | 15 | 1.36 | <-- 15/11
| 4 | 20 | 1.33 | <-- 20/15
| .. | .. | .. |
+-----------+-----------------+
Just need to work out how to perform the operation with the line before. Not interested in adding to the table. Any help appreciated :)
** also want to point out that period_id is in order but not necessarily increasing incrementally

The window function Lag() would be a good fit here.
You may notice that we use (amount+0.0). This is done just in case AMOUNT is an INT, and NullIf() to avoid the dreaded divide by zero
Declare #YourTable table (period_id int,amount int)
Insert Into #YourTable values
( 1,12),
( 2,11),
( 3,15),
( 4,20)
Select period_id
,amount
,growth = cast((amount+0.0) / NullIf(lag(amount,1) over (Order By Period_ID),0) as decimal(10,2))
From #YourTable
Returns
period_id amount growth
1 12 NULL
2 11 0.92
3 15 1.36
4 20 1.33

If you are using SQL Server 2012+ then go for John Cappelletti answer.
And If you are also less blessed like me then this below code work for you in the 2008 version too.
Declare #YourTable table (period_id int,amount int)
Insert Into #YourTable values
( 1,12),
( 2,11),
( 3,15),
( 4,20)
;WITH CTE AS (
SELECT ROW_NUMBER() OVER (
ORDER BY period_id
) SNO
,period_id
,amount
FROM #YourTable
)
SELECT C1.period_id
,C1.amount
,CASE
WHEN C2.amount IS NOT NULL AND C2.amount<>0
THEN CAST(C1.amount / CAST(C2.amount AS FLOAT) AS DECIMAL(18, 2))
END AS growth
FROM CTE C1
LEFT JOIN CTE C2 ON C1.SNO = C2.SNO + 1
Which works same as LAG.
+-----------+--------+--------+
| period_id | amount | growth |
+-----------+--------+--------+
| 1 | 12 | NULL |
| 2 | 11 | 0.92 |
| 3 | 15 | 1.36 |
| 4 | 20 | 1.33 |
+-----------+--------+--------+

Related

Create calculated value based on calculated value inside previous row

I'm trying to find a way to apply monthly percentage changes to forecast pricing. I set my problem up in excel to make it a bit more clear. I'm using SQL Server 2017.
We'll say all months before 9/1/18 are historical and 9/1/18 and beyond are forecasts. I need to calculate the forecast price (shaded in yellow on the sample data) using...
Forecast Price = (Previous Row Forecast Price * Pct Change) + Previous Row Forecast Price
Just to be clear, the yellow shaded prices do not exist in my data yet. That is what I am trying to have my query calculate. Since this is monthly percentage change, each row depends on the row before and goes beyond a single ROW_NUMBER/PARTITION solution because we have to use the previous calculated price. Clearly what is an easy sequential calculation in excel is a bit more difficult here. Any idea how to create forecasted price column in SQL?
You need to use a recursive CTE. That is one of the easier ways to look at the value of a calculated value from previous row:
DECLARE #t TABLE(Date DATE, ID VARCHAR(10), Price DECIMAL(10, 2), PctChange DECIMAL(10, 2));
INSERT INTO #t VALUES
('2018-01-01', 'ABC', 100, NULL),
('2018-01-02', 'ABC', 150, 50.00),
('2018-01-03', 'ABC', 130, -13.33),
('2018-01-04', 'ABC', 120, -07.69),
('2018-01-05', 'ABC', 110, -08.33),
('2018-01-06', 'ABC', 120, 9.09),
('2018-01-07', 'ABC', 120, 0.00),
('2018-01-08', 'ABC', 100, -16.67),
('2018-01-09', 'ABC', NULL, -07.21),
('2018-01-10', 'ABC', NULL, 1.31),
('2018-01-11', 'ABC', NULL, 6.38),
('2018-01-12', 'ABC', NULL, -30.00),
('2019-01-01', 'ABC', NULL, 14.29),
('2019-01-02', 'ABC', NULL, 5.27);
WITH ncte AS (
-- number the rows sequentially without gaps
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date) AS rn
FROM #t
), rcte AS (
-- find first row in each group
SELECT *, Price AS ForecastedPrice
FROM ncte AS base
WHERE rn = 1
UNION ALL
-- find next row for each group from prev rows
SELECT curr.*, CAST(prev.ForecastedPrice * (1 + curr.PctChange / 100) AS DECIMAL(10, 2))
FROM ncte AS curr
INNER JOIN rcte AS prev ON curr.ID = prev.ID AND curr.rn = prev.rn + 1
)
SELECT *
FROM rcte
ORDER BY ID, rn
Result:
| Date | ID | Price | PctChange | rn | ForecastedPrice |
|------------|-----|--------|-----------|----|-----------------|
| 2018-01-01 | ABC | 100.00 | NULL | 1 | 100.00 |
| 2018-01-02 | ABC | 150.00 | 50.00 | 2 | 150.00 |
| 2018-01-03 | ABC | 130.00 | -13.33 | 3 | 130.01 |
| 2018-01-04 | ABC | 120.00 | -7.69 | 4 | 120.01 |
| 2018-01-05 | ABC | 110.00 | -8.33 | 5 | 110.01 |
| 2018-01-06 | ABC | 120.00 | 9.09 | 6 | 120.01 |
| 2018-01-07 | ABC | 120.00 | 0.00 | 7 | 120.01 |
| 2018-01-08 | ABC | 100.00 | -16.67 | 8 | 100.00 |
| 2018-01-09 | ABC | NULL | -7.21 | 9 | 92.79 |
| 2018-01-10 | ABC | NULL | 1.31 | 10 | 94.01 |
| 2018-01-11 | ABC | NULL | 6.38 | 11 | 100.01 |
| 2018-01-12 | ABC | NULL | -30.00 | 12 | 70.01 |
| 2019-01-01 | ABC | NULL | 14.29 | 13 | 80.01 |
| 2019-01-02 | ABC | NULL | 5.27 | 14 | 84.23 |
Demo on DB Fiddle
In SQL Server you can access values of previous / next rows by using the windowing functions LAG and LEAD. You need to define the order of the rows by specifying it in the OVER clause. You may need to wrap the select query, that returns prev/next values in a derived table or CTE, and then select from it and calculate your forecasts.
with cte as (SELECT [Date], Price, LAG(Price, 1) over(order by [Date]) as PrevPrice from TABLE)
select [Date], Price, Price - PrevPrice as PriceChange from cte

How to get this kind of data?

I have data some thing like this:
+---------+---------+---------+-------+
| MAXIMUM | MINIMUM | SENSORS | TIME |
+---------+---------+---------+-------+
| 10 | 12 | 14 | 13:12 |
| 80 | 70 | 100 | 14:54 |
+---------+---------+---------+-------+
But I need something like this:
+---------+-------+
| X | Y |
+---------+-------+
| MAXIMUM | 10 |
| MINIMUM | 12 |
| SENSORS | 14 |
| TIME | 13:12 |
| MAXIMUM | 80 |
| MINIMUM | 70 |
| SENSORS | 100 |
| TIME | 14:54 |
+---------+-------+
How to get this kind of data is there any possibility to get data?
Just another option
Example
Select B.*
From YourTable
Cross Apply (values ('MAXIMUM',convert(nvarchar(50),MAXIMUM))
,('MINIMUM',convert(nvarchar(50),MINIMUM))
,('SENSORS',SENSORS)
,('TIME' ,convert(nvarchar(50),[TIME],108))
) B(x,y)
Returns
x y
MAXIMUM 10
MINIMUM 12
SENSORS 14
TIME 13:12:00
MAXIMUM 80
MINIMUM 70
SENSORS 100
TIME 14:54:00
You can use UNPIVOT:
declare #tmp table (MAXIMUM nvarchar(10), MINIMUM nvarchar(10), SENSORS nvarchar(10), [TIME] nvarchar(10))
insert into #tmp select 10,12,14 ,'13:12'
insert into #tmp select 80,70,100,'14:54'
select u.x,u.y
from #tmp s
unpivot
(
[y]
for [x] in ([MAXIMUM],[MINIMUM],[SENSORS],[TIME])
) u;
Results:

How to create date range row from start date and a number in Prestodb SQL?

I have this kind of table in prestodb which has a start_date field and a incremental counter.
| num | start_date | val |
|-----|------------|-----|
| 2 | 2017-03-01 | 100 |
| 3 | 2017-03-05 | 233 |
How can I convert it into this kind of table using presto sql?
| date | val |
|------------|-----|
| 2017-03-01 | 100 |
| 2017-03-02 | 100 |
| 2017-03-05 | 233 |
| 2017-03-06 | 233 |
| 2017-03-07 | 233 |
Thanks!
select date_add ('day',s.n,t.start_date) as date
,t.val
from mytable t
cross join unnest(sequence(0,num-1)) s (n)
;
date | val
------------+-----
2017-03-01 | 100
2017-03-02 | 100
2017-03-05 | 233
2017-03-06 | 233
2017-03-07 | 233
Try this for Sql Server
Declare #s_id int = 1
;WITH dates AS
(
SELECT start_date as Date,#s_id as times,num as value,val
from #t
UNION ALL
SELECT DATEADD(d,1,[Date]),times+1 as times,value,val
FROM dates
WHERE times < value
)
select Date,val from dates
order by val,date
Please note that the test table for this statement can be created by using...
declare #t ( num int,start_date date,val int)
insert into #t values (2,'2017-03-01',100)
insert into #t values (3,'2017-03-05',233)
Please replace #t in the main statement with the name of your table when testing the main statement against your table.
The following image shows the results of running the main statement against the test table...

How to Create Pivot in MS Access to avoid null values for one type of record?

Can anyone please help me in creating the below pivot table in MS-Access.
Data table
value | Rank | Type
1.5 | 5 | alpha
2.4 | 4 | alpha
3.6 | 3 | alpha
4.63 | 2 | alpha
5.36 | 1 | alpha
Required Pivot is
Type | 5 | 4 | 3 | 2 | 1
alpha|1.5 |2.4 |3.6 |4.63 |5.36
I have tried the below query
TRANSFORM [Value]
SELECT [Type]
FROM tbl
GROUP BY [Type], [Value]
PIVOT [Rank];
and getting resultset as
Type | 1 | 2 | 3 | 4 | 5
alpha| | | | |1.5
alpha| | | |2.4|
alpha| | |3.6| |
alpha| |4.63| | |
alpha|5.36| | | |
Could anyone please help me in updating this query to get the required result.
Thanks a ton.
Honey
To get all "alpha" values in one row, you must remove Value from the GROUP BY section.
Then you need an aggregation function for it - if you are sure to have only one value per rank, First() does the job.
To get the 5-4-3-2-1 order, add an ORDER BY clause.
TRANSFORM First([Value])
SELECT [Type]
FROM tPivot
GROUP BY [Type]
ORDER BY [Rank] DESC
PIVOT [Rank];
Edit: it works for multiple Types
+-------+-----+-----+-----+------+------+
| Type | 5 | 4 | 3 | 2 | 1 |
+-------+-----+-----+-----+------+------+
| alpha | 1,5 | 2,4 | 3,6 | | 5,36 |
| beta | 999 | | | 4,63 | 66 |
+-------+-----+-----+-----+------+------+
CREATE TABLE #A
(
value NUMERIC(22,6), Rank INT, Type VARCHAR(10)
)
INSERT INTO #A VALUES
(1.5, 5,'alpha'),
(2.4,4,'alpha'),
(3.6,3,'alpha'),
(4.63,2,'alpha'),
(5.36,1,'alpha')
SELECT [5],[4],[3],[2],[1]
FROM
(select value,RANK,TYPE from #A ) AS SourceTable
PIVOT
(
MAX(value)
FOR RANK IN ([1],[2],[3],[4],[5])
) AS PivotTable;

Create a sub query for sum data as a new column in SQL Server

Suppose that I have a table name as tblTemp which has data as below:
| ID | AMOUNT |
----------------
| 1 | 10 |
| 1-1 | 20 |
| 1-2 | 30 |
| 1-3 | 40 |
| 2 | 50 |
| 3 | 60 |
| 4 | 70 |
| 4-1 | 80 |
| 5 | 90 |
| 6 | 100 |
ID will be format as X (without dash) if it's only one ID or (X-Y) format if new ID (Y) is child of (X).
I want to add a new column (Total Amount) to output as below:
| ID | AMOUNT | Total Amount |
---------------------------------
| 1 | 10 | 100 |
| 1-1 | 20 | 100 |
| 1-2 | 30 | 100 |
| 1-3 | 40 | 100 |
| 2 | 50 | 50 |
| 3 | 60 | 60 |
| 4 | 70 | 150 |
| 4-1 | 80 | 150 |
| 5 | 90 | 90 |
| 6 | 100 | 100 |
The "Total Amount" column is the calculate column which sum value in Amount column that the (X) in ID column is the same.
In order to get parent ID (X), I use the following SQL:
SELECT
ID, SUBSTRING (ID, 1,
IIF (CHARINDEX('-', ID) = 0,
len(ID),
CHARINDEX('-', ID) - 1)
), Amount
FROM
tblTemp
How Can I query like this in SQL Server 2012?
You can use sqlfiddle here to test it.
Thank You
Pengan
You have already done most of the work. To get the final result you can use your existing query and make it a subquery or use a CTE, then use sum() over() to get the result:
;with cte as
(
SELECT
ID,
SUBSTRING (ID, 1,
IIF (CHARINDEX('-', ID) = 0,
len(ID),
CHARINDEX('-', ID) - 1)
) id_val, Amount
FROM tblTemp
)
select id, amount, sum(amount) over(partition by id_val) total
from cte
See SQL Fiddle with Demo
You can do this using the sum() window function:
select id, amount,
SUM(amount) over (partition by (case when id like '%-%'
then left (id, charindex('-', id) - 1)
else id
end)
) as TotalAmount
from tblTemp t