Insert a range of dates and should be unique with another column - sql

I have table
CREATE TABLE T_TEST
( KURS_NUMBER NUMBER PRIMARY KEY,
KURS_ID NUMBER NOT NULL,
DATEKURS DATE NOT NULL,
CONSTRAINT UNIQUE2 UNIQUE
(KURS_ID,DATEKURS)
);
TRIGGER for kurs_number
create or replace trigger TR_INSERT_TEST01
before insert on test01
FOR EACH ROW
declare
-- local variables here
begin
IF :NEW.KURS_NUMBER IS NULL
THEN SELECT SEQ_TEST.NEXTVAL INTO :NEW.KURS_NUMBER FROM DUAL;
END IF;
end TR_INSERT_T_TEST;
How can I insert data to kurs_id which will contain only one digit '1'
and datekurs will contain date in order period from 2017year 1 january to 31 december 2017year ( or random date )
connect by level 365
This code works very well but if I want to use my trigger for new column kurs_number it doesn't work due (not enough values). I guess that should be in different way.
insert into t_test
select 1
, date '2017-01-01' + (level-1)
from dual
connect by level <= 365
/

This trick generates 365 rows. We can do arithmetic with dates so adding (level-1) to a root date generates 365 dates.
insert into t_test
select SEQ_TEST.NEXTVAL
, 1
, date '2017-01-01' + (level-1)
from dual
connect by level <= 365
/
"not enough values"
You changed the structure of the target table so you needed to change the projection of the query to match. The revised version includes the additional primary key column.
"i want to use my trigger for new column kurs_number"
You can make this a procedure with parameters for kurs_id and the target year.
As a bonus this code handles leap years correctly.
create or replace procedure generate_kurs_year_recs
( p_kurs_id in number
, p_kurs_year in varchar2 )
is
last_dayno number;
begin
/* find number of days in year */
select to_number(to_char(to_date( p_kurs_year||'-12-31', 'yyyy-mm-dd')
, 'DDD'))
into last_dayno
from dual;
/* generate records for year */
insert into t_test
select SEQ_TEST.NEXTVAL
, p_kurs_id
, to_date( p_kurs_year||'-01-01', 'yyyy-mm-dd') + (level-1)
from dual
connect by level <= last_dayno;
end generate_kurs_year_recs;
/
Call the procedure like this:
begin
generate_kurs_year_recs(1,'2017');
end;
To call from a trigger you will need to pass parameters somehow, presumably using values from the trigger's table.

This will insert all dates in order
insert into t_test (kurs_id, datekurs)
with CTE (DD) as
(
select to_date('20170101','YYYYMMDD') as DD
from dual
union
select DD +1
from CTE
where DD < to_date('20171231','YYYYMMDD')
)
select row_number() over(order by DD) as kurs_id, DD as datekurs
from CTE

Related

Insert records for 40years in table only date

I need to insert only date into a table MONTH_YEAR from 01-01-2010 to 01-01-2040:
For example, I need to insert record on my table month wise
DATE:
01-01-2010
01-02-2010
01-03-2010
01-04-2010
01-05-2010
01-06-2010
01-07-2010
01-08-2010
01-09-2010
01-10-2010
01-11-2010
01-12-2010
01-01-2011
01-02-2011
01-03-2011
01-04-2011
01-05-2011
.....................................
01-06-2040
01-07-2040
01-08-2040
01-09-2040
01-10-2040
01-11-2040
01-12-2040
Like this I want to insert only date into my table for month wise from 01-01-2010 to 01-01-2040
You can use a hierarchical query:
INSERT INTO month_year (column_name)
SELECT ADD_MONTHS(DATE '2010-01-01', LEVEL - 1)
FROM DUAL
CONNECT BY LEVEL <= 31*12;
Or a recursive query:
INSERT INTO month_year (column_name)
WITH range (dt) AS (
SELECT DATE '2010-01-01' FROM DUAL
UNION ALL
SELECT ADD_MONTHS(dt, 1)
FROM range
WHERE dt < DATE '2040-12-01'
)
SELECT dt FROM range;
db<>fiddle here
Row generator it is:
SQL> insert into month_year (datum)
2 select date '2010-01-01' + level - 1
3 from dual
4 connect by level <= date '2040-01-01' - date '2010-01-01' + 1;
10958 rows created.
SQL> select min(datum) min_date,
2 max(datum) max_date
3 from month_year;
MIN_DATE MAX_DATE
---------- ----------
01.01.2010 01.01.2040
SQL>
If you only need 1st of every month, then
SQL> insert into month_year (datum)
2 select add_months(date '2010-01-01', level - 1)
3 from dual
4 connect by level <= months_between(date '2040-01-01', date '2010-01-01') + 1;
361 rows created.
SQL>
Do you really need 40 years of dates, it seems unlikely or can you make due with a virtual calendar, where you specify a start and end_date and all the dates are generated for you. This example does every day in the range but feel free to modify it to produce what you need. I use it in several places.
CREATE OR REPLACE TYPE nt_date IS TABLE OF DATE;
CREATE OR REPLACE FUNCTION generate_dates_pipelined(
p_from IN DATE,
p_to IN DATE
)
RETURN nt_date PIPELINED DETERMINISTIC
IS
v_start DATE := TRUNC(LEAST(p_from, p_to));
v_end DATE := TRUNC(GREATEST(p_from, p_to));
BEGIN
LOOP
PIPE ROW (v_start);
EXIT WHEN v_start >= v_end;
v_start := v_start + INTERVAL '1' DAY;
END LOOP;
RETURN;
END generate_dates_pipelined;
/

SQL Server : 5 days moving average for last month

I have a view with two columns TOTAL and DATE, the latter one excludes Saturdays and Sundays, i.e.
TOTAL DATE
0 1-1-2014
33 2-1-2014
11 3-1-2014
55 5-1-2014
...
25 15-1-2014
35 16-1-2014
17 17-1-2014
40 20-1-2014
33 21-1-2014
...
The task that I'm trying to complete is counting 5 days TOTAL average for the whole month, i.e between 13th and 17th, 14th and 20th (we skip weekends), 15th and 21st etc. up to current date.
And YES, they ARE OVERLAPPING RANGES.
Any idea how to achieve it in SQL?
Example of the output (starting from the 6th and using fake numbers)
5daysAVG Start_day
22 1-01-2014 <-counted between 1st to 6th Jan excl 4 and 5 of Jan
25 2-01-2014 <- Counted between 2nd to 7th excluding 4 and 5
27 3-01-2014 <- 3rd to 8th excluding 4/5
24 6-01-2014 <-6th to 10th
...
33 today-5
Okay, I usually set up some test data to play with.
Here is some code to create a [work] table in tempdb. I am skipping weekends. The total is a random number from 0 to 40.
-- Just playing
use tempdb;
go
-- drop existing
if object_id ('work') > 0
drop table work
go
-- create new
create table work
(
my_date date,
my_total int
);
go
-- clear data
truncate table work;
go
-- Monday = 1
SET DATEFIRST 1;
GO
-- insert data
declare #dt date = '20131231';
declare #hr int;
while (#dt < '20140201')
begin
set #hr = floor(rand(checksum(newid())) * 40);
set #dt = dateadd(d, 1, #dt);
if (datepart(dw, #dt) < 6)
insert into work values (#dt, #hr);
end
go
This becomes real easy in SQL SERVER 2012 with the new LEAD() window function.
-- show data
with cte_summary as
(
select
row_number() over (order by my_date) as my_num,
my_date,
my_total,
LEAD(my_total, 0, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 1, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 2, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 3, 0) OVER (ORDER BY my_date) +
LEAD(my_total, 4, 0) OVER (ORDER BY my_date) as my_sum,
(select count(*) from work) as my_cnt
from work
)
select * from cte_summary
where my_num <= my_cnt - 4
Basically, we give a row number to each row, calculate the sum for rows 0 (current) to row 4 (4 away) and a total count.
Since this is a running total for five periods, the remaining dates have missing data. Therefore, we toss them out. my_row <= my_cnt -4
I hope this solves your problem!
If you are only caring about one number for the month, change the select to the following. I left the other rows in for you to get an understanding of what is going on.
select avg(my_sum/5) as my_stat
from cte_summary
where my_num <= my_cnt - 4
FOR SQL SERVER < 2012 & >= 2005
Like anything in this world, there is always a way to do it. I used a small tally table to loop thru the data and collect sets of 5 data points for averages.
-- show data
with
cte_tally as
(
select
row_number() over (order by (select 1)) as n
from
sys.all_columns x
),
cte_data as
(
select
row_number() over (order by my_date) as my_num,
my_date,
my_total
from
work
)
select
(select my_date from cte_data where my_num = n) as the_date,
(
select sum(my_total) / 5
from cte_data
where my_num >= n and my_num < n+5
) as the_average
from cte_tally
where n <= (select count(*)-4 from work)
Here is an explanation of the common table expressions (CTE).
cte_data = order data by date and give row numbers
cte_tally = a set based counting algorithm
For groups of five calculate an average and show the date.
This solution does not depend on holidays or weekends. If data is there, it just partitions by groups of five order by date.
If you need to filter out holidays and weekends, create a holiday table. Add a where clause to cte_data that checks for NOT IN (SELECT DATE FROM HOLIDAY TABLE).
Good luck!
SQL Server offers the datepart(wk, ...) function to get the week of the year. Unfortunately, it uses the first day of the year to define the year.
Instead, you can find sequences of consecutive values and group them together:
select min(date), max(date, avg(total*1.0)
from (select v.*, row_number() over (order by date) as seqnum
from view
) v
group by dateadd(day, -seqnum, date);
The idea is that subtracting a sequence of numbers from a sequence of consecutive days yields a constant.
You can also do this by using a canonical date and dividing by 7:
select min(date), max(date, avg(total*1.0)
from view v
group by datediff(day, '2000-01-03', date) / 7;
The date '2000-01-03' is an arbitrary Monday.
EDIT:
You seem to want a 5-day moving average. Because there is missing data for the weekends, avg() should just work:
select v1.date, avg(v2.value)
from view v1 join
view v2
on v2.date >= v1.date and v2.date < dateadd(day, 7, v1.date)
group by v1.date;
Here's a solution that works in SQL 2008;
The concept here is to use a table variable to normalize the data first; the rest is simple math to count and average the days.
By normalizing the data, I mean, get rid of weekend days, and assign ID's in a temporary table variable that can be used to identify the rows;
Check it out: (SqlFiddle also here)
-- This represents your original source table
Declare #YourSourceTable Table
(
Total Int,
CreatedDate DateTime
)
-- This represents some test data in your table with 2 weekends
Insert Into #YourSourceTable Values (0, '1-1-2014')
Insert Into #YourSourceTable Values (33, '1-2-2014')
Insert Into #YourSourceTable Values (11, '1-3-2014')
Insert Into #YourSourceTable Values (55, '1-4-2014')
Insert Into #YourSourceTable Values (25, '1-5-2014')
Insert Into #YourSourceTable Values (35, '1-6-2014')
Insert Into #YourSourceTable Values (17, '1-7-2014')
Insert Into #YourSourceTable Values (40, '1-8-2014')
Insert Into #YourSourceTable Values (33, '1-9-2014')
Insert Into #YourSourceTable Values (43, '1-10-2014')
Insert Into #YourSourceTable Values (21, '1-11-2014')
Insert Into #YourSourceTable Values (5, '1-12-2014')
Insert Into #YourSourceTable Values (12, '1-13-2014')
Insert Into #YourSourceTable Values (16, '1-14-2014')
-- Just a quick test to see the source data
Select * From #YourSourceTable
/* Now we need to normalize the data;
Let's just remove the weekends and get some consistent ID's to use in a separate table variable
We will use DateName SQL Function to exclude weekend days while also giving
sequential ID's to the remaining data in our temporary table variable,
which are easier to query later
*/
Declare #WorkingTable Table
(
TempID Int Identity,
Total Int,
CreatedDate DateTime
)
-- Let's get the data normalized:
Insert Into
#WorkingTable
Select
Total,
CreatedDate
From #YourSourceTable
Where DateName(Weekday, CreatedDate) != 'Saturday'
And DateName(Weekday, CreatedDate) != 'Sunday'
-- Let's run a 2nd quick sanity check to see our normalized data
Select * From #WorkingTable
/* Now that data is normalized, we can just use the ID's to get each 5 day range and
perform simple average function on the columns; I chose to use a CTE here just to
be able to query it and drop the NULL ranges (where there wasn't 5 days of data)
without having to recalculate each average
*/
; With rangeCte (StartDate, TotalAverage)
As
(
Select
wt.createddate As StartDate,
(
wt.Total +
(Select Total From #WorkingTable Where TempID = wt.TempID + 1) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 2) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 3) +
(Select Total From #WorkingTable Where TempID = wt.TempID + 4)
) / 5
As TotalAverage
From
#WorkingTable wt
)
Select
StartDate,
TotalAverage
From rangeCte
Where TotalAverage
Is Not Null

Insert all dates in a time period in a table

I have a table having two columns (date_ID, entry_Date). I want to insert all the dates within a specific time period into the table (say dates all the dates between 2002-2030). Is there any way to do that using loops in SQL-Server?
Try this
DECLARE #d date='20020101'
WHILE #d<'20300101'
BEGIN
INSERT INTO dbo.Dates (entry_Date)
VALUES (#d)
SET #d=DATEADD(DAY,1,#d)
END
GO
This should do it:
WITH TestItOut AS
(
SELECT CAST('2002-01-01' as datetime) DateColumn
UNION ALL
SELECT DateColumn + 1
FROM TestItOut
WHERE DateColumn + 1 <= '2030-12-31'
)
INSERT INTO YourTable (ColumnName)
SELECT DateColumn
FROM TestItOut
OPTION (MAXRECURSION 0)
in oracle i would do
insert into sometable
select to_date('01/01/2013','dd/mm/yyyy') + level
from dual
connect by level < 10001
this will generate 10000 dates from 1/1/13 with a daily interval. if you want hourly interval for example you can just change + level to + level/24.
this is basic ANSI sql hierarchical query - it should work in SQL server as well.
insert into table values(date_ID,(select entry_Date from table where entry_Date between 01/01/2002 and 01/01/2030))
Try this kind of query.
In place of date_ID put your appropriate value.

Oracle 11g - FOR loop that inserts only weekdays into a table?

I want to insert some data into a table associated with dates for the next year. I actually only need workdays inserted.
BEGIN
FOR i IN 1..365 LOOP
INSERT INTO MY_TABLE (ID, MY_DATE)
VALUES (i, (to_date(sysdate,'DD-MON-YY')-1)+i);
END LOOP;
END;
I can solve my problem by going back and deleting rows that are weekend days, but that seems rather inelegant - can anyone think of a way to modify my loop so that it skips weekends?
You could always check the day of the week before inserting the row (the names of the days of the week will depend on your NLS settings so this isn't the most robust solution possible)
BEGIN
FOR i IN 1..365 LOOP
IF( to_char(sysdate-1+i,'fmDAY') NOT IN ('SATURDAY', 'SUNDAY') )
THEN
INSERT INTO MY_TABLE (ID, MY_DATE)
VALUES (i, (to_date(sysdate,'DD-MON-YY')-1)+i);
END IF;
END LOOP;
END;
I would suggest using to_date(your_date,'d') as #Jeff Moore mentions. However, I'd also suggest getting rid of the for..loop. As a bonus, this will add all days of any given year, unlike your version, which will generate an extra day on leap years:
INSERT INTO MY_TABLE (ID, MY_DATE)
SELECT lvl, dt
FROM ( SELECT LEVEL lvl,
TO_DATE('1/1/2011', 'mm/dd/yyyy') + LEVEL - 1 dt
FROM DUAL
CONNECT BY TO_DATE('1/1/2011', 'mm/dd/yyyy') + LEVEL - 1 <
ADD_MONTHS(TO_DATE('1/1/2011', 'mm/dd/yyyy'), 12))
WHERE TO_CHAR(dt, 'd') NOT IN (1, 7)
If you want your "ID" column to be contiguous, you can use rownum instead of lvl in the outer query.
You can use one of the following date formats to check which day it is.
select to_char(sysdate,'DAY') from dual; /* TUESDAY */
select to_char(sysdate,'D') from dual; /* 3 */
select to_char(sysdate,'DY') from dual; /* TUE */
Add the if statement as shown below to remove days that equal SAT or SUN.
BEGIN
FOR i IN 1..365 LOOP
IF to_char(sysdate-1+i,'DY') NOT in ('SAT','SUN') THEN
INSERT INTO MY_TABLE (ID, MY_DATE) VALUES (i, (to_date(sysdate,'DD-MON-YY')-1)+i);
END IF;
END LOOP;
END;
If you let Monday = 0 and Sunday = 6 you could use (if mod(i,7) < 4 )) then Insert... should work.

Difference between two dates

I have a table that has the following data
fromDate | toDate
20JAN11 | 29DEC30
Both dates are for the 21st Century (i.e. 2011 and 2030) but only the last two characters are stored.
Why is the following statement (when run from within a PL/SQL module) against the above data always returns a positive value
dateDifference := (fromDate - toDate)
If i run the following statement from sqlplus i get the correct negative value which is correct.
select to_date('20JAN11','DDMONYY')-to_Date('29DEC30','DDMONYY') from dual;
I remember reading somewhere that Oracle would sometimes use the wrong century but i dont quite remember the exact scenario where that would happen.
Assuming those columns are of DATE datatype, which seems to be the case: Oracle always stores DATE values in an internal format which includes the full year. The fact that you are seeing only a 2-digit year has to do with the date format used to convert the date to a string for display. So most likely the stored century values are not what you think they are.
Try selecting the dates with an explicit format to see what you really have stored:
SELECT TO_CHAR( fromDate, 'DD-MON-YYYY' ), TO_CHAR( toDate, 'DD-MON-YYYY' )
Seems to work for me either way on my 10g database:
SQL> set serveroutput on
SQL>
SQL> DECLARE
2 d1 DATE := to_date('20JAN11','DDMONRR');
3 d2 DATE := to_date('29DEC30','DDMONRR');
4 diff INTEGER;
5 BEGIN
6 diff := d1 - d2;
7 dbms_output.put_line(diff);
8 END;
9 /
-7283
PL/SQL procedure successfully completed
SQL>
EDIT: works for YY instead of RR year format as well.
EDIT2: Something like this, you mean?
SQL> create table t (d1 date, d2 date);
Table created
SQL> insert into t values (to_date('20JAN11','DDMONYY'), to_date('29DEC30','DDMONYY'));
1 row inserted
SQL> commit;
Commit complete
SQL>
SQL> DECLARE
2 R t%ROWTYPE;
3 diff INTEGER;
4 BEGIN
5 SELECT d1, d2
6 INTO R
7 FROM t;
8 diff := R.d1 - R.d2;
9 dbms_output.put_line(diff);
10 END;
11 /
-7283
PL/SQL procedure successfully completed
SQL>
As #Alex states, you may want to verify your data.
works without formatting as well
CREATE TABLE DATETEST(FROMDATE DATE, TODATE DATE);
insert into DATETEST (fromdate,todate) values (to_date('20Jan11','ddMonrr'),to_date('29DEC30','ddMonrr'));
SELECT TO_CHAR(FROMDATE,'ddMonrrrr hh24:mi:ss') FROMDATE,
TO_CHAR(TODATE,'ddMonrrrr hh24:mi:ss') TODATE
from datetest ;
/*
FROMDATE TODATE
------------------ ------------------
20Jan2011 00:00:00 29Dec2030 00:00:00
*/
set serveroutput on
DECLARE
l_FROMDATE DATETEST.FROMDATE%type ;
L_TODATE DATETEST.TODATE%TYPE;
dateDifference number;
BEGIN
--notice -- no formatting just putting them into a variable for test
SELECT FROMDATE, TODATE
INTO L_FROMDATE, L_TODATE
from datetest;
DATEDIFFERENCE := L_FROMDATE - L_TODATE ;
DBMS_OUTPUT.PUT_LINE('DATEDIFFERENCE = ' || DATEDIFFERENCE );
end ;
--DATEDIFFERENCE = -7283
SELECT FROMDATE-TODATE
from datetest ;
/* --still not formatting
FROMDATE-TODATE
----------------------
-7283
*/
SELECT (FROMDATE - TODATE) DATEDIFF,
TO_CHAR(FROMDATE,'ddMonrrrr') FROMDATE,
to_char(todate,'ddMonrrrr') todate
from (
SELECT TO_DATE('20JAN11','DDMONYY') FROMDATE,
TO_DATE('29DEC30','DDMONYY') TODATE
FROM DUAL)
;
/*
DATEDIFF FROMDATE TODATE
---------------------- --------- ---------
-7283 20Jan2011 29Dec2030
*/
try running the first query on your table:
SELECT TO_CHAR(FROMDATE,'ddMonrrrr hh24:mi:ss') FROMDATE,
TO_CHAR(TODATE,'ddMonrrrr hh24:mi:ss') TODATE
from datetest ;
see if the years are what you actually expect.
(Edit: changed to use two digit years)