Determine number of seconds in one year - sql

What SQL expression would be able to calculate the number of seconds in any particular year?

For non leap years:
SELECT 365 * 24 * 60 * 60 AS secInYear
FROM dual
And in general:
SqlFiddleDemo
SELECT
24 * 60 * 60 * (
CASE
WHEN MOD(EXTRACT(YEAR FROM sysdate), 400) = 0
OR ( MOD(EXTRACT(YEAR FROM sysdate), 4) = 0 AND MOD(EXTRACT(YEAR FROM sysdate), 100) != 0) THEN 366
ELSE 365
END ) AS secInYear
FROM dual

SELECT
(ADD_MONTHS(TRUNC(SYSDATE, 'YYYY'), 12) - TRUNC(SYSDATE, 'YYYY')) * 86400 SECONDS
FROM
DUAL;

SELECT DAYOFYEAR('2015-12-31') * 24 * 60 * 60 FROM DUAL
DAYOFYEAR will return 365 for New Years Eve for regular years, and 366 for leap years.

Related

calculate column names automatically sql

Is there a way to calculate column names automatically in SQL like below. I need top calculate the Calendar weeks based on from and to date and distribute evenly
Material
From
To
Sales
M01
03.10.2022
31.10.2022
1000
M02
14.11.2022
28.11.2022
1000
Expected output
CW =calendar week
Material
Cw40
CW41
Cw42
CW43
CW44
CW45
CW46
CW47
M01
250
250
250
250
M02
500
500
Is there a way to calculate column names automatically in SQL like below.
No, in SQL (not just Oracle SQL) you needs a fixed, known number of column names so it is impossible to dynamically generate columns with a static SQL query.
If you want to generate the data then either:
Generate the data as rows (rather than columns) and pivot the result in whatever third-party application you are using to access the database. You can generate the output using a correlated row-generator:
SELECT t.material,
w.iso_year,
w.iso_week,
w.weekly_sales
FROM table_name t
CROSS APPLY (
SELECT TO_NUMBER(
TO_CHAR(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
'IYYY'
)
) AS iso_year,
TO_NUMBER(
TO_CHAR(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
'IW'
)
) AS iso_week,
( LEAST(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * LEVEL,
to_dt
)
- GREATEST(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
from_dt
)
) / (to_dt - from_dt) * sales AS weekly_sales
FROM DUAL
CONNECT BY TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL-1) < to_dt
) w
or:
WITH data (from_dt, dt, to_dt, material, sales) AS (
SELECT from_dt, from_dt, to_dt, material, sales
FROM table_name
UNION ALL
SELECT from_dt,
TRUNC(dt + INTERVAL '7' DAY, 'IW'),
to_dt,
material,
sales
FROM data
WHERE TRUNC(dt + INTERVAL '7' DAY, 'IW') < to_dt
)
SELECT material,
TO_NUMBER(TO_CHAR(dt, 'IYYY')) AS iso_year,
TO_NUMBER(TO_CHAR(dt, 'IW')) AS iso_week,
( LEAST(dt + INTERVAL '7' DAY, to_dt) - dt)
/ (to_dt - from_dt) * sales AS weekly_sales
FROM data
Which, for the sample data:
CREATE TABLE table_name (Material, From_dt, To_dt, Sales) AS
SELECT 'M01', DATE '2022-10-03', DATE '2022-10-31', 1000 FROM DUAL UNION ALL
SELECT 'M02', DATE '2022-11-14', DATE '2022-11-28', 1000 FROM DUAL;
Both output:
MATERIAL
ISO_YEAR
ISO_WEEK
WEEKLY_SALES
M01
2022
40
250
M01
2022
41
250
M01
2022
42
250
M01
2022
43
250
M02
2022
46
500
M02
2022
47
500
Or, if you did want to output the values as columns then you need to specify the columns (which would be 53 columns for all 53 potential ISO weeks) and can do that using:
SELECT *
FROM (
SELECT t.material,
w.iso_year,
w.iso_week,
w.weekly_sales
FROM table_name t
CROSS APPLY (
SELECT TO_NUMBER(
TO_CHAR(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
'IYYY'
)
) AS iso_year,
TO_NUMBER(
TO_CHAR(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
'IW'
)
) AS iso_week,
( LEAST(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * LEVEL,
to_dt
)
- GREATEST(
TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL - 1),
from_dt
)
) / (to_dt - from_dt) * sales AS weekly_sales
FROM DUAL
CONNECT BY TRUNC(from_dt, 'IW') + INTERVAL '7' DAY * (LEVEL-1) < to_dt
) w
)
PIVOT (
SUM(weekly_sales)
FOR iso_week IN (
1 AS cw01,
2 AS cw02,
3 AS cw03,
-- ...
40 AS cw40,
41 AS cw41,
42 AS cw42,
43 AS cw43,
44 AS cw44,
45 AS cw45,
46 AS cw46,
47 AS cw47,
48 AS cw48,
49 AS cw49,
50 AS cw50,
51 AS cw51,
52 AS cw52,
53 AS cw53
)
)
or:
WITH data (from_dt, dt, to_dt, material, sales) AS (
SELECT from_dt, from_dt, to_dt, material, sales
FROM table_name
UNION ALL
SELECT from_dt,
TRUNC(dt + INTERVAL '7' DAY, 'IW'),
to_dt,
material,
sales
FROM data
WHERE TRUNC(dt + INTERVAL '7' DAY, 'IW') < to_dt
)
SELECT *
FROM (
SELECT material,
TO_NUMBER(TO_CHAR(dt, 'IYYY')) AS iso_year,
TO_NUMBER(TO_CHAR(dt, 'IW')) AS iso_week,
( LEAST(dt + INTERVAL '7' DAY, to_dt) - dt)
/ (to_dt - from_dt) * sales AS weekly_sales
FROM data
)
PIVOT (
SUM(weekly_sales)
FOR iso_week IN (
1 AS cw01,
2 AS cw02,
3 AS cw03,
-- ...
40 AS cw40,
41 AS cw41,
42 AS cw42,
43 AS cw43,
44 AS cw44,
45 AS cw45,
46 AS cw46,
47 AS cw47,
48 AS cw48,
49 AS cw49,
50 AS cw50,
51 AS cw51,
52 AS cw52,
53 AS cw53
)
)
Which both output:
MATERIAL
ISO_YEAR
CW01
CW02
CW03
CW40
CW41
CW42
CW43
CW44
CW45
CW46
CW47
CW48
CW49
CW50
CW51
CW52
CW53
M01
2022
null
null
null
250
250
250
250
null
null
null
null
null
null
null
null
null
null
M02
2022
null
null
null
null
null
null
null
null
null
500
500
null
null
null
null
null
null
fiddle

Convert Terdata SQL to GCP BG

I need to convert Teradata SQL to GCP BQ SQL
DATE_ADD(DATE_ADD(CAST(CURRENT_DATE/100*100+1 AS DATE),INTERVAL 1 MONTH),INTERVAL -4 MONTH)
AND DATE_ADD(CAST(CURRENT_DATE/100*100+1 AS DATE),INTERVAL 1 MONTH) - 1)
I am getting following error message
No matching signature for operator / for argument types: DATE, INT64. Supported signatures: FLOAT64 / FLOAT64; NUMERIC / NUMERIC; BIGNUMERIC / BIGNUMERIC;
Here is the solution
select
date_add(date_add(date_add(date_add(date_add(DATE '1900-01-01', interval div(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 10000) YEAR), interval div(mod(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 10000), 100) - 1 MONTH), interval mod(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 100) - 1 DAY), interval 1 MONTH), interval -4 MONTH) add_4_month,
---
date_sub(date_add(date_add(date_add(date_add(DATE '1900-01-01', interval div(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 10000) YEAR), interval div(mod(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 10000), 100) - 1 MONTH), interval mod(div((extract(YEAR from current_date()) - 1900) * 10000 + extract(MONTH from current_date()) * 100 + extract(DAY from current_date()), 100) * 100 + 1, 100) - 1 DAY), interval 1 MONTH), interval 1 DAY) sub_1_month

Return time between two dates except weekends

I need to return the time between two dates in Oracle except the time during the weekends, I could return the minute. But when I set a weekend date, I receive a null result instead of the remaining time in workweek.
First, we need to create a function:
CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
RETURN NUMBER
IS
v_return NUMBER;
BEGIN
select sum(greatest(end_dt - start_dt,0)) * 24 * 60 work_minutes
into v_return
from dual
where trunc(start_dt) - trunc(start_dt,'iw') < 5; -- exclude weekends
RETURN v_return;
END;
Case 1 - Return the minutes in the workweek - Ok
Starting and ending in the workweek.
SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('14-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'),
TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
"SYS"."DUAL";
Case 2 - Return the remaining minutes in the workweek - Fail
Starting at the weekend and ending in workweek.
SELECT
"GET_BUS_MINUTES_BETWEEN"(TO_DATE('13-09-2020 06:00:00', 'dd-mm-yyyy hh24:mi:ss'),
TO_DATE('14-09-2020 10:00:00', 'dd-mm-yyyy hh24:mi:ss')) "WORK_MINUTES"
FROM
"SYS"."DUAL";
13-09-2020 is Sunday, therefore I was expected the return as 600 minutes related the Monday.
In these possibilities, we can start at the workweek and end at weekend.
You don't need to use SQL or a row generator and can do it with a simple calculation using only PL/SQL. Adapted from my answers here and here.
CREATE OR REPLACE FUNCTION get_bus_minutes_between (start_dt DATE, end_dt DATE)
RETURN NUMBER
IS
p_start_date DATE;
p_end_date DATE;
p_working_days NUMBER;
BEGIN
IF start_dt IS NULL OR end_dt IS NULL THEN
RETURN NUll;
END IF;
-- Enforce that the values are earliest start date to latest end date.
p_start_date := LEAST( start_dt, end_dt );
p_end_date := GREATEST( start_dt, end_dt );
-- Calculate the number of days from the beginning of the ISO week containing
-- the start date and the beginning of the ISO week containing the end date
-- and then multiply this by 5/7 to get the number of full business days.
--
-- Then add on the extra days from the beginining of the ISO week containing
-- the end date and the end date and subtract the extra days from the
-- beginning of the ISO week containing the start date to the start date.
p_working_days := ( TRUNC( p_end_date, 'IW' ) - TRUNC( p_start_date, 'IW' ) ) * 5 / 7
+ LEAST( p_end_date - TRUNC( p_end_date, 'IW' ), 5 )
- LEAST( p_start_date - TRUNC( p_start_date, 'IW' ), 5 );
-- If the start date and end date are reversed then return a negative value.
IF start_dt > end_dt THEN
RETURN -ROUND( p_working_days * 24 * 60, 3 );
ELSE
RETURN +ROUND( p_working_days * 24 * 60, 3 );
END IF;
END;
/
Then:
SELECT GET_BUS_MINUTES_BETWEEN(
DATE '2020-09-14' + INTERVAL '6' HOUR,
DATE '2020-09-14' + INTERVAL '10' HOUR
) AS minutes_between
FROM DUAL;
Outputs:
| MINUTES_BETWEEN |
| --------------: |
| 240 |
and:
SELECT GET_BUS_MINUTES_BETWEEN(
DATE '2020-09-13' + INTERVAL '6' HOUR,
DATE '2020-09-14' + INTERVAL '10' HOUR
) AS minutes_between
FROM DUAL;
outputs:
| MINUTES_BETWEEN |
| --------------: |
| 600 |
db<>fiddle here
If the intervals are not too big, one method uses a brute force approach to generate all minutes in the range, then exclude week-ends:
with cte(dt, end_dt) as (
select start_dt, end_dt from dual
union all
select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
)
select count(*) work_minutes
from cte
where trunc(dt) - trunc(dt,'iw') < 5
If the intervals are not too big, one method uses a brute force approach to generate all minutes in the range, then exclude week-ends:
with cte(dt, end_dt) as (
select start_dt, end_dt from dual
union all
select dt + 1 / 24 / 60, end_dt from cte where dt < end_dt
)
select count(*) work_minutes
from cte
where to_char(dt, 'IW') <= 5
If you have large intervals, we can reduce the number of iterations by pre-generating minutes / hours series:
with
params (start_dt, end_dt) as (
select start_dt, end_dt from dual
)
minutes (mi) as (
select 0 from dual
union all select mi + 1 from minutes where mi < 59
),
hours (hr) as (
select 0 from dual
union all select hr + 1 from hours where hr < 23
)
select count(*) work_minutes
from params p
cross join minutes m
cross join hours h
where
p.start_dt + h.hr / 24 + m.mi / 24 / 60 <= end_dt
and trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60) - trunc(p.start_dt + h.hr / 24 + m.mi / 24 / 60,'iw') < 5

SQL Query calculating two additional columns

I have a table which gets populated daily with database size. I need to modify the query where I can calculate daily growth and weekly growth.
select * from sys.dbsize
where SNAP_TIME > sysdate -3
order by SNAP_TIME
Current Output
I would like to add two additional columns which would be
Daily Growth (DB_SIZE sysdate - DB_SIZE (sysdate -1))
Weekly Growth (DB_SIZE sysdate - DB_SIZE (sysdate -7))
Need some help constructing the SQL for those two additional columns. Any help will be greatly appreciated.
Thanks,
One option is to use LAG analytic function to calculate daily growth and correlated subquery (within the SELECT statement) for weekly growth.
For example:
SQL> with dbsize (snap_time, db_size) as
2 (select sysdate - 8, 100 from dual union all
3 select sysdate - 7, 110 from dual union all
4 select sysdate - 6, 105 from dual union all
5 select sysdate - 5, 120 from dual union all
6 select sysdate - 4, 130 from dual union all
7 select sysdate - 3, 130 from dual union all
8 select sysdate - 2, 142 from dual union all
9 select sysdate - 1, 144 from dual union all
10 select sysdate - 0, 150 from dual
11 )
12 select
13 a.snap_time,
14 a.db_size,
15 a.db_size - lag(a.db_size) over (order by a.snap_time) daily_growth,
16 --
17 db_size - (select db_size from dbsize b
18 where trunc(b.snap_time) = trunc(a.snap_time) - 7
19 ) weekly_growth
20 from dbsize a
21 order by a.snap_time;
SNAP_TIME DB_SIZE DAILY_GROWTH WEEKLY_GROWTH
------------------- ---------- ------------ -------------
24.08.2020 21:52:20 100
25.08.2020 21:52:20 110 10
26.08.2020 21:52:20 105 -5
27.08.2020 21:52:20 120 15
28.08.2020 21:52:20 130 10
29.08.2020 21:52:20 130 0
30.08.2020 21:52:20 142 12
31.08.2020 21:52:20 144 2 44
01.09.2020 21:52:20 150 6 40
9 rows selected.
SQL>
I would recommend lag() for both columns:
select s.*,
(dbsize - dbsize_1) as daily_growth,
(dbsize - dbsize_7) as weekly_growth
from (select s.*,
lag(dbsize) over (order by snap_time) as dbsize_1,
lag(dbsize, 7) over (order by snap_time) as dbsize_7
from sys.dbsize
) s
where SNAP_TIME > sysdate -3
order by SNAP_TIME;
If you don't have a snapshot each day, you can handle this with a window frame:
select s.*,
(dbsize - dbsize_1) as daily_growth,
(dbsize - dbsize_7) as weekly_growth
from (select s.*,
max(dbsize) over (order by trunc(snap_time) range between interval '1' day preceding and interval '1' second preceding) as dbsize_1,
lag(dbsize, 7) over (order by trunc(snap_time) range between '7' day preceding and interval '6 1' day to hour) as dbsize_7
from sys.dbsize
) s
where SNAP_TIME > sysdate - 3
order by SNAP_TIME;
If there is always is one record per day, you can use lag():
select
snap_time
db_size,
db_size - lag(db_size, 1) over(order by snap_time) daily_growth,
db_size - lag(db_size, 7) over(order by snap_time) weekly_growth
from sys.db.size
order by snap_time
This actually looks 1 row back and 7 rows back. If there are missing dates, or multiple records per day, then you could average the snap size by day, and use a window range in the window function:
select
trunc(snap_time) snap_day,
avg(db_size) avg_db_size,
avg(db_size) - avg(db_size) over(
order by trunc(snap_time)
range between interval '1' day preceding and interval '1' day preceding
) daily_growth,
avg(db_size) - avg(db_size) over(
order by trunc(snap_time)
range between interval '7' day preceding and interval '7' day preceding
) weekly_growth
from sys.db.size
group by trunc(snap_time)
order by trunc(snap_time)
If you want the results for the last 3 days only, you can turn any of the two above queries to subqueries, and filter in the outer query:
select *
from ( ... ) t
where snap_time > sysdate - 3 -- or: snap_day > trunc(sysdate) - 3

Oracle average difference between two date fields

I have a table in database like this:
unit arrival_date departure_date
---- ------------- --------------
1 27/1/2017 08:01:20 a. m. 27/1/2017 08:04:27 a. m.
1 27/1/2017 08:05:35 a. m. 27/1/2017 08:09:28 a. m.
I need to calculate the average time difference between arrival_date and departure_date of users and show the result as hour,minute,second format(HH:MI:SS).
If I made this arrival_date - departure_date, I get the result in days, but I am struggling to get the average in hour, minutes and seconds.
The fields are DATE fields not TIMESTAMP fields.
Here's an example.
When subtracting two date datatype values, the result is number of days. It shows the INTER CTE. When you multiply it by 24 (number of hours in a day), 60 (number of minutes in an hour) and 60 (number of seconds in a minute), the result is number of seconds (DIFF_SECS).
AVERAGES CTE shows how to apply AVG function to previous results; nothing special in that, just pay attention that you have to GROUP it BY the UNIT column.
Finally, apply TO_CHAR formatting to calculation (some TRUNC and MOD calls in order to extract hours, minutes and seconds from the AVG_DIFF_SECS value).
I suggest you run each CTE separately, step by step, to easier follow the execution.
SQL> with test (unit, arr, dep) as
2 (select 1, to_date('27.01.2017 08:01:20', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.01.2017 08:04:27', 'dd.mm.yyyy hh24:Mi:ss')
4 from dual union all
5 select 1, to_date('27.01.2017 08:05:35', 'dd.mm.yyyy hh24:mi:ss'),
6 to_date('27.01.2017 08:09:28', 'dd.mm.yyyy hh24:Mi:ss')
7 from dual
8 ),
9 inter as
10 (select unit, (dep - arr) diff_days,
11 (dep - arr) * 24 * 60 * 60 diff_secs
12 from test
13 ),
14 averages as
15 (select unit,
16 avg(dep - arr) avg_diff_days,
17 avg((dep - arr) * 24 * 60 * 60) avg_diff_secs
18 from test
19 group by unit
20 )
21 select
22 to_char(trunc(avg_diff_secs / 3600), 'fm00') || ':' || -- hours
23 to_char(trunc(mod(avg_diff_secs , 3600) / 60), 'fm00') || ':' || -- minutes
24 to_char(mod(avg_diff_secs, 60), 'fm00') -- seconds
25 avg_diff_formatted
26 from averages;
AVG_DIFF_FORMATTED
--------------------
00:03:30
SQL>
select unit, avg(departure_date - arrival_date) as avg_date
from mytable