Oracle SQL exlusion constraint - oracle

I am looking for an Oracle constraint that will exlude all rows in a table that have already been updated for a given year. For example, a given Id field of "123456789_2019_blah" includes the year 2019 as part of the Id. Each day a query will check the table to see if a given year is missing in the Id, such as the year 2020 in "123456789_2020_blah". If 2020 does not exist, a second row will be inserted with the value "123456789_2020_blah"
-----------------------
Id
-----------------------
1: 123456789_2019_blah
2: 123456789_2020_blah
Going forward, any other time the query runs it should never return rows for Id "123456789_2019_blah" or "123456789_2020_blah". The following year will repeat with 2021, etc. (Assume that field Id is the only field available for the constraint)
I tried using REGEXP_INSTR to check its length but this still returns the 2019 row because there will always be one true result. I also tried group by having with the same result.
where (REGEXP_INSTR(Id,'*_2019_*') > 0 and REGEXP_INSTR(Id,'*_2020_*') = 0)

The solution was to create a function that returns 0 if the latest year does not exist.
Return 0 if the latest year does not exist
REGEXP_INSTR(name,'*_' || i_year || '_*') > 0

Related

hour must be between 1 and 12 - sql error or ORA-01830: date format picture ends before converting entire input string

I have a column with timestamp stored in 20-02-18 03:50:58.347000000 PM format. These timestamps are stored in multiple rows so I want to update only the milliseconds in this column by using random number generator so that it will be unique for every row. I tried to use below query for updating the time
UPDATE table
SET field = TO_TIMESTAMP('18-01-18 02:23:27.265000050 PM'|| ' ' || TO_TIMESTAMP(field, 'HH:MI:SS.FF'),'DD-MM-YY HH:MI:SS.FF')
where objid = XXX;
I'm getting an error - ORA-01849: hour must be between 1 and 12
if I change the query to below format -
ow query for updating the time
UPDATE table
SET field = TO_TIMESTAMP('18-01-18 02:23:27.265000050 PM'|| ' ' || TO_TIMESTAMP(field, 'hh12:MI:SS.FF'),'DD-MM-YY hh12:MI:SS.FF')
where objid = XXX;
I get below error -
ORA-01830: date format picture ends before converting entire input string. Can anyone check this please
Don't pfaff around with date masks. Just use INTERVAL to add milliseconds:
update t23
set ts = ts + interval '0.001' second * dbms_random.value(0,999)
Here is a demo on db<>fiddle.
using random number generator so that it will be unique for every row.
Not guaranteed. A random series can still contain duplicate numbers. However, if you have only a few timestamps per second it's unlikely you will get any clashes. Likelihood of collisions increases with the number of timestamps per second. So if uniqueness is the object of the exercise this is the wrong approach. You need a different key to uniquely identify your records (probably a technical key such as a sequence or identity column).

Oracle historical reporting - what was the row at a point in time

I have been asked to run a report of the state of our assets at a fixed point in time (1st Jan 2019).
The way this database has been written is that the asset has its own table with current info and then for various bits of data there is also the history of that info changing, each bit is stored its own "history" table with a start and end date. So for example one of the bits of info is the asset class - the asset table will have a field that contains the current asset class and then if that class has changed in the past then there will be rows in the asset_history table with start and end dates. Something like...
AssetID AssetClass StartDate EndDate
------- ---------- --------- -------
1 1 12-12-87 23-04-90
1 5 23-04-90 01-02-00
1 2 01-02-00 27-01-19
1 1 27-01-19
So this asset has changed classes a few times but I need to write something to be able to check, for each asset, and work out which class was the active class as at 1st Jan. For this example that would be the second-from last row as it changed to class 2 back in 2000 and then after 1st Jan 2019 it became a class 1.
And to make it more complicated I will need this for several bits of data but if I can get the notion of how to do it right then I'm happy to translate this to the other data.
Any pointers would be much appreciated!
I usually write this like
select assetClass
from history_table h
where :point_in_time >= startDate
and (:point_in_time < endDate
or endDate is null)
(assuming that those columns are actually date type and not varchar2)
between always seems tempting, but it includes both endpoints, so you'd have to write something like :point_in_time between startDate and (endDate - interval '1' second)
EDIT: If you try to run this query with a point_in_time before your first start_date, you won't get any results. That seems normal to me, but maybe instead you want to pick "the first result which hasn't expired yet", like this:
select assetClass
from history_table h
where (:point_in_time < endDate
or endDate is null)
order by startDate asc
fetch first 1 row only

migration oracle app to postgresql date/time field value out of range: "1400-02-29 00:00:00 AD"

Im migrating an application from oracle to postgresql. In one of the functions that I already migrated I copy data from a different oracle db (db link in oracle, oracle_fdw extension in postgresql) from a few tables into a local table in my postgresql db. However, I`m getting the next error :
NOTICE: Insert data into table from remote table : insert into IP_MAN
select * from IP_MAN_production
NOTICE: PROCEDURE copy_table : V_Step = 4 // **SQLERRM = date/time field
value out of range: "1400-02-29 00:00:00 AD"**
CONTEXT: converting column "birthday" for foreign table scan of "ip_man_production",
row 32481
When I try to select the specific row in the oracle db I get the next value :
select date from bezeq.ip_manuim where
birthday=to_date('29/02/1400','dd/mm/yyyy');
birthday
--------
01010001
birthday is datatype is timestamp without time zone.
Any idea ?
PostgreSQL thinks that 1400 was no leap year.
See this definition in src/include/utils/datetime.h:
/*
* These are the rules for the Gregorian calendar, which was adopted in 1582.
* However, we use this calculation for all prior years as well because the
* SQL standard specifies use of the Gregorian calendar. This prevents the
* date 1500-02-29 from being stored, even though it is valid in the Julian
* calendar.
*/
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
As the comment says, it is technically wrong to reject 1400-02-29, but it is justified with a vague mention of the SQL standard. I don't know if the argument is valid, but I won't dig deeper since it seems that you have solved your peoblem.

Oracle query to fetch the previous value of a related row in same table

I have a table Student which has name and ratings year wise.
Name Year Rating
Ram 2016 10
Sam 2016 9
Ram 2014 8
Sam 2012 7
I need to find the previous rating of the employee which could be last year or some years before.
The query should return below results
Name Cur_rating_year_2016 Prev_rating
Ram 10 8
Sam 9 7
Below is the script for insert and create
Create table Student (name varchar2(10), year number, rating number );
insert into student values('Ram' ,2016 ,10);
insert into student values('Sam' ,2016 ,9);
insert into student values('Sam' ,2012 ,7);
insert into student values('Ram' ,2014 ,8);
Is there a way to achieve this using select query?
Use LAG analytical function https://docs.oracle.com/database/122/SQLRF/LAG.htm#SQLRF00652
LAG is an analytic function. It provides access to more than one row
of a table at the same time without a self join. Given a series of
rows returned from a query and a position of the cursor, LAG provides
access to a row at a given physical offset prior to that position.
For the optional offset argument, specify an integer that is greater
than zero. If you do not specify offset, then its default is 1. The
optional default value is returned if the offset goes beyond the scope
of the window. If you do not specify default, then its default is
null.
SELECT stud_name AS name,
r_year AS year,
r_value AS rating,
lag(r_value, 1, NULL) OVER(PARTITION BY stud_name ORDER BY r_year) AS prev_rating
FROM stud_r
ORDER BY stud_name;
Try as:
SELECT A.NAME,A.RATING,B.RATING FROM
STUDENTS A INNER JOIN STUDENTS B
ON A.NAME=B.NAME
WHERE A.YEAR='2016' AND B.YEAR<>'2016'
ORDER BY A.NAME ASC

Oracle - Date substraction in where clause

I'm trying to figure out how to compare the result of a date substraction in a where clause.
Clients subscribed to a service and therefore are linked to a subscription that has an end date. I want to display the list of subscriptions that will come to an end within the next 2 weeks.
I did not designed the databse but noticed that the End_Date column type is a varchar and not a date.. I can't change that.
My problem is the following:
If I try to select the result of the substraction for example with this request:
SELECT(TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) , s.name
from SUBSCRIPTION s WHERE s.id_acces = 15
This will work and give me the number of days between the end of the subscription and the current date.
BUT now, if I try to include the exact same request in a clause where for comparison:
SELECT s.name
from SUBSCRIPTION S
WHERE (TO_DATE(s.end_date,'YYYY-MM-DD') - TRUNC(SYSDATE)) between 0 and 16
I will get an error: "ORA-01839 : date not valid for month specified".
Any help would be appreciated..
Somewhere in the table you have your date formatted in a different way from YYYY-MM-DD. In your first query you check a certain row (or a set of rows, s.id_acces = 15), which is probably ok, but in the second you scan through all the table.
Try finding these rows with something like,
select end_date from subscription
where not regexp_like(end_date, '[0-9]{4}-[0-9]{2}-[0-9]{2}')
Check your DD value (ie: day of the month). This value must be between 1 and the number of days in the month.
January - 1 to 31
February - 1 to 28 (1 to 29, if a leap year)
March - 1 to 31
April - 1 to 30
May - 1 to 31
June - 1 to 30
July - 1 to 31
August - 1 to 31
September - 1 to 30
October - 1 to 31
November - 1 to 30
December - 1 to 31
" the End_Date column type is a varchar and not a date.. I can't
change that."
If you can't change the date you'll have to chang3 the data. You can identify the rogue values with this function:
create or replace check_date_format (p_str in varchar2) return varchar2
is
d date;
begin
d := to_date(p_str,'YYYY-MM-DD');
return 'VALID';
exception
when others then
return 'INVALID';
end;
You can use this function in a query:
select sid, end_date
from SUBSCRIPTION
where check_date_format(end_date) != 'VALID';
Your choices are:
fix the data so all the dates are in the same format
fix the data and apply a check constraint to enforce future validity
write a bespoke MY_TO_DATE() function which takes a string and applies lots of different date format masks to it in the hope of getting a successful conversion.

Resources