How to compare the timestamp of the exact hours/minute/seconds? - sql

I have to columns:
BEFORE_TIME & AFTER_TIME
2021-09-02 09:41:00.000 || 2021-09-09 09:41:00.000
2021-09-03 09:09:07.000 || 2021-09-10 09:09:07.000
2021-09-03 13:45:48.000 || 2021-09-10 15:45:48.000
I want to compare the hours/minutes and seconds to see if they match. I can ignore the years/months and days. Is there a way I can do this?
For example, in the above, I know the first two records match since the have the same hours, minutes and seconds in both columns, but the last record does not match.

You can use trunc() to truncate the timestamp to seconds, and cast to ::time to remove the date part:
select a=b
, trunc(a::time, 'second') = trunc(b::time, 'second')
from (
select '2021-01-01 10:10:10.313'::timestamp a
, '2021-08-01 10:10:10.444'::timestamp b
)
(Which gives false and true)

Snowflake provides HOUR(), MINUTE() and SECOND() functions:
https://docs.snowflake.com/en/sql-reference/functions/hour-minute-second.html
WITH t AS (
SELECT
'2021-09-02 09:41:00.000'::timestamp as before_time,
'2021-09-09 09:41:00.000'::timestamp as after_time
UNION
SELECT
'2021-09-03 09:09:07.000'::timestamp as before_time,
'2021-09-10 09:09:07.000'::timestamp as after_time
UNION
SELECT
'2021-09-03 13:45:48.000'::timestamp as before_time,
'2021-09-10 15:45:48.000'::timestamp as after_time
)
SELECT
hour(before_time) = hour(after_time)
and minute(before_time) = minute(after_time)
and second(before_time) = second(before_time) as time_check
FROM
t;
Or you can put it inside a UDF:
CREATE OR REPLACE FUNCTION time_check (
BEFORE_TIME timestamp,
AFTER_TIME timestamp
)
returns boolean
LANGUAGE JAVASCRIPT
as
$$
// do some validation here
var before = new Date(BEFORE_TIME);
var after = new Date(AFTER_TIME);
return before.getSeconds() == after.getSeconds() &&
before.getMinutes() == after.getMinutes() &&
before.getHours() == after.getHours();
$$
;
WITH t AS (
SELECT
'2021-09-02 09:41:00.000'::timestamp as before_time,
'2021-09-09 09:41:00.000'::timestamp as after_time
UNION
SELECT
'2021-09-03 09:09:07.000'::timestamp as before_time,
'2021-09-10 09:09:07.000'::timestamp as after_time
UNION
SELECT
'2021-09-03 13:45:48.000'::timestamp as before_time,
'2021-09-10 15:45:48.000'::timestamp as after_time
)
SELECT
time_check(before_time, after_time) as time_check
FROM
t;
Both of above should return:
+------------+
| TIME_CHECK |
|------------|
| True |
| True |
| False |
+------------+

Related

Comparing with other rows in table

Hi brilliant thinkers,
I want to create a CASE condition to give me a "yes" for active_users that is if there exists within 60 days, a more recent uuid_ts for the same anonymous_id.
SELECT t1.anonymous_id user_id,
t1.uuid_ts activity_date,
t2.uuid_ts signup_date,
-- Activity Lifetime: difference of number of days signed up to last activity
DATE_DIFF(CAST(t2.uuid_ts AS DATE), CAST(t1.uuid_ts AS DATE), DAY) AS activity_lifetime,
-- New Users: If month of activity is same as sign_up month
(CASE WHEN DATE_DIFF(CAST(t1.uuid_ts AS DATE), CAST(t2.uuid_ts AS DATE), MONTH)=0 THEN TRUE ELSE FALSE END) AS new_user,
-- Active Users: If month of activity is greater than sign_up month AND activity is found
(CASE WHEN DATE_DIFF(CAST(t1.uuid_ts AS DATE), CAST(t2.uuid_ts AS DATE), MONTH)>0
-- ** ____ NEED HELP HERE ____ **
AND anonymous_id NOT IN (SELECT anonymous_id FROM datascience.last_user_activity)
AND DATE_ADD(activity_date, INTERVAL 60 DAY) > (S)
FROM datascience.last_user_activity AS t1
INNER JOIN datascience.full_signup_completed AS t2
ON t2.anonymous_id = t1.anonymous_id
WHERE DATE(t1.uuid_ts) IS NOT NULL AND DATE(t2.uuid_ts) IS NOT NULL
ORDER BY activity_lifetime DESC
SAMPLE DATA:
anon_id|signup_date|activity_date|
__________________________________
123 |01-01-2019 |02-01-2019 |
123 |01-01-2019 |02-02-2019 |
123 |01-01-2019 |02-03-2019 |
123 |01-01-2019 |02-04-2019 |
WANTED:
anon_id|signup_date|activity_date| active
__________________________________
123 |01-01-2019 |02-01-2019 | yes
123 |01-01-2019 |02-02-2019 | yes
123 |01-01-2019 |02-03-2019 | no
123 |01-01-2019 |02-04-2019 | no
if a future date exists in the same row, within the range of 60 days, then the field active shows "yes", else a "no".
Still not 100% sure this is what you are looking for, but I hope it helps:
WITHIN 60 days:
(The output would be "yes, yes, yes, no" since 02-04-2019 > 02-03-2019 and within 60 days)
WITH
sample_data AS (
SELECT
'123' AS anon_id, DATE('2019-01-01') AS signup_date,
DATE('2019-01-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-02-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-03-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-04-02') AS activity_date)
SELECT
anon_id,
signup_date,
activity_date,
(CASE
WHEN EXISTS( SELECT 'found' FROM sample_data t2 WHERE t2.anon_id = t1.anon_id AND t2.activity_date > t1.activity_date AND t2.activity_date <= DATE_ADD(t1.activity_date, INTERVAL 60 DAY)) THEN 'yes'
ELSE
'no'
END
) AS active
FROM
sample_data t1
ORDER BY 1,2,3
60 DAYS or BEYOND:
(The output would be "yes, no, no, no" since February has 28 days and March 31, so between 02-02-2019 and 02-04-2019 there are 59 days)
WITH
sample_data AS (
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-01-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-02-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-03-02') AS activity_date
UNION ALL
SELECT
'123' AS anon_id,
DATE('2019-01-01') AS signup_date,
DATE('2019-04-02') AS activity_date)
SELECT
anon_id,
signup_date,
activity_date,
(CASE
WHEN EXISTS( SELECT 'found' FROM sample_data t2 WHERE t2.anon_id = t1.anon_id AND t2.activity_date >= DATE_ADD(t1.activity_date, INTERVAL 60 DAY)) THEN 'yes'
ELSE
'no'
END
) AS active
FROM
sample_data t1
ORDER BY 1,2,3
Your question/logic/dates are a bit unclear, but I think the following query should point you in the right direction.
with joined as (
-- Join your tables and handle casting here (only have to do it once)
select
anonymous_id,
date(full_signup_completed.uuid_ts) as signup_date,
extract(month from full_signup_completed.uuid_ts) as signup_month,
date(last_user_activity.uuid_ts) as activity_date,
extract(month from last_user_activity.uuid_ts) as activity_month
from datascience.full_signup_completed
left join datascience.last_user_activity using(anonymous_id)
where full_signup_completed.uuid_ts is not null and last_user_activity.uuid_ts is not null
),
activity60 as (
-- for each activity date, is there a future activity date within 60 days?
select j1.anonymous_id,j1.activity_date, true as has_activity_within_60_days
from joined j1
cross join joined j2
where j1.anonymous_id = j2.anonymous_id and date_diff(j2.activity_date, j1.activity_date, day) <= 60
group by 1,2
),
final as (
-- Get all of your logic
select
joined.*,
date_diff(activity_date,signup_date, day) as activity_lifetime,
signup_month = activity_month as new_user, -- Evaluates to T/F
(activity_month > signup_month) and has_activity_within_60_days as your_custom_field -- Evaluates to aT/F
from joined
inner join activity60 using(anonymous_id,activity_date)
)
select * from final
order by activity_lifetime desc
In your example, are your dates in DD-MM-YYYY format? If not, i'm not sure how the 60 day constraint makes sense.

Query Items which were resold within 1 year of last selling date

I have a table with details of sold cars. Some of these cars have been resold within last 1, 2 or 3 years. The table looks like this:
Car_Type || Car_Reg_No || Sold_Date || Listing_No
Hatch || 23789 || 2017-02-03 11:26 || X6529
Coupe || 16723 || 2016-11-07 09:40 || N8156
Sedan || 35216 || 2016-05-23 10:34 || M8164
Hatch || 23789 || 2016-09-16 04:30 || O7361
Now, I need to query records (cars) which were re-sold within 1 year of their latest sold date and how many times were they sold. So, my output would be like this:
Car_Type || Car_Reg_No || Sold_Count || Latest_Sold_Date
Hatch || 23789 || 2 || 2017-02-03 11:26
In essence, How do I check for re-sold records within a specific time frame of their latest sold date?
You can do this by finding the max, and joining based on your conditions.
declare #TableA table (Car_Type varchar(64)
,Car_Reg_No int
,Sold_Date datetime
,Listing_No varchar(6))
insert into #TableA
values
insert into #TableA
values
('Hatch',23789,'2017-02-03 11:26','X6529'),
('Coupe',16723,'2017-11-07 09:40','N8156'),
('Sedan',35216,'2017-05-23 10:34','M8164'),
('Hatch',23789,'2016-09-16 04:30','O7361'),
('Coupe',16723,'2014-11-07 09:40','N8156')
;with cte as(
select
Car_Type
,Car_Reg_No
,Latest_Sold_Date = max(Sold_Date)
from
#TableA
group by
Car_Type
,Car_Reg_No)
select
a.Car_Type
,a.Car_Reg_No
,Sold_Count = count(b.Listing_No) + 1
,a.Latest_Sold_Date
from cte a
inner join
#TableA b on
b.Car_Reg_No = a.Car_Reg_No
and b.Sold_Date != a.Latest_Sold_Date
and datediff(day,b.Sold_Date,a.Latest_Sold_Date) < 366
--if you want only cars which were sold within last year too, uncomment this
--and datediff(day,a.Latest_Sold_Date,getdate()) < 366
group by
a.Car_Type
,a.Car_Reg_No
,a.Latest_Sold_Date
By my understanding..,
select sd1.Car_Type, sd1.Car_Reg_No,
count(sd1.Car_Reg_No) + 1 'no of sales in last one year', --1 is added because, see the last condition
sd1.Sold_Date 'Last sold date'
from(
select *,ROW_NUMBER() over(partition by Car_Reg_No order by sold_date desc) as rn from #Table) as sd1
join
(select * from #Table) as sd2
on sd1.Car_Type = sd2.Car_Type
and DATEDIFF(dd,sd2.Sold_Date,sd1.Sold_Date) < 366
and sd1.rn = 1
and sd1.Sold_Date <> sd2.Sold_Date -- here last sold is eliminated. so count is added by one.
group by sd1.Car_Type,sd1.Sold_Date, sd1.Car_Reg_No
order by sd1.Car_Reg_No

SQL query to find all timestamps covered by an interval in A but not covered by an interval in B ("subtract" or "except" between multiple intervals)

I have multiple tables in a PostgreSQL 9.4 database, where each row contains an interval as two columns "start" (inclusive) and "stop" (exclusive).
Consider the following pseudo-code (the tables are more complicated).
CREATE TABLE left (
start TIMESTAMP,
stop TIMESTAMP,
[...]
);
CREATE TABLE right (
start TIMESTAMP,
stop TIMESTAMP,
[...]
);
The intervals are inclusive of the start, but exclusive of the stop.
I now need a query to find all possible intervals of time where there is a row in "left" covering the interval, but not simultaneously a row in "right" covering the same interval.
One interval in "left" can be cut up into any number of intervals in the result, be shortened, or be entirely absent. Consider the following graph, with time progressing from left to right:
left [-----row 1------------------) [--row 2--) [--row 3----)
right [--row1--) [--row2--) [--row3--)
result [----) [----) [-------) [-----------)
In this tiny example, "left" has tree rows each representing three intervals and "right" has three rows, each representing three other intervals.
The result has four rows of intervals, which together cover all possible timestamps where there is a row/interval in "left" covering that timestamp, but not a row/interval in "right" covering the same timestamp.
The tables are of course in reality very much larger than three rows each - in fact I will frequently be wanting to perform the algorithm between two subqueries that have the "start" and "stop" columns.
I have hit a dead end (multiple dead ends, in fact), and am on the virge of just fetching all records into memory and applying some procedural programming to the problem...
Any solutions or suggestions of what thinking to apply is greatly appreciated.
Change the types of columns to tsrange (or create an appropriate views):
CREATE TABLE leftr (
duration tsrange
);
CREATE TABLE rightr (
duration tsrange
);
insert into leftr values
('[2015-01-03, 2015-01-20)'),
('[2015-01-25, 2015-02-01)'),
('[2015-02-08, 2015-02-15)');
insert into rightr values
('[2015-01-01, 2015-01-06)'),
('[2015-01-10, 2015-01-15)'),
('[2015-01-18, 2015-01-26)');
The query:
select duration* gap result
from (
select tsrange(upper(duration), lower(lead(duration) over (order by duration))) gap
from rightr
) inv
join leftr
on duration && gap
result
-----------------------------------------------
["2015-01-06 00:00:00","2015-01-10 00:00:00")
["2015-01-15 00:00:00","2015-01-18 00:00:00")
["2015-01-26 00:00:00","2015-02-01 00:00:00")
["2015-02-08 00:00:00","2015-02-15 00:00:00")
(4 rows)
The idea:
l [-----row 1------------------) [--row 2--) [--row 3----)
r [--row1--) [--row2--) [--row3--)
inv(r) [----) [----) [------------------------->
l*inv(r) [----) [----) [-------) [-----------)
If the type change to tsrange is not an option, here an alternative solution using window function.
The important idea is to realize that only the start and end points of the intervals are relavent. In the first step a transformation in a sequence of starting and ending timestamps is performed. (I use numbers to simplify the example).
insert into t_left
select 1,4 from dual union all
select 6,9 from dual union all
select 12,13 from dual
;
insert into t_right
select 2,3 from dual union all
select 5,7 from dual union all
select 8,10 from dual union all
select 11,14 from dual
;
with event as (
select i_start tst, 1 left_change, 0 right_change from t_left union all
select i_stop tst, -1 left_change, 0 right_change from t_left union all
select i_start tst, 0 left_change, 1 right_change from t_right union all
select i_stop tst, 0 left_change, -1 right_change from t_right
)
select tst, left_change, right_change,
sum(left_change) over (order by tst) as is_left,
sum(right_change) over (order by tst) as is_right,
'['||tst||','||lead(tst) over (order by tst) ||')' intrvl
from event
order by tst;
This ends with a two recods for each interval one for start (+1) and one for end (-1 in the CHANGE column).
TST LEFT_CHANGE RIGHT_CHANGE IS_LEFT IS_RIGHT INTRVL
1 1 0 1 0 [1,2)
2 0 1 1 1 [2,3)
3 0 -1 1 0 [3,4)
4 -1 0 0 0 [4,5)
5 0 1 0 1 [5,6)
6 1 0 1 1 [6,7)
7 0 -1 1 0 [7,8)
8 0 1 1 1 [8,9)
9 -1 0 0 1 [9,10)
10 0 -1 0 0 [10,11)
11 0 1 0 1 [11,12)
12 1 0 1 1 [12,13)
13 -1 0 0 1 [13,14)
14 0 -1 0 0 [14,)
The window SUM finction
sum(left_change) over (order by tst)
adds all changes so far, yielding the 1 for beeing in interval and 0 beeing out of the interval.
The filter to get all (sub)intervals that are left only ist therefore trivial
is_left = 1 and is_right = 0
The (sub)interval start with the timstamp of the current row and ends with the timstamp of the next row.
Final notes:
You may need to add logik to ignore intervals of leghth 0
I'm testing in Oracle, so pls re-check the Postgres functionality
For completeness: the naive method, without using interval types.
[I used the same sample data as #klin ]
CREATE TABLE tleft (
start TIMESTAMP,
stop TIMESTAMP,
payload text
);
INSERT INTO tleft(start,stop) VALUES
-- ('2015-01-08', '2015-03-07'), ('2015-03-21', '2015-04-14'), ('2015-05-01', '2015-05-15') ;
('2015-01-03', '2015-01-20'), ('2015-01-25', '2015-02-01'), ('2015-02-08', '2015-02-15');
CREATE TABLE tright (
start TIMESTAMP,
stop TIMESTAMP,
payload text
);
INSERT INTO tright(start,stop) VALUES
-- ('2015-01-01', '2015-01-15'), ('2015-02-01', '2015-02-14'), ('2015-03-01', '2015-04-07') ;
('2015-01-01', '2015-01-06'), ('2015-01-10', '2015-01-15'), ('2015-01-18', '2015-01-26');
-- Combine all {start,stop} events into one time series
-- , encoding the event-type into a state change.
-- Note: this assumes non-overlapping intervals in both
-- left and right tables.
WITH zzz AS (
SELECT stamp, SUM(state) AS state
FROM (
SELECT 1 AS state, start AS stamp FROM tleft
UNION ALL
SELECT -1 AS state, stop AS stamp FROM tleft
UNION ALL
SELECT 2 AS state, start AS stamp FROM tright
UNION ALL
SELECT -2 AS state, stop AS stamp FROM tright
) zz
GROUP BY stamp
)
-- Reconstruct *all* (sub)intervals
-- , and calculate a "running sum" over the state variable
SELECT * FROM (
SELECT zzz.stamp AS zstart
, LEAD(zzz.stamp) OVER (www) AS zstop
, zzz.state
, row_number() OVER(www) AS rn
, SUM(state) OVER(www) AS sstate
FROM zzz
WINDOW www AS (ORDER BY stamp)
) sub
-- extract only the (starting) state we are interested in
WHERE sub.sstate = 1
ORDER BY sub.zstart
;
Result:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 3
CREATE TABLE
INSERT 0 3
zstart | zstop | state | rn | sstate
---------------------+---------------------+-------+----+--------
2015-01-06 00:00:00 | 2015-01-10 00:00:00 | -2 | 3 | 1
2015-01-15 00:00:00 | 2015-01-18 00:00:00 | -2 | 5 | 1
2015-01-26 00:00:00 | 2015-02-01 00:00:00 | -2 | 9 | 1
2015-02-08 00:00:00 | 2015-02-15 00:00:00 | 1 | 11 | 1
(4 rows)
If tsrange is not an option maybe stored procedure is?
Something like this:
--create tables
drop table if exists tdate1;
drop table if exists tdate2;
create table tdate1(start timestamp, stop timestamp);
create table tdate2(start timestamp, stop timestamp);
--populate tables
insert into tdate1(start, stop) values('2015-01-01 00:10', '2015-01-01 01:00');
insert into tdate2(start, stop) values('2015-01-01 00:00', '2015-01-01 00:20');
insert into tdate2(start, stop) values('2015-01-01 00:30', '2015-01-01 00:40');
insert into tdate2(start, stop) values('2015-01-01 00:50', '2015-01-01 01:20');
insert into tdate1(start, stop) values('2015-01-01 01:10', '2015-01-01 02:00');
insert into tdate1(start, stop) values('2015-01-01 02:10', '2015-01-01 03:00');
--stored procedure itself
create or replace function tdate_periods(out start timestamp, out stop timestamp)
returns setof record as
$$
declare
rec record;
laststart timestamp = null;
startdt timestamp = null;
stopdt timestamp = null;
begin
for rec in
select
t1.start as t1start,
t1.stop as t1stop,
t2.start as t2start,
t2.stop as t2stop
from tdate1 t1
left join tdate2 t2 on t2.stop > t1.start or t2.start > t1.stop
loop
if laststart <> rec.t1start or laststart is null then
if laststart is not null then
if startdt < stopdt then
start = startdt;
stop = stopdt;
return next;
startdt = stopdt;
end if;
end if;
startdt = rec.t1start;
stopdt = rec.t1stop;
laststart = startdt;
end if;
if rec.t2start is not null then
if startdt < rec.t2start then
start = startdt;
stop = rec.t2start;
return next;
end if;
startdt = rec.t2stop;
end if;
end loop;
if startdt is not null and startdt < stopdt then
start = startdt;
stop = stopdt;
return next;
end if;
end
$$ language plpgsql;
--call
select * from tdate_periods();

SQL Pulling results by year

Problem: I have dated salary information stored in a table. I need to display one result per year. For each year, I want to display the max dated record from the year prior. The problem is that some years don’t have data (salary didn’t change). I need these rows to contain the max record prior to that year (it may be from 2 years before or even 3).
My query right now would work if each row has data… but it doesn’t account for years where there is no data. How can I update this sql to pull the desired results below:
Example of data:
sch_sal_svc.beg_date -------sch_sal_svc.beg_date.per_plan_data
1/1/2007---100
6/1/2007---200
1/1/2008---300
1/1/2011---400
8/1/2011---500
9/1/2012---600
Current Results
1/1/2008---200
1/1/2011---300
1/1/2012---500
Desired Results
1/1/2008---200
1/1/2009---300
1/1/2010---300
1/1/2011---300
1/1/2012---500
SQL:
SELECT
years.control_id,
years.ssn,
ebe.plan_id,
to_number(to_char(years.sal_date,'yyyy')),
null as per_plan_salary,
null as per_vest_hours,
null as per_credsvc_hours,
LEAST(s.rate_1,cl.comp_genl),
null as salary_1,
null as per_comm,
null as per_overtime,
null as per_ncr,
null as salary_2
FROM
sch_sal_svc s
, (select distinct ssn, control_id, TRUNC(beg_date,'YEAR') as sal_date from sch_sal_svc where beg_date > to_date('12/31/1900', 'mm/dd/yyyy')) years
, employee_benefit_elig ebe, compliance_limits cl
WHERE
years.ssn = ebe.ssn
and years.control_id = ebe.control_id
and to_number(to_char(years.sal_date,'yyyy')) = cl.limit_year
and to_number(to_char(years.sal_date,'yyyy')) <= to_number(to_char(sysdate,'yyyy'))
and s.beg_date = (
select max(s2.beg_date) from sch_sal_svc s2
where s2.ssn = years.ssn and s2.control_id = years.control_id
and s2.beg_date <= years.sal_date
)
and s.ssn = years.ssn
and s.control_id = years.control_id
and ebe.benefit_id = 'DB'
and ebe.control_id = 'CLIENT'
and ebe.plan_id in ('100', '200')
CREATE TABLE sch_sal_svc
(
beg_date DATE
, per_plan_data NUMBER
);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2007', 'DD/MM/YYYY'), 100);
INSERT INTO sch_sal_svc VALUES(TO_DATE('06/01/2007', 'DD/MM/YYYY'), 200);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2008', 'DD/MM/YYYY'), 300);
INSERT INTO sch_sal_svc VALUES(TO_DATE('01/01/2011', 'DD/MM/YYYY'), 400);
INSERT INTO sch_sal_svc VALUES(TO_DATE('08/01/2011', 'DD/MM/YYYY'), 500);
INSERT INTO sch_sal_svc VALUES(TO_DATE('09/01/2012', 'DD/MM/YYYY'), 600);
SELECT MIN(beg_date) FROM sch_sal_svc;
-- 2007-01-01 00:00:00
SELECT d.r_level + NUMTOYMINTERVAL(1, 'YEAR') AS d_date
, NVL -- the salary must be updated at least once in three years
(
NVL
(
NVL
(
s.per_plan_data
, LAG(s.per_plan_data, 1) OVER (PARTITION BY 1 ORDER BY d.r_level)
)
, LAG(s.per_plan_data, 2) OVER (PARTITION BY 1 ORDER BY d.r_level)
)
, LAG(s.per_plan_data, 3) OVER (PARTITION BY 1 ORDER BY d.r_level)
) AS lag_per_plan_data
FROM
(
SELECT DATE'2006-01-01' + NUMTOYMINTERVAL(LEVEL, 'YEAR') AS r_level -- min beg_date minus 1
FROM DUAL
CONNECT BY
LEVEL < (SELECT TO_CHAR(MAX(beg_date), 'YYYY') - TO_CHAR(MIN(beg_date), 'YYYY') + 2 FROM sch_sal_svc)
) d
LEFT JOIN
(
SELECT beg_date
, per_plan_data
FROM sch_sal_svc
WHERE (beg_date) IN
(
SELECT MAX(beg_date)
FROM sch_sal_svc
GROUP BY
TRUNC(beg_date, 'YYYY')
)
) s
ON d.r_level = TRUNC(s.beg_date, 'YYYY')
;
/*
2008-01-01 00:00:00 200
2009-01-01 00:00:00 300
2010-01-01 00:00:00 300
2011-01-01 00:00:00 300
2012-01-01 00:00:00 500
2013-01-01 00:00:00 600
*/

Oracle SQL - max() with NULL values

I have a table that has a series of time based events, each bound with a start and end date. For the most recent (current) event, the end date is NULL. Im trying to collapse the duplicative rows and only show the earliest start date and the latest end date. With the NULL being in the date field, that row is ignored. I can dummy up an end date value with NVL(), but that will cause the front end logic to search for and replace that value.
Is there anyway to get max() function to sort NULL as high?
CREATE TABLE CONG_MEMBER_TERM
(
CONG_MEMBER_TERM_ID NUMBER(10) NOT NULL,
CHAMBER_CD VARCHAR2(30 BYTE) NOT NULL,
CONG_MEMBER_ID NUMBER(10) NOT NULL,
STATE_CD CHAR(2 BYTE) NOT NULL,
DISTRICT NUMBER(10),
START_DT TIMESTAMP(6) WITH TIME ZONE,
END_DT TIMESTAMP(6) WITH TIME ZONE
)
This query works, but drops the row where end date is NULL.
select CONG_MEMBER_ID,
district,
min(start_dt),
max(end_dt)
from CONG_MEMBER_TERM
where CONG_MEMBER_ID = 1716
group by CONG_MEMBER_ID, district;
This query fixes that, but now I have a "dummy" end date value(9/9/9999). Something I would rather not have to code around.
select CONG_MEMBER_ID,
district,
min(start_dt),
max(nvl(end_dt, to_date('9/9/9999', 'mm/dd/yyyy')))
from CONG_MEMBER_TERM
where CONG_MEMBER_ID = 1716
group by CONG_MEMBER_ID, district;
Thanks.
max(end_dt) keep (dense_rank first order by end_dt desc nulls first)
upd:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE t
(val int, s date, e date)
;
INSERT ALL
INTO t (val, s, e)
VALUES (1, sysdate-3, sysdate-2)
INTO t (val, s, e)
VALUES (1, sysdate-2, sysdate-1)
INTO t (val, s, e)
VALUES (1, sysdate-1, null)
INTO t (val, s, e)
VALUES (2, sysdate-1, sysdate-.5)
INTO t (val, s, e)
VALUES (2, sysdate-.5, sysdate-.25)
SELECT * FROM dual
;
Query 1:
select val, min(s), max(e) keep (dense_rank first order by e desc nulls first)
from t group by val
Results:
| VAL | MIN(S) | MAX(E)KEEP(DENSE_RANKFIRSTORDERBYEDESCNULLSFIRST) |
---------------------------------------------------------------------------------------------
| 1 | November, 13 2012 14:15:46+0000 | (null) |
| 2 | November, 15 2012 14:15:46+0000 | November, 16 2012 08:15:46+0000 |
select CONG_MEMBER_ID
, district
, min(start_dt)
, NULLIF(MAX(NVL(end_dt
,TO_DATE('9999-09-09','YYYY-MM-DD')
)
)
,TO_DATE('9999-09-09','YYYY-MM-DD')
)
from CONG_MEMBER_TERM
where CONG_MEMBER_ID = 1716
group by CONG_MEMBER_ID
, district
;