Average date of birth from a single table where the column's date format is '01-JAN-2001'. Looking to return total count of users, and the average DOB YEAR (oracle sql)
mock table:
|User_ID|birth_date|
|123|01-JAN-2001|
|123|01-JAN-2001|
|123|01-JAN-2001|
Assuming your intention is to just average the year, you can parse it to a date, extract the year and average it as a number:
SELECT COUNT(*), AVG(EXTRACT(YEAR FROM TO_DATE(birth_date, 'dd-mon-yyyy'))
FROM users
(Oracle 12c:) Test table and data:
create table dt (
userid_ number generated always as identity ( start with 123 )
, d_ date
);
begin
for i in 1 .. 15
loop
insert into dt ( d_ ) values (
to_date ( trunc( dbms_random.value( 2446067, 2458100 ) ), 'J' )
) ;
end loop;
end;
/
SQL> select userid_, to_char( d_, 'DD-MON-YYYY' ) dob from dt;
USERID_ DOB
---------- --------------------
123 19-JUN-2000
124 06-OCT-2005
125 27-JAN-2012
126 09-JUL-2003
127 23-JUL-2010
128 07-FEB-1992
129 20-DEC-2002
130 19-MAY-2002
131 23-FEB-1990
132 26-DEC-1990
133 19-JUN-1999
134 16-DEC-1994
135 13-APR-2017
136 31-MAR-2000
137 23-MAY-1987
Query and result:
select
count(*) user_count
, trunc( avg( extract( year from d_ ) ) ) avg_dob_year
from dt ;
USER_COUNT AVG_DOB_YEAR
---------- ------------
15 2000
(Not taking "days" into account, just "years").
When using the method suggested by #mathguy
"... It can be calculated by subtracting a fixed date, like
2000/01/01, from all dates, taking the average of the differences, and
adding it to the fixed date."
... this may be a query we could start with (I'm sure it can be refined):
select
count(*) usercount
, to_date( '2000/01/01', 'YYYY/MM/DD' ) fixeddate
, avg( differences ) difftofixed
, to_date('2000/01/01', 'YYYY/MM/DD' ) + avg( differences ) fixedplusdiff
, extract( year from ( to_date('2000/01/01', 'YYYY/MM/DD' ) + avg( differences ) ) ) dob_year
from (
select
d_ - to_date( '2000/01/01', 'YYYY/MM/DD' ) differences
from dt
);
-- result
USERCOUNT FIXEDDATE DIFFTOFIXED FIXEDPLUSDIFF DOB_YEAR
15 01-JAN-00 250.6 07-SEP-00 2000
I don’t know why you’re trying to get the average date but I think it would go something like this :
“SELECT COUNT(user_id), AVG(EXTRACT(YEAR FROM TO_DATE(birth_date, 'dd-MM-yyyy'))
FROM users”
Related
I have an Oracle table filled with rows of data associated to datetimes.
EX:
Value Datetime
----- ----------------
123 12/08/2018 00:00
456 12/08/2018 01:00
789 12/08/2018 02:00
... ...
312 12/08/2018 23:00
321 12/09/2018 00:00
... ...
I need to take this data and group it by day, but somehow transpose the hourly data to columns with one column for each hour in a 24 hour period.
EX:
Date Value(00:00) Value(01:00) Value(02:00) ... Value(23:00)
---- ------------ ------------ ------------ --- ------------
12/08/2018 123 456 789 ... 312
12/09/2018 321 654 987 ... 423
... ... ... ... ... ...
What is the most effective way of querying to obtain the data from the table in this format? I apologize if this question has already been answered. I've searched a good bit online, but I'm honestly somewhat at odds of even knowing the right keywords to search for this answer. Thank you in advance.
I've tried to simplify the answer the best I could for stack overflow. I was able to get the information formatted using a query similar to the one below.
SELECT date,
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 0 AND EXTRACT(HOUR FROM datetime) < 1 THEN value ELSE 0 END) as "value(00:00)",
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 1 AND EXTRACT(HOUR FROM datetime) < 2 THEN value ELSE 0 END) as "value(01:00)",
SUM(CASE WHEN EXTRACT(HOUR FROM datetime) >= 2 AND EXTRACT(HOUR FROM datetime) < 3 THEN value ELSE 0 END) as "value(02:00)",
...
FROM (
SELECT TO_CHAR(datetime,'YYYY-MM-DD') AS date,
value,
datetime
FROM table
)
GROUP BY date
The process of taking rows and turning them into columns is called pivoting. As of 11g (?), Oracle has the PIVOT keyword to do this natively. Note there is no "time" datatype in Oracle, so I converted them to two-digit characters before pivoting. There must be an aggregate function in the pivot, so make sure you do not have duplicate rows with the same date.
select * from (
select "Value", to_char( "Datetime", 'HH24' ) as dt from table1
)
pivot ( max( "Value" ) for ( dt ) in ( '00' AS "hour0",
'01' as "hour1", '02' as "hour2" ));
Edit:
To aggregate by the day, ignoring the time component, add a TRUNCed column to the inline view.
select * from (
select "Value", trunc("Datetime") as the_day,
to_char( "Datetime", 'HH24' ) as dt from table1
)
pivot ( max( "Value" ) for ( dt ) in ( '00', '01', '02' ));
SQL Fiddle 2
I'm trying to create a Trial Balance Report ,
and i have a field named : 'PERIOD_NAME' which stores the accounting period name,
and my question is : is it possible to convert the 'PERIOD_NAME' which is a "VARCHAR2" into "DATE"
in order to sort the months ASC , To give me this Result
jan- 16
jan- 17
Feb- 16
Feb- 17
Use TO_DATE( string, format_model, nls_settings ) assuming your PERIOD_NAME is like jan-17 (but it is unclear from your question what the exact format is):
SELECT TO_DATE( period_name, 'MON-YY', 'NLS_DATE_LANGUAGE=English' )
FROM your_table;
If you want to sort by month then year then you can use EXTRACT to get the month or year and sort on that:
SELECT *
FROM (
SELECT TO_DATE( period_name, 'MON-YY', 'NLS_DATE_LANGUAGE=English' ) AS period_date
FROM your_table
)
ORDER BY EXTRACT( MONTH FROM period_date ),
EXTRACT( YEAR FROM period_date );
I think this is what you are after? Basically, I have used to_date to convert the period name to a date and then extracted the month and year separately to use in the sort. Hope this helps!
Data
CREATE TABLE my_table
(
col1 VARCHAR2(10)
) ;
INSERT INTO my_table VALUES ('jan- 16');
INSERT INTO my_table VALUES ('jan- 17');
INSERT INTO my_table VALUES ('Feb- 16');
INSERT INTO my_table VALUES ('Feb- 17');
Solution
SELECT t.*
FROM my_table t
ORDER BY to_char(to_date(col1, 'mon- yy'), 'mm'),
to_char(to_date(col1, 'mon- yy'), 'yy')
Result
COL1
jan- 16
jan- 17
Feb- 16
Feb- 17
I am trying to write SQL code (using SQL Developer) that checks if a person had a birthday within the past 6 month insurance term.
This is what my code currently looks like.
SELECT DRIVER_KEY, CASE WHEN BDAY BETWEEN EFFDAY AND EXPDAY THEN 1 ELSE 0 END AS BDAYIND FROM (
SELECT DISTINCT A.DRIVER_KEY
, TO_CHAR(A.BIRTH_DATE,'mm/dd') AS BDAY
, TO_CHAR(SUBSTR(A.EFFECTIVE_DATE_KEY,5,2)||'/'||SUBSTR(A.EFFECTIVE_DATE_KEY,7,2) ) AS EFFDAY
, TO_CHAR(SUBSTR(A.EXPIRATION_DATE_KEY,5,2)||'/'||SUBSTR(A.EXPIRATION_DATE_KEY,7,2) ) AS EXPDAY
FROM DRIVER_TABLE A
);
It works - so long as the term doesn't cross the break in year. However, my code currently says that 01/25 is NOT between 09/19 and 03/19... How do I fix this?
EDIT: As APC pointed out, my solution does not work for leap years. I would normally delete this post, but it was already selected as the answer to the question. I updated my code below to use the year logic from Brian Leach's solution instead of the to_date strings. Please upvote Brian or APC's answers instead.
Here is my create statement with arbitrary dates:
create table DRIVER_TABLE
(
BIRTH_DATE date,
EFFECTIVE_DATE_KEY date,
EXPIRATION_DATE_KEY date
);
insert into DRIVER_TABLE
values(to_date('05/01/1980','MM/DD/YYYY'),
to_date('11/01/2016','MM/DD/YYYY'),
to_date('04/01/2017','MM/DD/YYYY'));
Here is the query:
select case when BirthdayEFFYear between EFFECTIVE_DATE_KEY and EXPIRATION_DATE_KEY
or BirthdayEXPYear between EFFECTIVE_DATE_KEY and EXPIRATION_DATE_KEY
or to_number(EXPIRATION_DATE_KEY - EFFECTIVE_DATE_KEY) / 365 > 1
then 1 else 0 end BDAYIND
from(
select add_months(BIRTH_DATE,12 * (extract(year from EFFECTIVE_DATE_KEY) - extract(year from BIRTH_DATE))) BirthdayEFFYear,
add_months(BIRTH_DATE,12 * (extract(year from EXPIRATION_DATE_KEY) - extract(year from BIRTH_DATE))) BirthdayEXPYear,
EFFECTIVE_DATE_KEY,EXPIRATION_DATE_KEY
from DRIVER_TABLE A
)
SQLFiddle
Compare dates as dates, not as strings.
Apparently EFFECTIVE_DATE_KEY contains the year in the first four characters, and as such the following should give you what you're looking for:
SELECT DRIVER_KEY,
CASE
WHEN BDAY BETWEEN EFFDAY AND EXPDAY THEN 1
ELSE 0
END AS BDAYIND
FROM (SELECT DISTINCT A.DRIVER_KEY,
A.BIRTH_DATE AS BDAY,
TO_DATE(A.EFFECTIVE_DATE_KEY, 'YYYYMMDD') AS EFFDAY,
TO_DATE(A.EXPIRATION_DATE_KEY, 'YYYYMMDD') AS EXPDAY
FROM DRIVER_TABLE A);
Best of luck.
'01/25' is not between '09/19' and '03/19' because between() is never true when the second argument is smaller than the first argument. You fall ito this trap because you're working with strings. It is always easier to work with dates using the DATE datatype.
It looks like your columns effective_date and expiry_date may not be stored as dates but rather a string; unfortunately this is a common data modelling mistake. If so, you need to cast them to DATE first before applying the following.
This solution has a subquery which selects the pertinent columns from driver_table and also calculates each driver's current age in years. The age is used to derive the last birthday, which is then compared in the main query to the bounds of the insurance term. Because we derive an actual date we can use Oracle's standard date arithmetic so the bdayind is calculated correctly.
SQL> with cte as (
2 select driver_key
3 , date_of_birth
4 , trunc(months_between(sysdate, date_of_birth)/12) as age
5 , add_months(date_of_birth, 12 * (trunc(months_between(sysdate, date_of_birth)/12))) as last_birthday
6 , effective_date
7 , expiry_date
8 from driver_table
9 )
10 select driver_key
11 , date_of_birth as dob
12 , age
13 , effective_date as eff_date
14 , expiry_date as exp_date
15 , last_birthday as last_bday
16 , case
17 when last_birthday between effective_date and expiry_date
18 then 1
19 else 0 end as bdayind
20 from cte
21 /
DRIVER_KEY DOB AGE EFF_DATE EXP_DATE LAST_BDAY BDAYIND
---------- --------- ---- --------- --------- --------- ----------
12 02-APR-98 19 01-DEC-16 31-MAY-17 02-APR-17 1
22 02-APR-98 19 01-JAN-17 30-JUN-17 02-APR-17 1
32 02-SEP-98 18 01-DEC-16 31-MAY-17 02-SEP-16 0
42 02-SEP-98 18 01-JAN-17 30-JUN-17 02-SEP-16 0
SQL>
The subquery produces both age and last_birthday just for demonstration purposes. In real life you only need the last_birthday column.
This solution differs slightly from the others in that:
It works for any birthday between any effective and expiration dates
It accounts for leap years
The raw_data is just setting up the dates for the example:
WITH
raw_data
AS
(SELECT DATE '1963-08-03' AS birthday
, DATE '2017-04-01' AS effectiveday
, DATE '2017-10-31' AS expirationday
, 'Billy' AS name
FROM DUAL
UNION ALL
SELECT DATE '1995-03-20' AS birthday
, DATE '2017-04-01' AS effectiveday
, DATE '2017-10-31' AS expirationday
, 'Sue' AS name
FROM DUAL
UNION ALL
SELECT DATE '1997-01-15' AS birthday
, DATE '2016-12-01' AS effectiveday
, DATE '2017-05-31' AS expirationday
, 'Olga' AS name
FROM DUAL),
mod_data
AS
(SELECT raw_data.*
, ADD_MONTHS (
birthday
, (extract(year from effectiveday) - extract (year from birthday)) * 12
)
effectiveanniversary
, ADD_MONTHS (
birthday
, (extract(year from expirationday) - extract (year from birthday)) * 12
)
expirationanniversary
FROM raw_data)
SELECT name, mod_data.birthday, effectiveday, expirationday
, CASE
WHEN effectiveanniversary BETWEEN effectiveday AND expirationday
OR expirationanniversary BETWEEN effectiveday AND expirationday
THEN
1
ELSE
0
END
found_between
FROM mod_data
NAME BIRTHDAY EFFECTIVEDAY EXPIRATIONDAY FOUND_BETWEEN
Billy 1963/08/03 2017/04/01 2017/10/31 1
Sue 1995/03/20 2017/04/01 2017/10/31 0
Olga 1997/01/15 2016/12/01 2017/05/31 1
I'm trying to target a date from the previous month that matches today. However, the target date needs to be based on the same number of business days left in the month. For example, if today is 6-JUN-14 then I need a query that gives me 7-MAY-14 since both dates have 16 business days left in their respective month. Also, I'm using SQL Developer.
There are a couple of ways to tell whether a day is weekday or weekend, but they are governed by your NLS settings. I'm in the UK, so my setting for NLS_TERRITORY determines that Saturday and Sunday are day number 6 and 7. However, other territories (such as USA) have Sunday as day number 1. Find out more.
Anyway, this query generates the weekdays between now and the end of June:
select weekday
from ( select weekday
, to_char(weekday,'D') as dayn
from ( select (sysdate + level) as weekday
from dual
connect by level <= (last_day(sysdate) - sysdate)
)
)
where dayn not in ('6','7')
/
... which gives this result set ...
8* where dayn not in ('6','7')
WEEKDAY
---------
09-JUN-14
10-JUN-14
...
26-JUN-14
27-JUN-14
30-JUN-14
16 rows selected.
SQL>
So we can wrap that logic in a sub-query to get the number of days, and use some LAST_DAY(), ADD_MONTHS() and TRUNC() magic to generate a similar list for the previous month.
with curr as ( select count(*) as nod
From
(
select weekday
from ( select weekday, to_char(weekday,'D') as dayn
from ( select sysdate + level weekday
from dual
connect by level <= (last_day(sysdate) - sysdate)
)
)
where dayn not in ('6','7')
)
)
, bds as ( select trunc(add_months(sysdate, -1), 'MM') fd
, last_day(add_months(sysdate, -1)) ld
from dual )
, prev as ( select weekday
, row_number() over (order by weekday desc) rn
from (
select fd + (level-1) weekday
from bds
connect by level <= ld-fd
)
where to_char(weekday,'D') not in ('6','7')
)
select prev.weekday
, rn
from prev
where prev.rn <= ( select nod+1 from curr )
order by prev.weekday
/
... generating the following range of dates in May ...
30* order by prev.weekday
WEEKDAY RN
--------- ----------
08-MAY-14 17
09-MAY-14 16
12-MAY-14 15
...
28-MAY-14 3
29-MAY-14 2
30-MAY-14 1
17 rows selected.
SQL>
Now, this range doesn't match the range you propose. That's because of Whitsun. In the UK 26-MAY-2014 was a bank holiday. However, there is nothing the Oracle date features can do to handle such things. So, if you want to include public holidays in the calculation you will need to create a reference table for them. We could include that table in the sub-query like this:
, prev as ( select weekday
, row_number() over (order by weekday desc) rn
from (
select fd + (level-1) weekday
from bds
connect by level <= ld-fd
)
where to_char(weekday,'D') not in ('6','7')
and weekday not in ( select holiday from public_holidays )
)
... which gives us this result set ...
WEEKDAY RN
--------- ----------
07-MAY-14 17
08-MAY-14 16
...
28-MAY-14 3
29-MAY-14 2
30-MAY-14 1
17 rows selected.
SQL>
I want to select with an oracle sql statement the records with a 6 month time interval.
Example
01/06/2011 AMOUNT
01/12/2011 AMOUNT
01/06/2012 AMOUNT
01/12/2012 AMOUNT
And so on
How can I do this with oracle sql?
select ADD_MONTHS(trunc(sysdate), (rownum - 1) * 6) some_date
from dual
connect by level <= 5;
SOME_DATE
-----------
18.04.2014
18.10.2014
18.04.2015
18.10.2015
18.04.2016
WITH got_r_num AS
(
SELECT t.* -- OR WHATEVER YOU WANT
, DENSE_RANK () OVER ( PARTITION BY TRUNC (created_date, 'MONTH')
ORDER BY TRUNC (created_date) -- DESC
) AS r_num
FROM test_table
WHERE MOD ( MONTHS_BETWEEN ( TRUNC (SYSDATE)
, TRUNC (created_date)
)
, 6
) = 0
)
SELECT * -- or list all columns except r_num
FROM got_r_num
WHERE r_num = 1
;
Have a look here please .
If you need to sum of all records of 6 months in the AMOUNT field:
You can subquery with sum function and query with CONNECT BY LEVEL
SELECT x AS l_date,
(
SELECT sum(your_data)
FROM your_table
WHERE table_date >= x
AND table_date < add_months(x,6)
)AMOUNT
FROM(
SELECT add_months(to_date('01/06/2011','dd/mm/yyyy'),(LEVEL-1)*6) x
FROM dual
CONNECT BY LEVEL <= 4
);