Subquery as an offset Argument- Alternative of LAG() Function in DB2 - database

I would like to ask you a question. Generally, we know that LAG() function can be expressed us LAG(column expression, Offset, [defaul value]) OVER (ORDER BY ...). The offset can't be a subquery in DB2. Does anyone know a function in db2 that has the same functionality with LAG() and it can take as an offset argument a subquery or a calculated field that is calculated in the previous step in a subquery from the source?
So the case is as follows, I want to use lag function but the offset argument LAG(column expression, offset,[default value]) OVER(ORDER BY . . .)
is not a constant value.
For example I have:
LAG(CAST(DATE_utc AS VARCHAR),
offset,
0)
OVER (PARTITION BY ID,
ID_2 ORDER BY DATE_utc) AS PREV
FROM
(
SELECT , CAST(count(fid)
OVER (PARTITION BY f|| t, ID,
ID_2) AS INTEGER) AS offset
FROM SCHEMA.TABLE_1
WHERE ID = 65020475
)
AS inner_dataset
As you can see the [offset] is calculated from the TABLE_1 and is used as a parameter on the LAG function on the outer level.
However, this results in the following error:
SQL Error [42815]: The statement was not processed because the data type, length or value of the argument for the parameter
in position "2" of routine "LAG" is incorrect. Is there any alternative to this?
Thank you in advance!

You can use row_number() and a self join as in:
with tt (a, rn) as (
select a, row_number() over (order by a) as rn from t
)
select t1.a, t2.a as lag_a, t1.rn, t2.rn
from tt t1
left join tt t2
on t2.rn = t1.rn - (select max(x) from p);
Modified Impalers Fiddle

Related

How to get Column from Max of multi another columns?

I need to get the G value at that row contain max of max columns (H,J,J)
Below example: after group, max value of H or I or J is 170, so I need to get column value in column G is 06/25/2022 07:00:00.
I used the following query, it seems to work but returned a lot of missing values after GROUP
"Select C,MAX(MAX(H),MAX(I),MAX(J)) as d1,G GROUP BY C HAVING H=d1 OR I=d1 OR j=d1"
How do I fix this.
Use a CTE that returns the max of H, I and J for each C like this:
WITH cte AS (
SELECT C, MAX(MAX(H), MAX(I), MAX(J)) max
FROM tablename
GROUP BY C
)
SELECT t.C, t.G
FROM tablename t
WHERE (t.c, MAX(t.H, t.I, t.J)) IN (SELECT C, max FROM cte);
For your sample data, maybe it is more suitable to GROUP BY B.
Or, with ROW_NUMBER() window function:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY C ORDER BY MAX(H, I, J) DESC) rn
FROM tablename
)
SELECT C, G
FROM cte
WHERE rn = 1;
Or, with FIRST_VALUE() window fuction:
SELECT DISTINCT C,
FIRST_VALUE(G) OVER (PARTITION BY C ORDER BY MAX(H, I, J) DESC) G
FROM tablename;
See the demo.

update using over order by row_number()

I found some answers to ways to update using over order by, but not anything that solved my issue. In SQL Server 2014, I have a column of DATES (with inconsistent intervals down to the millisecond) and a column of PRICE, and I would like to update the column of OFFSETPRICE with the value of PRICE from 50 rows hence (ordered by DATES). The solutions I found have the over order by in either the query or the subquery, but I think I need it in both. Or maybe I'm making it more complicated than it is.
In this simplified example, if the offset was 3 rows hence then I need to turn this:
DATES, PRICE, OFFSETPRICE
2018-01-01, 5.01, null
2018-01-03, 8.52, null
2018-02-15, 3.17, null
2018-02-24, 4.67, null
2018-03-18, 2.54, null
2018-04-09, 7.37, null
into this:
DATES, PRICE, OFFSETPRICE
2018-01-01, 5.01, 3.17
2018-01-03, 8.52, 4.67
2018-02-15, 3.17, 2.54
2018-02-24, 4.67, 7.37
2018-03-18, 2.54, null
2018-04-09, 7.37, null
This post was helpful, and so far I have this code which works as far as it goes:
select dates, price, row_number() over (order by dates asc) as row_num
from pricetable;
I haven't yet figured out how to point the update value to the future ordered row. Thanks in advance for any assistance.
LEAD is a useful window function for getting values from subsequent rows. (Also, LAG, which looks at preceding rows,) Here's a direct answer to your question:
;WITH cte AS (
SELECT dates, LEAD(price, 2) OVER (ORDER BY dates) AS offsetprice
FROM pricetable
)
UPDATE pricetable SET offsetprice = cte.offsetprice
FROM pricetable
INNER JOIN cte ON pricetable.dates = cte.dates
Since you asked about ROW_NUMBER, the following does the same thing:
;WITH cte AS (
SELECT dates, price, ROW_NUMBER() OVER (ORDER BY dates ASC) AS row_num
FROM pricetable
),
cte2 AS (
SELECT dates, price, (SELECT price FROM cte AS sq_cte WHERE row_num = cte.row_num + 2) AS offsetprice
FROM cte
)
UPDATE pricetable SET offsetprice = cte2.offsetprice
FROM pricetable
INNER JOIN cte2 ON pricetable.dates = cte2.dates
So, you could use ROW_NUMBER to sort the rows and then use that result to select a value 2 rows ahead. LEAD just does that very thing directly.

Can't Seem to Insert CTE Into Table

My cte run find, and gives me the numbers I expect, but I can't seem to insert the results into a table. I did some research online, before posting, and the setup seems correct, based on what I've seen, but I must be missing some step somewhere, because this script doesn't work. Can someone see something that I can't see? I am on SQL Server 2008.
with cte as
(
select
*, rn = row_number() over (partition by Credit_Line_NO order by REVIEW_FREQUENCY)
from TBL_FBNK_LIMIT_HIST
)
(select CREDIT_LINE_NO
,LIMIT_CURRENCY
,(CAST(AVAIL_AMT AS DECIMAL(30,15)) * (CAST(SUBSTRING(CUSIP_NO,1,CHARINDEX('%',CUSIP_NO)-1) AS DECIMAL(30,15))/100))/(12/CAST(LEFT(SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) AS DECIMAL)) AS AMOUNT
,REVIEW_FREQUENCY
,CAST(LEFT(REVIEW_FREQUENCY, 8) AS DATE) AS STARTDATE
,CAST(EXPIRY_DATE AS DATE) AS EXPIRY_DATE
,CAST(round((DATEDIFF(MONTH,cast(LEFT(REVIEW_FREQUENCY,8) as DATE),CAST(EXPIRY_DATE AS DATE)))/cast(LEFT(SUBSTRING (REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) as decimal)+0.4,0) AS INTEGER) AS FREQUENCY
,CAST(DATEADD(MONTH, (rn-1)* LEFT((SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY))),2),LEFT(REVIEW_FREQUENCY, 8)) AS DATE) AS EFFECTIVESTARTDATE
FROM cte
WHERE AVAIL_AMT NOT LIKE '%]%'
AND CUSIP_NO IS NOT NULL
AND CUSIP_NO <> '0'
AND AVAIL_AMT <> '0'
AND AVAIL_AMT IS NOT NULL)
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT (CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE
From cte
Thanks!
You are not really using the cute in the insert. Try it like:
with cte as
(
select
*, rn = row_number() over (partition by Credit_Line_NO order by REVIEW_FREQUENCY)
from TBL_FBNK_LIMIT_HIST
),
cte2 as
(select CREDIT_LINE_NO
,LIMIT_CURRENCY
,(CAST(AVAIL_AMT AS DECIMAL(30,15)) * (CAST(SUBSTRING(CUSIP_NO,1,CHARINDEX('%',CUSIP_NO)-1) AS DECIMAL(30,15))/100))/(12/CAST(LEFT(SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) AS DECIMAL)) AS AMOUNT
,REVIEW_FREQUENCY
,CAST(LEFT(REVIEW_FREQUENCY, 8) AS DATE) AS STARTDATE
,CAST(EXPIRY_DATE AS DATE) AS EXPIRY_DATE
,CAST(round((DATEDIFF(MONTH,cast(LEFT(REVIEW_FREQUENCY,8) as DATE),CAST(EXPIRY_DATE AS DATE)))/cast(LEFT(SUBSTRING (REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) as decimal)+0.4,0) AS INTEGER) AS FREQUENCY
,CAST(DATEADD(MONTH, (rn-1)* LEFT((SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY))),2),LEFT(REVIEW_FREQUENCY, 8)) AS DATE) AS EFFECTIVESTARTDATE
FROM cte
WHERE AVAIL_AMT NOT LIKE '%]%'
AND CUSIP_NO IS NOT NULL
AND CUSIP_NO <> '0'
AND AVAIL_AMT <> '0'
AND AVAIL_AMT IS NOT NULL)
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT (CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE
From cte2;
Code looks like redundant but at least it would work the way you think it should.
You can only use the CTE in the statement for which it is defined. In your case, you have the CTE definition, and a SELECT statement that reads the CTE.
Then, you have a totally separate statement which attempts to read the CTE again for the INSERT. This is not permitted, because the CTE does not exist in the second query's context. So, from the perspective of your INSERT statement, the CTE does not exist. I'm sure you're getting this message:
Msg 208, Level 16, State 1, Line [x] Invalid object name 'cte'.
Get rid of the SELECT statement and replace it with your INSERT.
Alternatively, if you must have the SELECT statement used in both the SELECT and INSERT statements, a CTE may not be appropriate for the use case, or you will need to include the CTE definition for both the SELECT and INSERT.
It should be working fine unless those columns are not in TBL_FBNK_LIMIT_HIST_TRANS_SPLIT or the data types do not match.
That select in the middle is not part of the insert.
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT
(CREDIT_LINE_NO, LIMIT_CURRENCY, AMOUNT, REVIEW_FREQUENCY, START_DATE, EXPIRY_DATE, FREQUENCY, AsOfDate, EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO, LIMIT_CURRENCY, AMOUNT, REVIEW_FREQUENCY, START_DATE, EXPIRY_DATE, FREQUENCY, AsOfDate, EFFECTIVESTARTDATE
From cte

Finding point of interest on a square wave using sql

Good day,
I have a sql table with the following setup:
DataPoints{ DateTime timeStampUtc , bit value}
The points are on a minute interval, and store either a 1(on) or a 0(off).
I need to write a stored procedure to find the points of interest from all the data points.
I have a simplified drawing below:
I need to find the corner points only. Please note that there may be many data points between a value change. For example:
{0,0,0,0,0,0,0,1,1,1,1,0,0,0}
This is my thinking atm (high level)
Select timeStampUtc, Value
From Data Points
Where Value before or value after differs by 1 or -1
I am struggling to convert this concept to sql, and I also have a feeling there is an more elegant mathematical solution that I am not aware off. This must be a common problem in electronics?
I have wrapped the table into a CTE. Then, I am joining every row in the CTE to the next row of itself. Also, I've added a condition that the consequent rows should differ in the value.
This would return you all rows where the value changes.
;WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY TimeStampUTC) AS id, VALUE, TIMESTAMPUTC
FROM DataPoints
)
SELECT CTE.TimeStampUTC as "Time when the value changes", CTE.id, *
FROM CTE
INNER JOIN CTE as CTE2
ON CTE.id = CTE2.id + 1
AND CTE.Value != CTE2.Value
Here's a working fiddle: http://sqlfiddle.com/#!6/a0ddc/3
If I got it correct, you are looking for something like this:
with cte as (
select * from (values (1,0),(2,0),(3,1),(4,1),(5,0),(6,1),(7,0),(8,0),(9,1)) t(a,b)
)
select
min(a), b
from (
select
a, b, sum(c) over (order by a rows unbounded preceding) grp
from (
select
*, iif(b = lag(b) over (order by a), 0, 1) c
from
cte
) t
) t
group by b, grp

T-SQL - get only latest row for selected condition

I have table with measurement with column SERIAL_NBR, DATE_TIME, VALUE.
There is a lot of data so when I need them to get the last 48 hours for 2000 devices
Select * from MY_TABLE where [TIME]> = DATEADD (hh, -48, #TimeNow)
takes a very long time.
Is there a way not to receive all the rows for each device, but only the latest entry? Would this speed up the query execution time?
Assuming that there is column named deviceId(change as per your needs), you can use top 1 with ties with window function row_number:
Select top 1 with ties *
from MY_TABLE
where [TIME]> = DATEADD (hh, -48, #TimeNow)
Order by row_number() over (
partition by deviceId
order by Time desc
);
You can simply create Common Table Expression that sorts and groups the entries and then pick the latest one from there.
;WITH numbered
AS ( SELECT [SERIAL_NBR], [TIME], [VALUE], row_nr = ROW_NUMBER() OVER (PARTITION BY [SERIAL_NBR] ORDER BY [TIME] DESC)
FROM MY_TABLE
WHERE [TIME]> = DATEADD (hh, -48, #TimeNow) )
SELECT [SERIAL_NBR], [TIME], [VALUE]
FROM numbered
WHERE row_nr = 1 -- we want the latest record only
Depending on the amount of data and the indexes available this might or might not be faster than Anthony Hancock's answer.
Similar to his answer you might also try the following:
(from MSSQL's point of view, the below query and Anthony's query are pretty much identical and they'll probably end up with the same query plan)
SELECT [SERIAL_NBR] , [TIME], [VALUE]
FROM MY_TABLE AS M
JOIN (SELECT [SERIAL_NBR] , max_time = MAX([TIME])
FROM MY_TABLE
GROUP BY [SERIAL_NBR]) AS L -- latest
ON L.[SERIAL_NBR] = M.[SERIAL_NBR]
AND L.max_time = M.[TIME]
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
Your listed column values and your code don't quite match up so you'll probably have to change this code a little, but it sounds like for each SERIAL_NBR you want the record with the highest DATE_TIME in the last 48 hours. This should achieve that result for you.
SELECT SERIAL_NBR,DATE_TIME,VALUE
FROM MY_TABLE AS M
WHERE M.DATE_TIME >= DATEADD(hh,-48,#TimeNow)
AND M.DATE_TIME = (SELECT MAX(_M.DATE_TIME) FROM MY_TABLE AS _M WHERE M.SERIAL_NBR = _M.SERIAL_NBR)
This will get you details of the latest record per serial number:
Select t.SERIAL_NBR, q.FieldsYouWant
from MY_TABLE t
outer apply
(
selct top 1 t2.FieldsYouWant
from MY_TABLE t2
where t2.SERIAL_NBR = t.SERIAL_NBR
order by t2.[TIME] desc
)q
where t.[TIME]> = DATEADD (hh, -48, #TimeNow)
Also, worth sticking DATEADD (hh, -48, #TimeNow) into a variable rather than calculating inline.

Resources