Can't Seem to Insert CTE Into Table - sql-server

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

Related

How can I get, and re-use, the next 6 *dates* from today in SQL Server?

I need to create a CTE I can re-use that will hold seven dates. That is today and the next six days.
So, output for today (4/22/2022) should be:
2022-04-22
2022-04-23
2022-04-24
2022-04-25
2022-04-26
2022-04-27
2022-04-28
So far, I have this:
WITH seq AS
(
SELECT 0 AS [idx]
UNION ALL
SELECT [idx] + 1
FROM seq
WHERE [idx] < 6
)
SELECT DATEADD(dd, [idx], CONVERT(date, GETDATE()))
FROM seq;
The problem is my SELECT is outside the WITH, so I would need to wrap this whole thing with another WITH to re-use it, for example to JOIN on it as a list of dates, and I'm not having luck getting that nested WITH to work. How else could I accomplish this?
To be clear: I'm not trying to find records in a specific table full of dates that are from the next seven days. There are plenty of easy solutions for that. I need a list of dates for today and the next six days, that I can re-use in other queries as a CTE.
You're close. Here's an example:
with cte as (
select
1 as n
,GETDATE() as dt
union all
select
n+1
,DATEADD(dd,n,GETDATE()) as dt
from cte
where n <= 6
)
select * from cte
Fiddle here
You can create a view for reusability and simply query the view rather than using the same CTE over and over again.
You can do this by adding a second column for the date to the CTE:
WITH seq AS (
SELECT 0 AS [idx], cast(current_timestamp as date) as date
UNION ALL
SELECT [idx] + 1, dateadd(dd, idx+1, cast(current_timestamp as date))
FROM seq
WHERE [idx] < 6
)
SELECT *
FROM seq;
See it here:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=208ecd76be2071529078f38b1735b0cd
Another option is you can "stack" CTEs, rather than nest, to avoid the second column:
WITH seq0 AS (
SELECT 0 AS [idx]
UNION ALL
SELECT [idx] + 1
FROM seq0
WHERE [idx] < 6
),
seq As (
SELECT dateadd(dd, idx, cast(current_timestamp as date)) as idx
FROM seq0
)
SELECT *
FROM seq;
Note how the final query only needed to reference the 2nd CTE.
See it here:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=22315438e4710792f368009cc6ff6451
Never recommend using recursion if you don't have a need for it. It's more complex and slower. I'd just use a hardcoded list of numbers, could encapsulate it in TVF if you wanted to reuse it across different stored procedures/functions. If you need to reuse it in 1 stored proc in multiple places, I'd just throw it in a temp table.
CTE Version without Recursion
WITH cte_7days AS (
SELECT theDate = CAST(DATEADD(dd,num,GETDATE()) AS DATE)
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
)
SELECT *
FROM cte_7days
CROSS APPLY Version to Remove Need for CTE
Could use something like this as your base query, and then just add more joins below table depending on your query
SELECT theDate
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
CROSS APPLY (SELECT theDate = CAST(DATEADD(DAY,num,GETDATE()) AS DATE)) AS B
TVF Version
CREATE FUNCTION dbo.uf_7days()
RETURNS TABLE AS
RETURN
(
SELECT theDate
FROM (VALUES (0),(1),(2),(3),(4),(5),(6)) AS A(num)
CROSS APPLY (SELECT theDate = CAST(DATEADD(DAY,num,GETDATE()) AS DATE)) AS B
)
GO

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.

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.

Calculate same day start/end dates as 0 days if another occurrence already exists

I have a query where there are instances where a "phase" starts and ends on the same day - this is calculated as 1 day. If, however, another "phase" starts and ends on the same day against the same ref. no. and period no., then I'd like to calculate this as 0 days.
Example:
**Ref. Period. Phase StDt EndDt**
013 3 KAA 01/01/16 01/01/16 - This is one day
013 3 TAA 02/01/16 03/01/16 - this is 2 days
013 3 KAT 01/01/16 01/01/16 - **would like this to be counted as 0 day**
013 3 TTA 04/04/16 04/04/16 - this is one day
I would like this unique calculation to be done in the data grouped by Ref. And Period numbers. This is a tricky one....
Thanks
Try this.
I am assuming that you are using TSQl (Not sure a you have also tagged SQL.
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY ID) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
If there is no ID column then select some column to order by, probably Phase so replace ID with Phase as here ROW_NUMBER() OVER (PARTITION BY StDt,EndDt ORDER BY ID) AS [Order], if there is no candidate column to order by then try this
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY (SELECT NULL)) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
This expression should work on the SSRS side:
=IIF(Fields!StartDate.Value=Fields!EndDate.Value AND Fields!Phase.Value <> LOOKUPSET(Fields!StartDate.Value &"_" & Fields!EndDate.Value,Fields!StartDate.Value & "_" & Fields!EndDate.Value,Fields!Phase.Value,"DatasetName").GetValue(0),0,DATEDIFF("D",Fields!StartDate.Value,Fields!EndDate.Value)+1)
It will return a value of 1 for the first phase returned by the dataset. If the phase-date range combinations are not unique within the grouping, this will not work as written, but you should be able to modify accordingly.
Also, if the rows are sorted differently between SSRS and the dataset, it may not be the first row that appears that gets the 1.
The below did the trick! Basically, I'm using count aggregate to count the number of instances where phases start and end on the same day PER Ref and period. Then, for any where there are more than 1, I just use simple case statments to count the first one as 1 and any subsequent ones as 0. I'm creating the below as a subquery in the joins as a left outer join:
LEFT OUTER JOIN
(SELECT TOP (100) PERCENT Period, Ref,
CONVERT(date, PhaseStartDate) AS stdt, CONVERT(date, PhaseEndDate) AS enddt,
COUNT(*)
AS NoOfSameDayPhases,
MIN(PhaseSequence) AS FirstPhSeq
FROM Phases AS Phases_1
WHERE (CONVERT(date, PhaseStartDate) =
CONVERT(date, PhaseEndDate))
GROUP BY VoidPeriod, Ref, CONVERT(date,
PhaseStartDate), CONVERT(date, PhaseEndDate)) AS SameDayPH ON CONVERT(date,
PhaseEndDate) = SameDayPH.enddt AND CONVERT(date,
PhaseStartDate) = SameDayPH.stdt AND
VoidPeriod = SameDayPH.VoidPeriod AND SameDayPH.Ref =
VoidPhases.Ref

T-SQL CTE self-reference CROSS APPLY previous row by date with gaps

I have an updatable table of date-value sequence (say dbo.sequence) in SQL Server 2014. Dates are unique.
When new updates come I want to distribute that values into different columns in a separate table (say dbo.distributed_values) by certain conditions, e.g. if previous value from dbo.sequence is less/greater than current dbo.sequence value, it gets inserted into specified column of dbo.distributed_values or becomes NULL in that column.
Here is the main idea:
;WITH
CTE_tbl (date, value, val_1, val_2, val_3)
AS (
SELECT ... FROM dbo.distributed_values -- get latest values from database
UNION ALL
SELECT
SEQ.date,
SEQ.value,
CASE
WHEN ABS (SEQ.value - prev.value) >= 0.5
THEN SEQ.value
ELSE NULL
END AS val_1,
...
FROM dbo.sequence AS SEQ
CROSS APPLY (SELECT * FROM CTE_tbl WHERE date = DATEADD(DAY, -1, SEQ.date)) AS prev
)
INSERT INTO dbo.distributed_values (...)
SELECT *
FROM CTE_tbl
ORDER BY date ASC
OPTION (MAXRECURSION 1000)
Seems it works mostly, but the dbo.sequence contains gaps, so I can not use things like date = DATEADD(DAY, -1, SEQ.date) to bind on previous row properly.
2012-01-04
2012-01-05
2012-01-06
2012-01-09
2012-01-10
2012-01-11
How to bind previous value correctly in case of date gaps?
UPD:
By the way, I can not use LAG ... OVER in WHERE clause, I tried. Could it be used here somehow?
Add another CTE and use that in your recursive CTE, something like this:
;WITH
SequenceWithPrevious AS(
SELECT *
,PrevValue = LAG(value,1,NULL) OVER (ORDER BY SEQ.date)
,Prevdate = LAG(date,1,NULL) OVER (ORDER BY SEQ.date)
FROM dbo.sequence AS SEQ
),
CTE_tbl (date, value, val_1, val_2, val_3)
AS (
SELECT ... FROM dbo.distributed_values -- get latest values from database
UNION ALL
SELECT ...
FROM SequenceWithPrevious AS SEQ
CROSS APPLY (SELECT * FROM CTE_tbl WHERE date = SEQ.PrevDate) AS prev
)
INSERT INTO dbo.distributed_values (...)
SELECT *
FROM CTE_tbl
ORDER BY date ASC
OPTION (MAXRECURSION 1000)

Resources