Oracle [0RA-01722 :invalid number] upon casting regexp_substr to NUMBER - sql

We are working on Oracle Database SQL Query.
DataBase Structure:
(etd_log_id, region,sla_status,escaltion_level,added_ts)
escalation_level field has:
1,2,3,4,5,6,7,8,9,10
1,2,3,4,5,6,7,8,9,10
1,2,3,4,5,6,7,8,9,10
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,
1,2,3,4,5,6,7,
1,2,3,4,5,6,
1,2,3,4,5,
1,2,3,4,
1,2,3
1,2
1
Requirement: We need to send alert based on level as a input,
Lets say we get 6 as a input, we should get all these records :
1,2,3,4,5,6,
1,2,3,4,5,
1,2,3,4,
1,2,3
1,2
1
Java passes level as input in the where clause
`WHERE CAST(REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') AS NUMBER) <= 10<--Input from Java`
Approach: We developed a SQL query initially wherein we had this
CAST(REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') AS NUMBER) <= 6--<input from java>
But it threw an error as
ORA-01722: invalid number
01722. 00000 - "invalid number"
*Cause: The specified number was invalid.
*Action: Specify a valid number.
Upon RCA we came to know that REGEXP is treating the value into ASCII values.
So we changed the condition by removing CAST as NUMBER from the CLAUSE.( WHERE (REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') <= ‘10’)Now 10 is treated as a character and comparison is made on the basis of ascii values.
The code failed in testing .
I did RCA for the same and came to know that 1 and 10 have same ASCII value as ‘49’.
So here is the interesting part.
1 and 10 have ASCII value as 49.
2,3,4,5,6,7,8,9 have ASCII values as 50,51,52,53,…
When it reaches level 10, it shows output for escalation level 1 and 10 ( as we have WHERE (REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') < =‘10’) because it satisfies the condition <=49(ascii value) for level 1 as well as 10.
Also, when it reaches 10, and then we provide any level between 2 and 9 we get level 10 also in the output.(obviously because now string has 10 in it with lower ASCII value to satisfy the condition)
What I want?
Is there any way we can convert regexp and the level to same format? ( I tried with TO_NUMBER and CAST as NUMBER)
Is there any other approach you would suggest to solve this issue?
Your help will be highly appreciated!
QUERY
SELECT CAST( REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') AS VARCHAR2(200)) "LEVEL",
ETD_ALERT_LOG_ID ,REGION, REQUIRED_PERCENTAGE AS SLA_SET,
----------------- THIS PART FETCHES START TIME FOR EMAIL ALERT---------------------------------------------------
CAST(TO_CHAR(
(FROM_TZ((SELECT ADDED_TS FROM (SELECT ADDED_TS, DENSE_RANK() OVER (PARTITION BY REGION ORDER BY ADDED_TS DESC) RNK
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG WHERE UPPER(REGION) = UPPER('Southern Ohio')
AND ESCALATION_LEVEL ='1' AND SLA_STATUS = 'Missed SLA'
AND ADDED_TS > (SELECT ADDED_TS FROM (SELECT ADDED_TS, DENSE_RANK() OVER (PARTITION BY REGION ORDER BY ADDED_TS DESC) RNK
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG WHERE UPPER(REGION) = UPPER(EAL.REGION)
AND (SLA_STATUS = 'In SLA' OR IS_SEND_ALERT = 'N') )
WHERE RNK = 1 AND ROWNUM = 1)
AND TRUNC(FROM_TZ(ADDED_TS, (SELECT TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR')) FROM DUAL ))
AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION) = UPPER(EAL.REGION) AND ROWNUM=1))=
TRUNC(SYSTIMESTAMP AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG
WHERE UPPER(REGION) = UPPER(EAL.REGION) AND ROWNUM=1)))
WHERE RNK = 1 AND ROWNUM = 1), (SELECT TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR')) FROM DUAL ))
AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION)= UPPER(EAL.REGION) AND ROWNUM=1))
- NUMTODSINTERVAL((SELECT VALUE FROM CHRT_SMTFOMGR.APP_PROPERTY
WHERE UPPER(NAME) = 'ETD_ALERT_ESCALATION_INTERVAL'),'MINUTE')
,'HH:MI AM') AS VARCHAR2(10)) ||' '|| (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION)= UPPER(EAL.REGION) AND ROWNUM=1) AS ALERT_START_TIME,
----------------------END OF START TIME **WORKS FINE**-------------------------
------------------FETCHES END TIME FOR EMAIL TEMPLATE-------------------------------------
CAST(TO_CHAR(
(FROM_TZ(ADDED_TS, (SELECT TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR')) FROM DUAL ))
AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION)= UPPER(EAL.REGION) AND ROWNUM=1))
,'HH:MI AM') AS VARCHAR2(10)) ||' '|| (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION)= UPPER(EAL.REGION) AND ROWNUM=1)
AS ALERT_END_TIME,
-------------------------END TIME **WORKS FINE----------------------------------------------------
TOTAL_ETD, (TOTAL_ETD - TOTAL_MET_SLA) AS UCM_SLA_MISSED_ETD, CURRENT_PERCENTAGE AS ACTUAL_SLA
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG EAL
---------------------WHERE CLAUSE MENTIONED IN THE MAIL--------------------------------------------
WHERE CAST(REGEXP_SUBSTR(ESCALATION_LEVEL, '[^,]*$') AS NUMBER) <= 10
----------------------------------------------------------------------------------------------------
--------------PART TO FILTER REGION AND CHECK IF THE ALERT IS ON FOR SENDING EMAIL----
AND UPPER(EAL.REGION) = UPPER('Southern Ohio') AND IS_SEND_ALERT = 'Y'
-----------------PART TO CONVERT THE TIME ZONE INTO REGION BASED TIMEZONE SO THAT ALERT IS SENT ONLY DURING CONFIGURED START AND END TIME-------------------------------------------
AND TRUNC(FROM_TZ(ADDED_TS, (SELECT TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR')) FROM DUAL ))
AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION) = UPPER(EAL.REGION) AND ROWNUM=1))=
TRUNC(SYSTIMESTAMP AT TIME ZONE (SELECT TIMEZONE FROM CHRT_SMTFOMGR.ETD_ALERT_CONFIG EAC
WHERE UPPER(EAC.REGION) = UPPER(EAL.REGION) AND ROWNUM=1))
---PART TO CHECK IF THE TICKETS MEET SLA OR THE ALERT WAS DISABLED IN BETWEEN THE RECORDS THAT ARE TO BE FETCHED SHOULD BE AFTER THAT 'In SLA' RECORD-----------------------------------------------------------------------
AND ADDED_TS > (SELECT ADDED_TS FROM (SELECT ADDED_TS, DENSE_RANK() OVER (PARTITION BY REGION ORDER BY ADDED_TS DESC) RNK
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG WHERE UPPER(REGION) = UPPER(EAL.REGION)
AND (SLA_STATUS = 'In SLA' OR IS_SEND_ALERT = 'N') )
WHERE RNK = 1 AND ROWNUM = 1)
ORDER BY ADDED_TS DESC;

You do not need (slow) regular expressions and can do it with simple string functions:
SELECT *
FROM table_name
WHERE TO_NUMBER(
RTRIM(
SUBSTR(escalation_level, INSTR(escalation_level, ',', -2) + 1),
','
)
) <= 6;
Which, for the sample data:
CREATE TABLE table_name ( escalation_level ) AS
SELECT '1,2,3,4,5,6,7,8,9,10 ' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,8,9,10' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,8,9,10' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,8,9' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,8,' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,7,' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,6,' FROM DUAL UNION ALL
SELECT '1,2,3,4,5,' FROM DUAL UNION ALL
SELECT '1,2,3,4,' FROM DUAL UNION ALL
SELECT '1,2,3' FROM DUAL UNION ALL
SELECT '1,2' FROM DUAL UNION ALL
SELECT '1' FROM DUAL
Outputs:
ESCALATION_LEVEL
1,2,3,4,5,6,
1,2,3,4,5,
1,2,3,4,
1,2,3
1,2
1
db<>fiddle here
Additionally:
(SELECT TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR')) FROM DUAL)
Does not need the sub-query and you can just use:
TZ_OFFSET(TO_CHAR(SYSTIMESTAMP,'TZR'))
and
SELECT ADDED_TS FROM (
SELECT ADDED_TS,
DENSE_RANK() OVER (PARTITION BY REGION
ORDER BY ADDED_TS DESC) RNK
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG
WHERE UPPER(REGION) = UPPER(EAL.REGION)
AND (SLA_STATUS = 'In SLA' OR IS_SEND_ALERT = 'N')
)
WHERE RNK = 1 AND ROWNUM = 1
When there are two REGION values that have identical characters but differing cases then the inner query will return multiple rows where rnk = 1 and it may be random which is matched by ROWNUM = 1 and it may not be the one with the latest date.
db<>fiddle here
You can write it much simpler using ROW_NUMBER and without the PARTITION BY clause (since that is already handled in the WHERE filter and I am assuming case does not matter):
SELECT ADDED_TS FROM (
SELECT ADDED_TS,
ROW_NUMBER() OVER (ORDER BY ADDED_TS DESC) RN
FROM CHRT_SMTFOMGR.ETD_ALERT_LOG
WHERE UPPER(REGION) = UPPER(EAL.REGION)
AND (SLA_STATUS = 'In SLA' OR IS_SEND_ALERT = 'N')
)
WHERE RN = 1
However, you can probably write it without all the nested sub-queries if you use conditional aggregation in a windowed analytic function but your query is huge, badly-formatted and difficult to understand and I'll leave that to you to solve if you want to.

Related

Hive query takes forever on Superset

I have a query that was written in Presto SQL format (100 lines of insert a query result to a table that already exists) and takes within 10 minutes to get the result.
Now I am going to use Airflow and need to change the query to Hive SQL format to append previous month's data, there is no error, but it is taking 75+ minutes now and the query is still running and not returning any result.
Shall I 'stop' it or is there anything else to consider?
SET hive.limit.query.max.table.partition = 1000000;
INSERT INTO TABLE schema.temp_tbl partition(year_month_key)
Select
distinct
tbl.account_id,
tbl.theme_status,
streaming.streaming_hours,
tbl.year_month as year_month_key
From
(
Select
tbl_0.year_month,
tbl_0.account_id,
case when max(tbl_0.theme_status) = 1 then 'With Theme' else 'No Theme' end as theme_status
From
(Select
streaming.year_month,
streaming.account_id,
case when theme_events.account_id is not null then 1 else 0 end as theme_status
from
(
Select
substring(date_key, 1, 7) as year_month,
last_day(add_months(date_key, -1)) as year_month_ed,
date_key,
upper(account_id) as account_id,
play_seconds
from agg_device_streaming_metrics_daily
Where date_key between date_add(last_day(add_months(current_date, -2)),1) and last_day(add_months(current_date, -1))
and play_seconds > 0
) streaming
left join
(
Select
upper(theme.virtualuserid) as account_id,
min(theme.createddate) as min_createddate,
min(theme.date_key) as date_key
From
(
select * from theme_activate_event_history
where date_key between '2019-01-01' and '2020-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between '2020-01-01' and '2021-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between '2021-01-01' and '2022-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between cast('2022-01-01' as date) and last_day(add_months(current_date, -1))
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
) theme
group by theme.virtualuserid
) theme_events
on streaming.account_id = theme_events.account_id
and date(theme_events.date_key) <= date(streaming.year_month_ed)
) tbl_0
group by tbl_0.year_month, tbl_0.account_id
) tbl
inner join
(Select
substring(date_key, 1, 7) as year_month,
upper(account_id) as account_id,
cast(sum(play_seconds) / 3600 as double) as streaming_hours
from agg_device_streaming_metrics_daily
Where date_key between date_add(last_day(add_months(current_date, -2)),1) and last_day(add_months(current_date, -1))
and play_seconds > 0
group by substring(date_key, 1, 7), upper(account_id)
) streaming
on tbl.account_id = streaming.account_id and tbl.year_month = streaming.year_month;

Oracle - Display 'No Rows' when query returns no results

I would like my Oracle SQL output to display 'No Rows Found' when the query returns no results.
I am trying to use the NVL function but Im getting an error stating
'ERROR at line 21: ORA-00907: missing right parenthesis'
SELECT NVL((
SELECT TO_CHAR(CHGDATE, 'yyyy-mm')
,CHGFIELD
,DBNAME
,COUNT(*)
FROM APPCHANGEHIST A
,DATABASEFIELD D
WHERE A.CHGFIELD = D.FIELDNUM
AND trunc(CHGDATE) BETWEEN add_months(to_date(to_char((sysdate - to_char(sysdate, 'dd') + 1), 'dd-mon-yyyy')), - 1)
AND to_date(to_char((sysdate - to_char(sysdate, 'dd')), 'dd-mon-yyyy'))
AND CHGFIELD = 79
AND OLDVALUE IS NOT NULL
AND EXISTS (
SELECT 1
FROM USERPROF
WHERE USERID = A.CHGREQUESTOR
)
GROUP BY TO_CHAR(CHGDATE, 'yyyy-mm')
,CHGFIELD
,DBNAME
ORDER BY 1
,4 DESC
), "No Rows");
I don't have issues when I run this statement alone without the NVL
SELECT TO_CHAR(CHGDATE, 'yyyy-mm')
,CHGFIELD
,DBNAME
,COUNT(*)
FROM APPCHANGEHIST A
,DATABASEFIELD D
WHERE A.CHGFIELD = D.FIELDNUM
AND trunc(CHGDATE) BETWEEN add_months(to_date(to_char((sysdate - to_char(sysdate, 'dd') + 1), 'dd-mon-yyyy')), - 1)
AND to_date(to_char((sysdate - to_char(sysdate, 'dd')), 'dd-mon-yyyy'))
AND CHGFIELD = 79
AND OLDVALUE IS NOT NULL
AND EXISTS (
SELECT 1
FROM USERPROF
WHERE USERID = A.CHGREQUESTOR
)
GROUP BY TO_CHAR(CHGDATE, 'yyyy-mm')
,CHGFIELD
,DBNAME
ORDER BY 1
,4 DESC
Ok, at a high level, you can use the following pattern:
WITH results AS
(
SELECT *
FROM dual d
WHERE d.dummy = 'Y'
)
SELECT *
FROM results
UNION ALL
SELECT 'No Rows Found'
FROM dual
WHERE NOT EXISTS (SELECT 'X'
FROM results);
You can play with this by changing the value in the WITH clause between 'X' and 'Y'.
In your query, you would just replace the SELECT within the WITH clause with your query.

SQL query from Oracle SQL to T-SQL

I have a subquery which is used for an Oracle database, but I want to use an equivalent query for a SQL Server database.
I didn't figure out how to migrate the TO_TIMESTAMP(TO_CHAR(TO_DATE part and also didn't know how to handle the thing with rownums in T-SQL.
Is it even possible to migrate this query?
SELECT 0 run_id,
0 tran_id,
0 sort_id,
' ' tran_type,
10 prod_id,
72 type_id,
1 value,
TO_TIMESTAMP(TO_CHAR(TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1, 'YYYY.MM.DD') || to_char(sw.end_time, 'HH24:MI:SS'), 'YYYY.MM.DD HH24:MI:SS') event_publication,
EXTRACT (YEAR
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) y,
EXTRACT (MONTH
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) mo,
EXTRACT (DAY
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) d,
to_number(to_char (sw.end_time, 'HH24')) h,
to_number(to_char (sw.end_time, 'MI')) mi,
to_number(to_char (sw.end_time, 'SS')) s,
0 ms
FROM all_objects ao,
settlement_win sw,
prod_def pd
WHERE pd.prod_id = 10
AND sw.country = pd.country
AND sw.commodity = pd.commodity
AND rownum <= TO_DATE('2016-03-18 23:59:00', 'YYYY.MM.DD HH24:MI:SS') -TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS')+1
The first thing to address is the use of rownum which has no direct equivalent in TSQL but we can mimic it, and for this particular query you need to recognize that the table ALL_OBJECTS is only being used to produce a number of rows. It has no other purpose to the query.
In TSQL we can generate rows using a CTE and there are many many variants of this, but for here I suggest:
;WITH
cteDigits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, cteTally AS (
SELECT
d1s.digit
+ d10s.digit * 10
+ d100s.digit * 100 /* add more like this as needed */
-- + d1000s.digit * 1000 /* add more like this as needed */
+ 1 AS rownum
FROM cteDigits d1s
CROSS JOIN cteDigits d10s
CROSS JOIN cteDigits d100s /* add more like this as needed */
--CROSS JOIN cteDigits d1000s /* add more like this as needed */
)
This will quickly spin-up 1000 rows as is and can be extended to produce many more rows by adding more cross joins. Note this returns a column called rownum which starts at 1 thus mimicking the Oracle rownum.
So next you can just add some of the remaining query, like this:
SELECT
0 run_id
, 0 tran_id
, 0 sort_id
, ' ' tran_type
, 10 prod_id
, 72 type_id
, 1 value
, convert(varchar, dateadd(day, rownum - 1,'20160318'),121) event_publication
-- several missing rows here
, 0 ms
FOM cteTally
INNER JOIN settlement_win sw
INNER JOIN prod_def pd ON sw.country = pd.country AND sw.commodity = pd.commodity
WHERE pd.prod_id = 10
AND rownum <= datediff(day,'20160318','20160318') + 1
Note that you really do not need a to_timestamp() equivalent you just need the ability to output date and time to the maximum precision of your data which appears to be to the level of seconds.
To progress further (I think) requires an understanding of the data held in the column sw.end_time. If this can be converted to the mssql datetime data type then it is just a matter of adding a number of days to that value to arrive at the event_publication and similarly if sw.end_time is converted to a datetime data type then use date_part() to get the hours, minutes and seconds from that column. e.g.
, DATEADD(day,rownum-1,CONVERT(datetime, sw.end_time)) AS event_publication
also, if such a calculation works then it would be possible to use an apply operator to simplify the overall query, something like this
;WITH
cteDigits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, cteTally AS (
SELECT
d1s.digit
+ d10s.digit * 10
+ d100s.digit * 100 /* add more like this as needed */
-- + d1000s.digit * 1000 /* add more like this as needed */
+ 1 AS rownum
FROM cteDigits d1s
CROSS JOIN cteDigits d10s
CROSS JOIN cteDigits d100s /* add more like this as needed */
--CROSS JOIN cteDigits d1000s /* add more like this as needed */
)
SELECT
0 run_id
, 0 tran_id
, 0 sort_id
, ' ' tran_type
, 10 prod_id
, 72 type_id
, 1 value
, convert(varchar(23), CA.Event_publication, 121) Event_publication
, datepart(day,CA.Event_publication) dd
, datepart(month,CA.Event_publication) mm
, datepart(year,CA.Event_publication) yyyy
, datepart(hour,CA.Event_publication) hh24
, datepart(minute,CA.Event_publication) mi
, datepart(second,CA.Event_publication) ss
, 0 ms
FOM cteTally
INNER JOIN settlement_win sw
INNER JOIN prod_def pd ON sw.country = pd.country AND sw.commodity = pd.commodity
CROSS APPLY (
SELECT DATEADD(day,rownum-1,CONVERT(datetime, sw.end_time)) AS event_publication ) CA
WHERE pd.prod_id = 10
AND rownum <= datediff(day,'20160318','20160318') + 1
NB: IT may be necessary to include this datediff(day,'19000101,'20160318') (which equals 42445) into the calculation of the event_date e.g.
SELECT DATEADD(day,42445 + (rownum-1),CONVERT(datetime, sw.end_time)) AS event_publication
One last point is that you could use datetime2 instead of datetime if you really do need a greater degree of time precision but there is no easily apparent requirement for that.

Can we write group by to get first record of the order by in Oracle sql? Or can you suggest solution for given query?

I have a table which shows good, bad and other status for a Device everyday. I want to display a row per device with today's status and previous best status('Good' if anytime good in the time span, otherwise the previous day status). I am using join and query is as shared below.
SELECT t1.devid,
t1.status AS Today_status,
t2.status AS yest_status,
t2.runtime AS yest_runtime
FROM devtable t1
INNER JOIN devtable t2
ON t1.devid = t2.devid
AND t1.RUNTIME = '17-jul-2018'
AND t2.runtime > '30-jun-2018'
ORDER BY t1.devID, (CASE WHEN t2.status LIKE 'G%' THEN 0 END), t2.runtime;
Now I am not able to group it to a single record per device(getting many records per device). Can you suggest a solution on this?
This would be easier to interpret with sample data and results, but it sounds like you want something like:
select devid, runtime, status, prev_status,
coalesce(good_status, prev_status) as best_status
from (
select devid, runtime, status,
lag(status) over (partition by devid order by runtime) as prev_status,
max(case when status = 'Good' then status end) over (partition by devid) as good_status
from (
select devid, runtime, status
from devtable
where runtime > date '2018-06-30'
)
)
where runtime = date '2018-07-17';
The innermost query restricts the date range; if you need an upper bound on that (i.e. it isn't today as in your example) then include that as another filter.
The next layer out uses lag() and max() analytic functions to find the previous status, and any 'Good' status (via a case expression), for each ID.
The outer query then filters to only show the target end date, and uses coalesce() to show 'Good' if that existed, or the previous status if not.
Demo with some made-up sample data in a CTE:
with devtable (devid, runtime, status) as (
select 1, date '2018-06-30', 'Good' from dual -- should be ignored
union all select 1, date '2018-07-01', 'a' from dual
union all select 1, date '2018-07-16', 'b' from dual
union all select 1, date '2018-07-17', 'c' from dual
union all select 2, date '2018-07-01', 'Good' from dual
union all select 2, date '2018-07-16', 'e' from dual
union all select 2, date '2018-07-17', 'f' from dual
union all select 3, date '2018-07-01', 'g' from dual
union all select 3, date '2018-07-16', 'Good' from dual
union all select 3, date '2018-07-17', 'i' from dual
union all select 4, date '2018-07-01', 'j' from dual
union all select 4, date '2018-07-16', 'k' from dual
union all select 4, date '2018-07-17', 'Good' from dual
)
select devid, runtime, status, prev_status,
coalesce(good_status, prev_status) as best_status
from (
select devid, runtime, status,
lag(status) over (partition by devid order by runtime) as prev_status,
max(case when status = 'Good' then status end) over (partition by devid) as good_status
from (
select devid, runtime, status
from devtable
where runtime > date '2018-06-30'
)
)
where runtime = date '2018-07-17';
DEVID RUNTIME STAT PREV BEST
---------- ---------- ---- ---- ----
1 2018-07-17 c b b
2 2018-07-17 f e Good
3 2018-07-17 i Good Good
4 2018-07-17 Good k Good
You could remove the innermost query by moving that filter into the case expression:
select devid, runtime, status, prev_status,
coalesce(good_status, prev_status) as best_status
from (
select devid, runtime, status,
lag(status) over (partition by devid order by runtime) as prev_status,
max(case when runtime > date '2018-06-30' and status = 'Good' then status end)
over (partition by devid) as good_status
from devtable
)
where runtime = date '2018-07-17';
but that would probably do quite a lot more work as it would examine and calculate a lot of data you don't care about.
Analytic functions should do what you want. It is unclear what your results should look like, but this gathers the information you need:
SELECT d.*
FROM (SELECT d.*,
LAG(d.status) OVER (PARTITION BY d.devid ORDER BY d.runtime) as prev_status,
LAG(d.runtime) OVER (PARTITION BY d.devid ORDER BY d.runtime) as prev_runtime,
ROW_NUMBER() OVER (PARTITION BY d.devid ORDER BY d.runtime) as seqnum,
SUM(CASE WHEN status = 'GOOD' THEN 1 ELSE 0 END) OVER (PARTITION BY d.devid) as num_good
FROM devtable d
WHERE d.runtime = DATE '2018-07-17' AND
d.runtime > DATE '2018-06-2018'
) d
WHERE seqnum = 1;

Connect by level for SQL Server 2008 like Oracle

nDays := Round( dEndTime - dStartTime ) + 1;
For i in 1..7 Loop
nDay := i + 1;
if i = 7 Then
nDay := 1;
End If;
SELECT To_Date(To_Char((dStartTime+Level-1),'DD.MM.YYYY')||' 00:00','DD.MM.YYYY HH24:MI'),
To_Date(To_Char((dStartTime+Level-1),'DD.MM.YYYY')||' 23:59','DD.MM.YYYY HH24:MI')
FROM DUAL
WHERE To_Char( dStartTime + Level -1 , 'd' ) = To_Char(nDay)
CONNECT BY Level <= nDays;
End Loop;
output:
22-JUL-12
23-JUL-12
18-JUL-12
19-JUL-12
20-JUL-12
21-JUL-12
I need to convert this query to SQL Server 2008, please help to find workaround with the same......
I have tried above output with single query with nDay from 1 to 7.....
You can do this with a recursive CTE. However, that syntax can be hard to remember, so when I need a handful of items, I do something like:
select *
from (select row_number() over (order by (select NULL)) as seqnum
from information_schema.columns
) t
where seqnum < <value>
Any table can be used. I just put in the INFORMATION_SCHEMA.columns table, because it is easy and generally has dozens or hundreds of rows.
Your output doesn't match the query. The following generates all dates between two values that are reasonably close together, Here is an example:
declare #dstarttime date = '2012-07-11', #dendtime date= '2012-07-13';
with const as (select #dstarttime as dStartTime, #dendtime as dendTime)
SELECT DATEADD(d, seqnum - 1, dStartTime)
FROM (select *
from (select row_number() over (order by (select NULL)) as seqnum, const.*
from information_schema.columns cross join const
) t
where seqnum <= DATEDIFF(d, dStartTime, dendTime) + 1
) t
As I said, you can also do this with recursive CTEs or, if you have one, with a calendar table.