Update all the following values in column depending on previous value - sql-server

I am looking to do a "cascading" update on a column with all null values except the "first". The order is determined by date ascending.
I have this table
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[StockA]
(
[date] [date] NOT NULL,
[PercentChange] [decimal](19, 6) NULL,
[Price] [decimal](16, 2) NULL
) ON [PRIMARY]
GO
INSERT INTO [dbo].[StockA] ([date], [PercentChange], [Price])
VALUES (CAST(N'2021-08-19' AS Date), CAST(-0.005100 AS Decimal(19, 6)), NULL)
INSERT INTO [dbo].[StockA] ([date], [PercentChange], [Price])
VALUES (CAST(N'2021-08-20' AS Date), CAST(0.013000 AS Decimal(19, 6)), NULL)
INSERT INTO [dbo].[StockA] ([date], [PercentChange], [Price])
VALUES (CAST(N'2021-08-23' AS Date), CAST(0.015400 AS Decimal(19, 6)), NULL)
INSERT INTO [dbo].[StockA] ([date], [PercentChange], [Price])
VALUES (CAST(N'2021-08-24' AS Date), CAST(0.009500 AS Decimal(19, 6)), NULL)
INSERT INTO [dbo].[StockA] ([date], [PercentChange], [Price])
VALUES (CAST(N'2021-08-18' AS Date), CAST(0.010000 AS Decimal(19, 6)), CAST(100.00 AS Decimal(16, 2)))
GO
SELECT *
FROM StockA
ORDER BY date ASC
The four NULL values should be calculated based on the previous, starting with first NULL value on date 2021-08-19 gives is a calculation of 100(this is the previous value) * (1+PercentChange) = 99.49
I have tried this
WITH CTE AS
(
SELECT
date, PercentChange, Price,
(LAG(Price) OVER (Order by date))*(1+PercentChange) AS NextPrice
FROM
StockA
)
UPDATE CTE
SET Price = NextPrice;
which incorrectly gives me this:
The calculation is correct. The row 2021-08-19 should be 99.49. Nothing wrong there. But something is wrong with my update statement apparently. If I run my CTE query again, the correct value is added to the next row and disappearing from the previous.
Does anybody know how to fill the entire "Price" column, so that there are no NULLS, with calculated values that are based on the "first" (ordered by date ascending) Price?
I am using SQL Server 2019.

One method uses logs to emulate a product aggregation function:
with toupdate as (
select a.*,
exp(sum(log(1 + percent_change)) over (order by date)) / (1 + first_value(percent_change) over (order by date)) as factor,
first_value(price) over (order by date) as orig_price
from stockA
)
update toupdate
set price = orig_price * factor
where price is null;

Related

How to cast nulls to blank and those with number to decimal 3 places?

If I want the output to be blank if there is no gpa, and rounded to 3 decimal places if there is a gpa - how do I get that in one statement?
Here are the 2 statements I have come up with that I am having trouble combining as one (for blank if answer is null and 3 places decimal if there is a gpa).
decimal 3 places:
NULLIF(cast(round(termgpa.gpa, 3) AS DECIMAL(18, 3)), 0)
if null then blank:
isnull(cast(termgpa.gpa as varchar), ' ')
How about using a case statement. Also, if you want the column to contain both blanks and decimal numbers you will have to cast it as a varchar. The below statement shows both null and non null.
declare #gpa decimal(18,5)
set #gpa = '5.789456'
select
case
when #gpa is null then ''
else cast(cast(round(#gpa, 3) AS DECIMAL(18, 3)) as varchar)
end as gpa
--returns 5.789
set #gpa = null
select
case
when #gpa is null then ''
else cast(cast(round(#gpa, 3) AS DECIMAL(18, 3)) as varchar)
end as gpa
--returns blank
CREATE TABLE [dbo].[termgpa](
[id] [int] NULL,
[gpa] [decimal](18, 3) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (1, CAST(20.000 AS Decimal(18, 3)))
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (2, NULL)
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (3, CAST(15.761 AS Decimal(18, 3)))
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (4, CAST(45.650 AS Decimal(18, 3)))
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (5, NULL)
GO
INSERT [dbo].[termgpa] ([id], [gpa]) VALUES (6, CAST(78.100 AS Decimal(18, 3)))
GO
I have created sample table with just two columns. now you can either choose to covert your decimal column to varchar or you can set 0 instead of null value as follows
SELECT ISNULL(CAST(gpa AS VARCHAR(20)),'') FROM dbo.termgpa
or you can set 0 or -1 whenever gpa is null and handle the same in code
SELECT CASE WHEN gpa IS NULL THEN '0' ELSE CAST(gpa AS DECIMAL(18,3)) END FROM dbo.termgpa

SQL Server : select data from table based on certain timestamp

I have a table that stores data in a one minute timestamp from each other and I'd like to create a select command, that would fetch data from the :59 minute mark from each hour of a requested period, for example from 01.01.2020 to 01.02.2020.
How could I do this? I attach a sample of data from that table, to which the select command will refer to:
I think you're looking for something like this. In plain language the code says "For the range of start date to end date, select the hourly summary statistics for the test table without skipping any hours."
Table
drop table if exists test_table;
go
create table test_table(
ID int primary key not null,
date_dt datetime,
INP3D decimal(4, 3),
ID_device varchar(20));
Data
insert test_table(ID, date_dt, INP3D, ID_device) values
(1, '2020-08-21 13:44:34.590', 3.631, 'A1'),
(2, '2020-08-21 13:44:34.590', 1.269, 'A1'),
(3, '2020-08-21 13:44:34.590', 0.131, 'A1'),
(4, '2020-08-21 13:44:34.590', 8.169, 'A1');
--select * from test_table;
insert test_table(ID, date_dt, INP3D, ID_device) values
(5, '2020-08-21 11:44:34.590', 3.631, 'A1'),
(6, '2020-08-21 02:44:34.590', 1.269, 'A1'),
(7, '2020-08-22 11:44:34.590', 0.131, 'A1'),
(8, '2020-08-22 01:44:34.590', 8.169, 'A1');
Query
declare
#start_dt datetime='2020-08-21',
#end_dt datetime='2020-08-22';
;with
hours_cte as (
select hours_n
from
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),
(13),(14),(15),(16),(17),(18),(19),(20),(21),(22),(23),(24)) v(hours_n)),
days_cte as (
select dateadd(d, hours_n-1, #start_dt) calc_day from hours_cte where hours_n<=datediff(d, #start_dt, #end_dt)+1)
select
dc.calc_day,
hc.hours_n,
count(*) row_count,
isnull(avg(INP3D), 0) inp3d_avg,
isnull(sum(INP3D+0000.000),0) inp3d_sum
from days_cte dc
cross join hours_cte hc
left join test_table t on t.date_dt between dateadd(hour, (hours_n-1), dc.calc_day)
and dateadd(hour, (hours_n), dc.calc_day)
group by
dc.calc_day,
hc.hours_n
order by
1,2;
This?
SELECT * FROM table WHERE DATEPART(MINUTE, 'your_datetime') = '59'
Datepart

How check if specific record exist in table or not

I have a table in which I have multiple dates stored which are the attendance dates of employee
CREATE TABLE Attendance
(
EmpCode INT,
AttendanceDate DATETIME
)
INSERT INTO Attendance VALUES (24, '2018-12-01');
INSERT INTO Attendance VALUES (24, '2018-12-02');
INSERT INTO Attendance VALUES (24, '2018-12-03');
INSERT INTO Attendance VALUES (24, '2018-12-04');
INSERT INTO Attendance VALUES (24, '2018-12-06');
Now as there is not date saved for this employee on 5th Dec it should show Absent in that case.
SELECT * FROM Attendance
It will give missed date for that emp from table if you want to insert you can insert by using existing code
CREATE TABLE #Attendance
(
EmpCode INT,
AttendanceDate DATETIME
)
INSERT INTO #Attendance VALUES (24, '2018-12-01');
INSERT INTO #Attendance VALUES (24, '2018-12-02');
INSERT INTO #Attendance VALUES (24, '2018-12-03');
INSERT INTO #Attendance VALUES (24, '2018-12-04');
INSERT INTO #Attendance VALUES (24, '2018-12-06');
select * from #Attendance
;WITH CTE AS
(
SELECT CONVERT(DATE,'2018-12-01') AS DATE1
UNION ALL
SELECT DATEADD(DD,1,DATE1) FROM CTE WHERE DATE1<'2018-12-06'
)
--insert into #Attendance(EmpCode,AttendanceDate)
SELECT DATE1 MISSING_ONE,'a' FROM CTE
EXCEPT
SELECT AttendanceDate,'a' FROM #Attendance
option(maxrecursion 0)

can we implement innerjoin in the following sql query

These are my tables:
CREATE TABLE forgerock (id INT, [date] DATETIME, empcode INT,[file] VARCHAR);
INSERT INTO forgerock
VALUES
(1, '2015-12-31 01:20:02', 56, 'abc1'),
(2, '2016-01-01 01:20:02', 58, 'abc2'),
(3, '2016-01-02 01:20:02', 46, 'abc3'),
(4, '2016-01-03 01:20:02', 16, 'abc4'),
(5, '2016-01-04 01:20:02', 36, 'abc5');
CREATE TABLE forge (empcode INT, [user_name] VARCHAR);
INSERT INTO forge
VALUES
(56, 'ram'),
(58, 'ram1'),
(46, 'ram2'),
(16, 'ram3'),
(36, 'ram4');
I am trying to print the file name and user_name from the tables with respect to current date and the day before the current date.
I tried the query:
ResultSet resultset = statement.executeQuery("select file from forgerock where '"+date+"' >= CURRENT_DATE('"+date+"', INTERVAL 1 DAY);") ;
but I got the exception:
Incorrect syntax near the keyword 'CURRENT_DATE'.
IF OBJECT_ID('dbo.forgerock', 'U') IS NOT NULL
DROP TABLE dbo.forgerock
CREATE TABLE dbo.forgerock (id INT PRIMARY KEY, [date] DATETIME, empcode INT,[file] VARCHAR(10));
INSERT INTO dbo.forgerock
VALUES
(1, '2015-12-31 01:20:02', 56, 'abc1'),
(2, '2016-01-01 01:20:02', 58, 'abc2'),
(3, '2016-01-02 01:20:02', 46, 'abc3'),
(4, '2016-01-03 01:20:02', 16, 'abc4'),
(5, '2016-01-04 01:20:02', 36, 'abc5');
IF OBJECT_ID('dbo.forge', 'U') IS NOT NULL
DROP TABLE dbo.forge
CREATE TABLE dbo.forge (empcode INT PRIMARY KEY, [user_name] VARCHAR(10));
INSERT INTO dbo.forge
VALUES (56, 'ram'),(58, 'ram1'),(46, 'ram2'),(16, 'ram3'),(36, 'ram4')
DECLARE #dt DATETIME = FLOOR(CAST(GETDATE() AS FLOAT))
SELECT *
FROM dbo.forge
WHERE empcode IN (
SELECT f.empcode
FROM dbo.forgerock f
WHERE f.[date] BETWEEN DATEADD(DAY, -1, #dt) AND #dt
)
output -
empcode user_name
----------- ----------
16 ram3
SELECT fr.file, f.user_name
FROM forgerock fr inner join forge f on fr.empcode = f.empcode
WHERE fr.date >= DATE_ADD(NOW(), INTERVAL -1 DAY)
You can use datediff to get the difference between two dates.
Try this :
ResultSet resultset = statement.executeQuery("select file from forgerock where DATEDIFF(day, GETDATE(), '" + date + "') >= 1") ;
To test the query use this one :
SELECT * FROM forgerock WHERE DATEDIFF(day, GETDATE(), #date) >= 1;
Just replace the #date with the value you want, for example '2016-01-02'
Use this filter:
SELECT [file]
FROM forgerock
WHERE [date] >= DATEADD(DAY, DATEDIFF(DAY,0,GETDATE()-1),0)
The DATEADD expression above will always return 12:00am yesterday morning, allowing your query to only return records from yesterday or today.
Bonus Tip: avoid using reserved keywords (such as file and date) as column or table names.
Since am using ms sql the code should be the following way
SELECT fr.file, f.user_name FROM forgerock fr inner join forge f on fr.empcode = f.empcodewhere [date] >= DATEADD(DAY, DATEDIFF(DAY,0,GETDATE()-1),0)
which will result in printing the two tables file from forgerock and user_name from forge
You have to try following query:-
SELECT fr.file, f.user_name
FROM forgerock fr inner join forge f on fr.empcode = f.empcode
AND `date' >= (DATE_ADD(`date`, INTERVAL 1 day))

sum COALESCE 0 instead of null

i cant add zero values instead of null, here is my sql:
SELECT
S.STOCK_ID,
S.PRODUCT_NAME,
SUM(COALESCE(AMOUNT,0)) AMOUNT,
DATEPART(MM,INVOICE_DATE) AY
FROM
#DSN3_ALIAS#.STOCKS S
LEFT OUTER JOIN DAILY_PRODUCT_SALES DPS ON S.STOCK_ID = DPS.PRODUCT_ID
WHERE
MONTH(INVOICE_DATE) >= #attributes.startdate# AND
MONTH(INVOICE_DATE) < #attributes.finishdate+1#
GROUP BY
DATEPART(MM,INVOICE_DATE),
S.STOCK_ID,
S.PRODUCT_NAME
ORDER BY
S.PRODUCT_NAME
and my table:
<cfoutput query="get_sales_total" group="stock_id">
<tr height="20" class="color-row">
<td>#product_name#</td>
<cfoutput group="ay"><td><cfif len(amount)>#amount#<cfelse>0</cfif></td></cfoutput>
</tr>
</cfoutput>
the result i want:
and the result i get:
thank you all for the help!
+ EDIT :
I have used the cross join technique, rewrote the sql:
SELECT
SUM(COALESCE(AMOUNT,0)) AMOUNT,S.STOCK_ID,S.PRODUCT_NAME,DPS.AY
FROM
#DSN3_ALIAS#.STOCKS S
CROSS JOIN (SELECT DISTINCT <cfif attributes.time_type eq 2>DATEPART(MM,INVOICE_DATE) AY<cfelse>DATEPART(DD,INVOICE_DATE) AY</cfif>
FROM DAILY_PRODUCT_SALES) DPS
LEFT OUTER JOIN DAILY_PRODUCT_SALES DP ON S.STOCK_ID = DP.PRODUCT_ID AND
<cfif attributes.time_type eq 2>DATEPART(MM,DP.INVOICE_DATE)<cfelse>DATEPART(DD,DP.INVOICE_DATE)</cfif> = DPS.AY
WHERE
<cfif attributes.time_type eq 2>
MONTH(INVOICE_DATE) >= #attributes.startdate# AND
MONTH(INVOICE_DATE) < #attributes.finishdate+1#
<cfelse>
MONTH(INVOICE_DATE) = #attributes.startdate#
</cfif>
<cfif len(trim(attributes.product_cat)) and len(attributes.product_code)>
AND S.STOCK_CODE LIKE '#attributes.product_code#%'
</cfif>
GROUP BY DPS.AY,S.STOCK_ID,S.PRODUCT_NAME
ORDER BY DPS.AY,S.STOCK_ID,S.PRODUCT_NAME
and the result is:
Use CASE instead
SUM(CASE WHEN A IS NULL THEN 0 ELSE A END)
You can do it in the database as Lasse suggested, or you can wrap each output value in a Val function, like so:
<cfoutput group="ay"><td>#Val(amount)#</td></cfoutput>
The Val function will convert any non-numeric value to 0.
Can you use ISNULL instead, ie;
SUM(ISNULL(AMOUNT,0)) AMOUNT,
?
EDIT: okay, given that the problem seems to be missing values rather than nulls as such. try something like this.
First, create a permanent reporting_framework table. This one is based on months and years but you could extend it into days if you wished.
create table reporting_framework
([month] smallint, [year] smallint);
go
declare #year smallint;
declare #month smallint;
set #year=2000;
while #year<2500
begin
set #month=1;
while #month<13
begin
insert into reporting_framework ([month], [year]) values (#month, #year);
set #month=#month+1;
end
set #year=#year+1;
end
select * from reporting_framework;
(this gives you 6000 rows, from 2000 to 2499 - adjust to taste!)
Now we'll make a table of parts and a table of orders
create table parts
([part_num] integer, [description] varchar(100));
go
insert into parts (part_num, [description]) values (100, 'Widget');
insert into parts (part_num, [description]) values (101, 'Sprocket');
insert into parts (part_num, [description]) values (102, 'Gizmo');
insert into parts (part_num, [description]) values (103, 'Foobar');
create table orders
([id] integer, part_num integer, cost numeric(10,2), orderdate datetime);
go
insert into orders ([id], part_num, cost, orderdate) values
(1, 100, 49.99, '2011-10-30');
insert into orders ([id], part_num, cost, orderdate) values
(2, 101, 109.99, '2011-10-31');
insert into orders ([id], part_num, cost, orderdate) values
(3, 100, 47.99, '2011-10-31');
insert into orders ([id], part_num, cost, orderdate) values
(4, 102, 429.99, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(5, 101, 111.17, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(6, 101, 111.17, '2011-11-01');
insert into orders ([id], part_num, cost, orderdate) values
(7, 103, 21.00, '2011-09-15');
Now this is the table you base your query on, eg;
select rf.month, rf.year, p.description, sum(isnull(o.cost,0))
from reporting_framework rf cross join parts p
full outer join orders o
on rf.year=year(o.orderdate) and rf.month=month(o.orderdate)
and p.part_num=o.part_num
where rf.year='2011'
group by p.description, rf.month, rf.year
order by rf.year, rf.month, p.description
Does this example help? There are probably loads of better ways of doing this (hello StackOverflow) but it might get you started thinking about what your problem is.
Not the CROSS JOIN to get all parts/dates combinations and then the FULL OUTER JOIN to get the orders into it.
The 'where' clause is just controlling your date range.

Resources