We have to implement a versioning system for documents. Each document has an identifier (DOCID), a major and a minor version number. All of this is stored in a table
DOCID MAJOR MINOR
123 1 0
123 1 1
123 2 0
123 1 2
455 1 0
455 2 0
We need to implement a stored proceudre that will take four parameters: whether we want the next major or minor version (VERSION_TYPE), the document ID, the current major version and the current minor version and have to give the next avaialable version number.
For example based on above table if I want a new major version for document 123 I would get 3.0, if I want a new minor version for document 123 for current major version 1 and current minor version 2 I would get 1.2.
If I want to get a version for a new doc id that is not in the system, it has to store the new doc ID and give back major version 1 and minor version 0.
I wrote the below stored procedure but it gives me an error on the last line when I try to execute it:
Cannot insert NULL INTO REVIEW_VERSIONS.DOCID
Any idea what's wrong? Many thanks
CREATE OR REPLACE PROCEDURE GET_NEXT_VERSION
(
VERSION_TYPE IN VARCHAR2
, DOC_ID IN VARCHAR2
, CURRMAJOR IN NUMBER
, CURRMINOR IN NUMBER
, NEXTMINOR OUT NUMBER
, NEXTMAJOR OUT NUMBER
) AS
BEGIN
IF VERSION_TYPE = 'MAJOR' THEN
NEXTMINOR := 0;
SELECT MAX(MAJOR)+1 INTO NEXTMAJOR FROM REVIEW_VERSIONS WHERE DOCID= DOC_ID;
IF NEXTMAJOR IS NULL THEN
NEXTMAJOR := 1;
END IF;
ELSE IF VERSION_TYPE = 'MINOR' THEN
NEXTMAJOR := CURRMAJOR;
SELECT MINOR+1 INTO NEXTMINOR FROM REVIEW_VERSIONS WHERE MAJOR = CURRMAJOR AND DOCID = DOC_ID AND MINOR = (SELECT MAX(MINOR) FROM REVIEW_VERSIONS WHERE MAJOR = CURRMAJOR AND DOCID = DOC_ID);
IF NEXTMINOR IS NULL THEN
NEXTMAJOR := 1;
NEXTMINOR := 0;
END IF;
END IF;
INSERT INTO REVIEW_VERSIONS (DOCID, MAJOR, MINOR) VALUES (DOC_ID, NEXTMAJOR, NEXTMINOR);
END GET_NEXT_VERSION;
Last seen code: When VERSION_TYPE = 'MINOR', you assign NEXTMAJOR := CURRMAJOR, but never check to see if CURRMAJOR is NULL.
You may wish to define DEFAULT values in the parameter statement.
Related
create table file( member_no number, filepath varchar2(100) );
I want to limit the number of duplicate rows of member_no in this table.
for example:
In this way, the number of member_no can be up to 5, but it shouldn't be more than six.
how can I do this?
So you have two ways I can think of:
when you are inserting (i assume you are using a store procedure) run an if to check the current rows
Declare
count number;
too_many_num_exception EXCEPTION;
BEGIN
select count(file_path) into count from file where member_no = <num_you_are_inserting>;
if(count = 5)
Then
raise too_many_num_exception;
end if;
insert(...);
EXCEPTION
WHEN too_many_num_exception then
--do something
end;
or you could try play around with creating indexes on you tables (however this may not work - it's just a thought)
CREATE UNIQUE INDEX file_ix1 on file (
CASE WHEN (select count() from file ... ) < 6 THEN member_id ELSE NULL END)
online
Although im not 100% if that would work
I'm trying to make a function that does a simple insert into a table called poli, the purpose of this fuction:
returns 1 when it inserts the values to the table
in any other case it returns 0.
This is the code in oracle that i wrote:
CREATE OR REPLACE FUNCTION ADDPOLI
( ID IN NUMBER, NAME IN VARCHAR2 , LON IN FLOAT , LAT IN FLOAT , STATUS OUT NUMBER )
return status
IS cursor poli_count is select count(id) from poli;
BEGIN
declare number_of_cities int;
fetch poli_c into number_of_cities;
if number_of_cities<= 15 and number_of_cities>=0 then
insert into poli values(id,name,lat,lon);
return 1;
else
return 0;
end if;
END ADDPOLI;
i have a syntax error here: fetch poli_c into number_of_cities;
how can i fix it ?
Why you are using cursor to achieve this. Try below -
CREATE FUNCTION ADDPOLI(ID INT, NAME VARCHAR(255), LON FLOAT, LAT FLOAT)
RETURNS INT
BEGIN
declare number_of_cities int;
select count(id) into number_of_cities from poli;
if number_of_cities between 0 and 15 then
insert into poli values(id,name,lat,lon);
return 1;
else
return 0;
end if;
END
There is something more fundamentally of concern here. What happens when you deploy this function in a multi-user environment (which most databases typically will run in).
The logic of:
"Do I have less than 15 cities?"
"Yes, insert another row"
is more complex than first appears. Because if I have 10 sessions all currently running this function, you can end up with the following scenario:
I start with say 13 rows. Then this happens:
Session 1: Is there less than 15? Yes, do the insert.
Session 2: Is there less than 15? Yes, do the insert.
Session 3: Is there less than 15? Yes, do the insert.
Session 4: Is there less than 15? Yes, do the insert.
Session 5: Is there less than 15? Yes, do the insert.
...
and now Session 1 commits, and so forth for Session 2, 3, ....
And hence voila! You now have 18 rows in your table and everyone is befuddled as to how this happened.
Ultimately, what you are after is a means of enforcing a rule about the data ("max of 15 rows in table X"). There is a lengthy discussion about the complexities of doing that over at AskTOM
https://asktom.oracle.com/pls/asktom/asktom.search?tag=declarative-integrity
I am converting some code in Access over to Oracle, and one of the queries in Access uses a table that I am unable to use in Oracle. I am unable to create new tables, so I am trying to figure out a way to use the logic behind the table in the FOR section of my select.
The logic of the table is similar to:
FOR i = 1 To 100
number = number + 1
.AddNew
!tbl_number = number
NEXT i
I'm trying to convert this to oracle, and so far I have:
FOR i in 1 .. 100 LOOP
number := number + 1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
I was thinking a cursor or a record would be the answer, but I can't seem to figure out how to implement that. In the end I basically want to have:
SELECT
table.number
FROM
(
--My for loop logic
) table
EDIT
The calculation is a bit more complicated; that was just an example. They aren't actually sequential, and there isn't really a pattern to rows.
EDIT
Here is a more complicated version of the for loop which is closer to what I'm actually doing:
FOR i in 1 .. 100 LOOP
number1 := number1 + 7;
number2 := (number2 + 8) / number1;
--This is where I am stuck; How do I simulate the table part
END LOOP;
You could use a recursive query (assuming you are on Oralce 11gR2 or later):
with example(idx, number1, number2) as (
-- Anchor Section
select 1
, 1 -- initial value
, 2 -- initial value
from dual
union all
-- Recursive Section
select prev.idx + 1
, prev.number1 + 7
, (prev.number2 + 8) / prev.number1
from example prev
where prev.idx < 100 -- The Guard
)
select * from example;
In the Anchor section set all the values for your first record. Then in the Recursive section setup the logic to determine the next records values as a function of the prior records values.
The Anchor section could select the initial values from some other table rather than being hard coded as in my example.
The recursive section needs to select from the named subquery (in this case example) but may also join to other tables as needed.
You need to generate a set with sequential integer numbers. Maybe you can use this (for Oracle 10g and above):
SELECT
ROWNUM NUM
FROM
DUAL D1,
DUAL D2
CONNECT BY
(D1.DUMMY = D2.DUMMY AND ROWNUM <= 100)
I want to order search results by (age group, rank), and have age groups of 1 day, 1 week, 1 month, 6 months etc. I know I can get the "days old" with
SELECT NOW()::DATE - created_at::DATE FROM blah
and am thinking to do a CASE statement based on that, but am I barking up the right tree performance wise? Is there a nicer way?
You can also create separate table with intervals definition and labels. However this comes at cost of extra join to get the data.
create table distance (
d_start int,
d_end int,
d_description varchar
);
insert into distance values
(1,7,'1 week'),
(8,30,'1 month'),
(31,180,'6 months'),
(181,365,'1 year'),
(366,999999,'more than one year')
;
with
sample_data as (
select *
from generate_series('2013-01-01'::date,'2014-01-01'::date,'1 day') created_at
)
select
created_at,
d_description
from
sample_data sd
join distance d on ((current_date-created_at::date) between d.d_start and d.d_end)
;
Using this function to update an INT column stored on the table for performance reasons,and running an occasional update task. What's nice that way is that it's only necessary to run it against a small subset of the data once per hour (anything <~ 1 week old), and every 24 hours can just run it against anything > 1 week old (perhaps even a weekly task for even older stuff.)
CREATE OR REPLACE FUNCTION age_group(_date timestamp) RETURNS int AS
$$
DECLARE
days_old int;
age_group int;
BEGIN
days_old := current_date - _date::DATE;
age_group := CASE
WHEN days_old < 2 THEN 0
WHEN days_old < 8 THEN 1
WHEN days_old < 30 THEN 2
WHEN days_old < 90 THEN 3
ELSE 4
END;
RETURN age_group;
END;
$$
LANGUAGE plpgsql;
My Source Data
ename Age
BAL N
BAL Y
BAL Y
YUV N
YUV Y
NAR N
Logic
if ( (ename <> Previous_Ename or Previous_AGE='N' ) then AGE = as is
Else AGE='Y'
Could you please let me know how to code this using Oracle funcaiton? i tried but in all case it not showing the desired result set.
i used
create or replace function () RETURN
VARCHAR2
IS
previous_name VARCHAR2 (9) := 'DUMMY';
previous_age VARCHAR2 (9) := 'Z';
BEGIN
For cur_rec in (select ename, age from tablename order by ename) LOOP
if ( cur_rec.ename <> previous_ename or previous_age ='N')
then return cur_rec.age; /** it is populating the result set with only "N"***/
else return 'Y';
end if;
previous_ename :=ename; /*** not sure whether this assignment is correct- im trying to assignt current value as previous value for next row reference.****/
previous_age :=age; /*** not sure whether this assignment is correct****/
END LOOP;
END
REsult im getting:- actually the result should be same as Source for this data scenerio
ename Age
BAL N
BAL N
BAL N
YUV N
YUV N
NAR N
I still don't understand what you're trying to achieve here (at all)... or why
The problem is that you're trying to to this in a function but you're returning the something almost immediately. By your order logic (in the comments) the first value will always be N because that's the first value in the ORDER BY. For every record in your table this will be true.
Use a MERGE statement instead:
merge into tmp n
using ( select rowid as rid
, ename
, age
, lag(age) over ( partition by ename order by age ) as lag_age
from tmp
) o
on ( n.rowid = o.rid )
when matched then
update
set n.age = case when lag_age is null then age
when lag_age = 'N' then age
else 'Y'
end
;
SQL Fiddle