I tried to use subquery in CASE THEN like below.(I wrote down the code in ORACLE)
WITH SAMPLE AS (
SELECT 1 AS VALUE_,
'20191230' AS DATE_
FROM DUAL
UNION
SELECT 2 AS VALUE_,
'20201230' AS DATE_
FROM DUAL)
SELECT VALUE_,
DATE_,
CASE
WHEN TO_CHAR(ADD_MONTHS(DATE, -12), 'YYYYMMDD') IN (SELECT DATE_ FROM SAMPLE)
THEN (SELECT VALUE_ FROM SAMPLE WHERE DATE_ = TO_CHAR(ADD_MONTHS(DATE, -12),
'YYYYMMDD'))
ELSE NULL
END AS ONE_YEAR_AGO_VALUE_
FROM SAMPLE;
I expected to get a new column(ONE_YEAR_AGO_VALUE_) that has NULL in first row and 1 in second row.
However, result was different then I expected.
The result had a 'ONE_YEAR_AGO_VALUE_' that had NULL in first row and NULL in second row.
I want to make second row have 1.
If you let me know the wrong thing in my code, I really appreciate that.
Thank you.
When I fix your query to have correct use of dates, then it produces one row with NULL and one with 1:
WITH SAMPLE AS (
SELECT 1 AS VALUE_, DATE '2019-12-30' AS DATE_
FROM DUAL
UNION ALL
SELECT 2 AS VALUE_, DATE '2020-12-30' AS DATE_
FROM DUAL
)
SELECT S.VALUE_, S.DATE_,
(CASE WHEN ADD_MONTHS(S.DATE_, -12) IN (SELECT S2.DATE_ FROM SAMPLE S2)
THEN (SELECT S2.VALUE_ FROM SAMPLE S2 WHERE S2.DATE_ = ADD_MONTHS(S.DATE_, -12))
END) AS ONE_YEAR_AGO_VALUE_
FROM SAMPLE S;
Your code is also overly complicated. You don't need a CASE expression, which you can use directly for the value. Here is a db<>fiddle.
Related
I ran into a simple bit of code, but I've been wracking my brains ever since.
It's either counter-intuitive, or I'm missing something, either simple or fundamental. No idea.
WITH T1 AS
(
SELECT TO_DATE ('14/11/19', 'DD/MM/YY') as SomeDate FROM DUAL
UNION
SELECT TO_DATE ('28/11/19', 'DD/MM/YY') as SomeDate FROM DUAL
),
T2 AS
(
SELECT TO_DATE ('14/11/19', 'DD/MM/YY') as SomeDate FROM DUAL
UNION
SELECT TO_DATE ('28/11/19', 'DD/MM/YY') as SomeDate FROM DUAL
)
SELECT * FROM T1 WHERE T1.SomeDate >= TO_DATE ('05/11/19','DD/MM/YY')
AND NOT EXISTS
(SELECT 1 FROM T2
WHERE T2.SomeDate >= TO_DATE ('05/11/19','DD/MM/YY')
AND T2.Somedate < T1.Somedate
);
Excluding all other conditions, the basic version does something like the code above.
The subquery returns all rows where SomeDate = 14/11/19 since those are the ones that are greater than the 5th but strictly lower than the 28th.
And Not Exists should evaluate to false when rows are returned.
So by my logic, that means that excluding all rows with 14/11/19, the code above should return 28/11/19.
But it returns '14/11/2019'.
Could someone please explain what I'm missing?
Thank you very much.
I would write the query like this so I can read it more easily:
WITH T1 AS (
SELECT DATE '2019-11-14' as SomeDate FROM DUAL UNION ALL
SELECT DATE '2019-11-28' as SomeDate FROM DUAL
),
T2 AS (
SELECT DATE '2019-11-14' as SomeDate FROM DUAL UNION ALL
SELECT DATE '2019-11-28' as SomeDate FROM DUAL
)
SELECT *
FROM T1
WHERE T1.SomeDate >= DATE '2019-11-05' AND
NOT EXISTS (SELECT 1
FROM T2
WHERE T2.SomeDate >= DATE '2019-11-05' AND
T2.Somedate < T1.Somedate
);
I'm not sure where your confusion is. There are two possibilities for T1.SomeDate, 2019-11-14 and 2019-11-18.
Only the earlier date (2019-11-14) has no rows in T2 that are less than the date. Hence, NOT EXISTS matches this date.
I suspect that you are confused by the inequality in the subquery.
I have a query which is order by date , there is the query I have simplified it a bit but basically is :
select * from
(select start_date, to_char(end_date,'YYYY-mm-dd') as end_date from date_table
order by start_date ,end_date )
where start_date is null or end_date is null
It shows prefect order
but I add
union all
select start_date, 'single missing day' as end_date from
calendar_dates
where db_date>'2017-12-12' and db_date<'2018-05-13'
Then the whole order messed up. Why is that happened? Union or union all should just append the dataset from first query with the second, right? It should not mess the order in the first query, right?
I know this query doesn't makes any sense, but I have simplified it to
show the syntax.
You can't predict what would be the order outcome by just assuming that UNION ALL will append queries in the order you write them.
The query planner will execute your queries in whatever order it sees it fit. That's why you have the ORDER BY clause. Use it !
For example, if you want to force the order of the first query, then the second, do :
select * from
(select 1, start_date, to_char(end_date,'YYYY-mm-dd') as end_date from date_table
order by start_date ,end_date )
where start_date is null or end_date is null
union all
select 2, start_date, 'single missing day' as end_date from
calendar_dates
where db_date>'2017-12-12' and db_date<'2018-05-13'
ORDER BY 1
You are mistaken. This query:
select d.*
from (select start_date, to_char(end_date,'YYYY-mm-dd') as end_date
from date_table
order by start_date, end_date
) d
where start_date is null or end_date is null
does not "show perfect order". I might just happen to produce the ordering that you want, but that is a coincidence. The only way to get results in a particular order is to use ORDER BY in the outermost SELECT. Period.
So, if you want results in a particular order, then use order by:
select d.*
from ((select d.start_date, to_char(end_date, 'YYYY-mm-dd') as end_date, 1 as ord
from date_table d
where d.start_date is null or d.end_date is null
order by start_date, end_date
) union all
(select cd.start_date, 'single missing day' as end_date, 2 as ord
from calendar_dates cd
where cd.db_date > '2017-12-12' and cd.db_date < '2018-05-13'
)
) d
order by ord, start_date;
UNION or UNION ALL will mess up the order in the first SELECT. Therefore, we can make a trick that we will re-order these columns in the Outer Select as below:
SELECT * FROM
(
select colA, colB
From TableA
-- ORDER BY colA, colB --
UNION ALL
select colC, colD
FROM TableB
ORDER BY colC, colD
) tb
ORDER BY colA, colB
I have a table and each record has a date. We can assume that a date range is contiguous if there's not a 3 month break. How can I find the start of the most recent contiguous date range?
For example, imagine if I had this data:
1990-5-1
1990-6-4
1990-10-28
1990-11-14
1990-12-19
1991-1-20
1991-4-30
1991-5-13
I'd like for it to return 1991-4-30 because it's the start of the most recent contiguous range of dates.
I think this does what you're looking for. Using my own table and column names as test data. This is on Oracle.
select * from (
select * from sm_ss_tickets t1 where exists (
select * from sm_ss_tickets t2 where t2.created_date between t1.created_date and t1.created_date+90 and t1.rowid <> t2.rowid
) order by created_date asc
) where rownum = 1;
Maybe something like the following would work:
WITH d1 AS (
SELECT date'1990-05-01' AS dt FROM dual
UNION ALL
SELECT date'1990-06-04' AS dt FROM dual
UNION ALL
SELECT date'1990-10-28' AS dt FROM dual
UNION ALL
SELECT date'1990-11-14' AS dt FROM dual
UNION ALL
SELECT date'1990-12-19' AS dt FROM dual
UNION ALL
SELECT date'1991-01-20' AS dt FROM dual
UNION ALL
SELECT date'1991-04-30' AS dt FROM dual
UNION ALL
SELECT date'1991-05-13' AS dt FROM dual
)
SELECT MAX(dt) FROM (
SELECT dt, LAG(dt) OVER ( ORDER BY dt ) AS prev_dt, LEAD(dt) OVER ( ORDER BY dt ) AS next_dt
FROM d1
) WHERE ( dt > ADD_MONTHS(prev_dt, 3) OR prev_dt IS NULL )
AND dt > ADD_MONTHS(next_dt, -3)
In the above, a date can only be the start of a contiguous sequence if there is no prior date within 3 months (either it is more than three months ago or it doesn't exist at all) and there is also a subsequent date within 3 months.
You can use LAG and LEAD. Find the query below. I think it works fine.
tmp_year is the table I have created. tdate is the column.
The records in the table are
28-JAN-15
27-JAN-15
26-JAN-15
25-JAN-15
12-JUL-14
11-JUL-14
10-JUL-14
09-JUL-14
24-DEC-13
23-DEC-13
22-DEC-13
21-DEC-13
15-SEP-13
07-JUN-13
27-FEB-13
19-NOV-12
11-AUG-12
Please find the query which returns 25th Jan 2015.
select max(d.tdate) from (
select c.tdate,c.next_date,c.date_diff,lag(date_diff) over( order by tdate) prev_diff from (
select b.tdate ,b.next_date,(next_date-tdate) date_diff from
(select a.tdate,lead(a.tdate) over(order by a.tdate) next_date from tmp_year a ) b ) c) d where d.date_diff<90 and d.prev_diff>=90;
I have following table tbl in database and I have dynamic joining date 1-1-2012 and I want this date is between (Fall and spring) or (spring and summer) or (summer and fall).I want query in which i passed only joining date which return semestertime and joining date in Oracle.
Semestertime joiningDate
Fall 10-13-2011
Spring 2-1-2012
Summer 6-11-2012
Fall 10-1-2015
If I understand your question correctly:
SELECT *
FROM your_table
WHERE joiningDate between to_date (your_lower_limit_date_here, 'mm-dd-yyyy')
AND to_date (your_upper_limit_date_here, 'mm-dd-yyyy`);
What about something like that:
select 'BEFORE' term,
t."Semestertime", to_char(t."joiningDate", 'MM-DD-YYYY')
from (
select tbl.*, rownum rn from tbl where tbl."joiningDate" < to_date('1-1-2012','MM-DD-YYYY')
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- your reference date
order by tbl."joiningDate" desc) t
where rn = 1
union all
select 'AFTER' term,
t."Semestertime", to_char(t."joiningDate", 'MM-DD-YYYY')
from (
select tbl.*, rownum rn from tbl where tbl."joiningDate" > to_date('1-1-2012','MM-DD-YYYY')
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-- your reference date
order by tbl."joiningDate" asc) t
where rn = 1
This will return the "term" before and after a given date. You will probably have to adapt such query to your specific needs. But that might be a good starting point.
For example, given your business rules, you might consider using <= instead of <. You you might require to have the result displayer a column instead of rows. Bu all of this shouldn't be too had to change.
As an alternate solution using CTE and sub-queries:
with testdata as (select to_date('1-1-2012','MM-DD-YYYY') refdate from dual)
select v.what, tbl.* from tbl join
(
select 'BEFORE' what, max(t1."joiningDate") d
from tbl t1
where t1."joiningDate" < to_date('1-1-2012','MM-DD-YYYY')
union all
select 'AFTER' what, min(t1."joiningDate") d
from tbl t1
where t1."joiningDate" > to_date('1-1-2012','MM-DD-YYYY')
) v
on tbl."joiningDate" = v.d
See http://sqlfiddle.com/#!4/c7fa5/15 for a live demo comparing those solutions.
I'm trying to implement this askTom solution to count the business days between two date fields in a table.
select count(*)
from ( select rownum rnum
from all_objects
where rownum <= to_date('&1') - to_date('&2')+1 )
where to_char( to_date('&2')+rnum-1, 'DY' ) not in ( 'SAT', 'SUN' )
I don't know how I can pass values to toms code or how to do a work around so that each time the select executes with a different set of dates and that way obtaining a similar output :
rowid | bussiness_days
I guess this could be implemented with a PL/SQL block but I'd prefer to keep it down to a query if possible. Could it be possible to pass values using &1 parameters from a select above toms one?
This is not like the original askTom, but if you're using 11gR2 you can use a Recursive CTE:
with rcte(a,b, i, is_wd) as (
select from_dt , to_dt , id, case when (to_char(from_dt, 'DY') in ('SAT','SUN')) then 0 else 1 end from t
union all
select decode(a, null, a,a+1), b, i, case when (to_char(a, 'DY') in ('SAT','SUN')) then 0 else 1 end
from rcte
where a+1 <= b
)
select i id, sum(is_wd)
from rcte
group by i
where t is a table containing "from_dates" and "to_dates"
Here is a sqlfiddle demo
Try this one:
SELECT COUNT(*)
FROM ( SELECT ROWNUM rnum
FROM all_objects
WHERE ROWNUM <= TO_DATE('2014-02-07','yyyy-mm-dd') - TO_DATE('2014-02-01','yyyy-mm-dd')+1 )
WHERE TO_CHAR( TO_DATE('2014-02-01','yyyy-mm-dd')+rnum-1, 'DY' ) NOT IN ( 'SAT', 'SUN' )
However, it has at least two issues:
It presumes the session NLS_DATE_LANGUAGE is set to English
The query gives wrong result if number of rows in ALL_OBJECTS is smaller than the amount of days between your ranges.
The version is less error-prone and faster:
WITH t AS
(SELECT TO_DATE('2014-02-01','yyyy-mm-dd')+LEVEL-1 the_date
FROM dual
CONNECT BY TO_DATE('2014-02-01','yyyy-mm-dd')+LEVEL-1 <= TO_DATE('2014-02-07','yyyy-mm-dd'))
SELECT COUNT(*)
FROM t
WHERE TO_CHAR(the_date, 'DY', 'NLS_DATE_LANGUAGE = AMERICAN') NOT IN ( 'SAT', 'SUN' )
This is another situation where a calendar table comes in handy. A calendar table has a row for every date.
If Saturday and Sunday are not business days, it's quite likely that holidays are not either. A field in the calendar table to indicate holidays makes it a lot easier to exclude them from your business days calculation.