oracle database data synchronized from one to multi relative tables - oracle

I hava a user table , which is quite simple
create table user (
user_id int primary key,
user_name varchar2(20)
)
And I build a couples of relative tables assocaite with user table and each table has a user_id , user_name.
So here comes a question, I happend misinput a data with wrong name, but then I just linked to this wrong record with all relative tables. If I want correct the user table and same time synchronized user_name in all relative tables.How I do in simple way? Plus I didn't set any constraint with these tables.
Edit:
So let me put that more clearly. I can query all user from user table, and then I just create a select in the jsp page. And this selector got two field user_id, user_name. This is how we call it a selector. First I recorded a man with '01','tam' maybe, and I just recorded another row in salary with 'tam','$1300'. This was all wrong cause name was 'tom'. It's easily to change user or salary , but in our system, there are over 40 tables linked to user. I know it's a bad idea but it is designed that way
by our dba and it already worked a long time.

We'll start by making the problem explicit. The data model violates Third Normal Form: instead of relying on user_id to reference user_name every table dependent on the user table has the attribute. The consequence of this is that correcting a mistake in user_name means propagating that change to every table.
Further more it seems that this application lacks a mechanism for correcting errors, or rather propagating the correction to all the impacted tables. So, what to do?
Dynamic SQL and the data dictionary to the rescue:
declare
l_id user.user_id%type := 1234;
l_old_name user.user_name%type := 'Tam';
l_new_name user.user_name%type := 'Tom';
begin
for rec in ( select table_name from user_tab_cols where column_name = 'USER_ID'
intersect
select table_name from user_tab_cols where column_name = 'USER_NAME'
)
loop
execute immediate 'update '|| rec.table_name ||
' set user_name = :1 where user_id = :2 and user_name = :3'
using l_new_name, l_id, l_old_name;
commit;
end loop;
end;
/
No guarantees about performance, because it depends on the data and indexing for each table.
"it already worked a long time"
Which makes me wonder how many data inconsistencies are contained in your system that you don't know about? Maybe your DBA needs to brush up on their data modelling skills.

Related

Set new row value on insert from select statement in Oracle SQLplus

Is it possible to set a trigger to set the new row's value to be the result of a select statement? My current syntax is as follows and it's just not working:
CREATE TRIGGER "BRAND_NEW_TRIGGER"
BEFORE INSERT ON my_table
FOR EACH ROW
BEGIN
:NEW.column_one := (SELECT details_col FROM other_table WHERE property_id = :NEW.property_id);
END;
/
I've fudged the details of the code above to protect my company's security, I know the code above doesn't make too much sense but there is a valid reason I need to pull and organise the data this way.
You can do a select into
select ot.details_col
into :new.column_one
from other_table ot
where ot.property_id = :new.property_id;
Of course, I'd strongly question the data model if this makes sense. That strongly implies that you've got a data model in need of some normalization.

ORA-02437: "primary key violated" - why can't I see duplicate ID in SQL Developer?

I would receive an error:
ORA-02437: cannot validate (%s.%s) - primary key violated
Cause: attempted to validate a primary key with duplicate values or null values
I found it was because I have a stored procedure that increments the ID, but it had failed to do so when it re-ran and had an error related to one of my datatypes. I found I now had a duplicate ID in my database table. All this made sense and I was able to easily rectify it with a DELETE FROM MyTable WHERE ID = x, where x was the offending duplicate ID. The problem I have is the only way I was able to even find the IDs that were duplicated is in the first place is because I did a SELECT * FROM MyTable WHERE ID = x -- where x was one greater than the last ID I could actually see. I found it just by an educated guess. So:
Why can't I see these duplicate IDs when I open the table in Oracle SQL Developer? It only shows the last row as the ID before the duplicates. I don't think it is because of my primary key constraint, since the first line in my stored procedure is to remove that (and put it back, at the end - probably when I got my error), and it was not present when I looked at my table.
Is there some way to make these last IDs that got inserted into the table visible, so I wouldn't have to guess or assume that the duplicate IDs are "hiding" as one greater than the last ID I have in my table, in the future? There is a commit; in my stored procedure, so they should have appeared -- unless, of course, the procedure got hung up before it could run that line of code (highly probable).
Stored procedure that runs:
create or replace
PROCEDURE PRC_MYTABLE_INTAKE(
, EMPLOYEE_ID IN NVARCHAR2
, TITLE_POSITION IN NVARCHAR2
, CREATED_DATE IN DATE
, LAST_MODIFIED IN DATE
) AS
myid integer := 0;
appid integer := 0;
BEGIN
-- disable PK constraint so it can be updated
EXECUTE IMMEDIATE 'ALTER TABLE MYTABLE DROP CONSTRAINT MYTABLE_PK';
COMMIT;
-- assign ID to myid
SELECT ID INTO myid FROM MYTABLE WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE);
-- increment
myid := myid + 1;
-- assign APPLICATION_ID to appid
SELECT APPLICATION_ID INTO appid FROM MYTABLE WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE);
-- increment
appid := appid + 1;
-- use these ids to insert with
INSERT INTO MYTABLE (ID, APPLICATION_ID,
, EMPLOYEE_ID
, TITLE_POSITION
, CREATED_DATE
, LAST_MODIFIED
) VALUES(myid, appid,
, EMPLOYEE_ID
, TITLE_POSITION
, CREATED_DATE
, LAST_MODIFIED
);
COMMIT;
-- re-enable the PK constraint
EXECUTE IMMEDIATE 'ALTER TABLE PASS ADD CONSTRAINT MYTABLE_PK PRIMARY KEY (ID)';
COMMIT;
END;
Here's one problem:
SELECT ID
INTO myid
FROM MYTABLE
WHERE ROWID IN (SELECT MAX(ROWID) FROM MYTABLE)
There is no correlation between ID and ROWID, so you're not getting the maximum current ID, you're just getting the one that happens to be on the row that is furthest from the start of a datafile with a high number.
The code you need is:
SELECT COALESCE(MAX(ID),0)
FROM MYTABLE;
Or better yet, just use a sequence.
No idea why you're dropping the PK either.
Furthermore, when you issue the query:
SELECT APPLICATION_ID INTO appid ...
... that could be for a different row than the one you already got the id for, because a change could have been committed to the table.
Of course another issue is that you can't run two instances of this procedure at the same time either.
For David Aldridge, since he wants to look at code instead of the real reason I posted my question, run this ---
CREATE TABLE YOURSCHEMA.TESTING
(
TEST_ID NVARCHAR2(100) NOT NULL
, TEST_TYPE NVARCHAR2(100) NOT NULL
, CONSTRAINT TEST_PK PRIMARY KEY
(
TEST_ID
)
ENABLE
);
create or replace
PROCEDURE PRC_TESTING_INSERT(
TEST_TYPE IN NVARCHAR2
) AS
testid integer := 0;
BEGIN
-- disable PK constraint so it can be updated
EXECUTE IMMEDIATE 'ALTER TABLE TESTING DROP CONSTRAINT TEST_PK';
COMMIT;
-- assign TEST_ID to testid
SELECT TEST_ID INTO testid FROM TESTING WHERE ROWID IN (SELECT MAX(ROWID) FROM TESTING);
-- increment
testid := testid + 1;
-- use this id to insert with
INSERT INTO TESTING (TEST_ID, TEST_TYPE) VALUES(testid, TEST_TYPE);
COMMIT;
-- re-enable the PK constraint
EXECUTE IMMEDIATE 'ALTER TABLE TESTING ADD CONSTRAINT TEST_PK PRIMARY KEY (TEST_ID)';
COMMIT;
END;
SET serveroutput on;
DECLARE
test_type varchar(100);
BEGIN
test_type := 'dude';
YOURSCHEMA.PRC_TESTING_INSERT(test_type);
-- to verify the variable got set and procedure ran, could do:
--dbms_output.enable;
--dbms_output.put_line(test_type);
END;
Now, because there is no data in the table, the stored procedure will fail with ORA-06512: no data found. If you then try and run it again, you will get ORA-02443: cannot drop constraint - nonexistent constraint, because the EXECUTE IMMEDIATE 'ALTER TABLE TESTING DROP CONSTRAINT TEST_PK'; successfully dropped it, and the procedure never ran the command at the end to re-add it. This is what made me think I needed the commits, but even without them, it still will not complete the whole procedure.
To prove that the procedure DOES run, if given proper data, run this after creating the table, but before creating/running the stored procedure:
INSERT INTO TESTING (TEST_ID, TEST_TYPE)
VALUES ('1', 'hi');
And if you run the proc from a new table (not one with its constraint dropped), it will run fine.
Since mathguy didn't post this as the answer, though I'll credit him for the information...
Answer to why I can't see the duplicates is because the COMMIT does not occur in the procedure when it failed due to a datatype mismatch (which we found was actually in the application's code that sent the variable's values into this procedure, not in the stored procedure, itself). (It's also why I'll mark down anyone that says you don't have to add so many COMMIT lines in this procedure.) The commands were run in the session of the user that starts it - in my case, another session of the same DB user I was logged in with, but started from my application, instead of my SQL Developer session. It also explains why I could do a COMMIT, myself, but it did not affect the application's session - I could not commit any actions ran from another session. Had I ran a COMMIT as an OracleCommand and did an .ExecuteNonQuery on my OracleConnection right after the failure within the catch of my application, I would have seen the rows in SQL Developer without having to do a special query.
So, in short, the only way to see the items was with a direct query using WHERE ID =, find the last ID and increment it, and put it in the query.

Trigger to find next available inventory location

I am trying to implement inventory tracking and am running into problems. As this is my first foray into database triggers (& PL/SQL in general) I think I need an adjustment to my thinking/understanding of how to solve this issue.
My situation is as follows: Each time a new item is added to my inventory, I need to auto-assign it the first available physical storage location. When items are consumed, they are removed from the inventory thus freeing up a physical location (i.e. we are recycling these physical locations). I have two tables: one inventory table and one table containing all legal location names/Ids.
Table: ALL_LOCATIONS
Location_ID
SP.1.1.1.a
SP.1.1.1.b
SP.1.1.1.c
SP.1.1.2.a
SP.1.1.2.b
SP.1.1.2.c
SP.1.1.3.a
SP.1.1.3.b
SP.1.1.3.c
...
SP.25.5.6.c
Table: ITEM_INVENTORY
Item_ID | Location_ID
1 SP.1.1.1.a
2 SP.1.1.1.b
4 SP.1.1.2.a
5 SP.1.1.2.b
6 SP.1.1.2.c
21 SP.1.1.4.a
… …
Note: First available location_ID should be SP.1.1.1.c
I need to create a trigger that will assign the next available Location_ID to the inserted row(s). Searching this site I see several similar questions along these lines, however they are geared towards the logic of determining the next available location. In my case, i think I have that down, but I don't know how to implement it as a trigger. Let's just focus on the insert trigger. The "MINUS" strategy (shown below) works well in picking the next available location, but Oracle doesn't like this inside a trigger since I am reading form the same table that I am editing (throws a mutating table error).
I've done some reading on mutating table errors and some workarounds are suggested (autonomous transactions etc.) however, the key message from my reading is, "you're going about it the wrong way." So my question is, "what's another way of approaching this problem so that I can implement a clean & simple solution without having to hack my way around mutating tables?"
Note: I am certain you can find all manner of things not-quite-right with my trigger code and I will certainly learn something if you point them out -- however my goal here is to learn new ways to approach/think about the fundamental problem with my design.
create or replace TRIGGER Assign_Plate_Location
BEFORE INSERT ON ITEM_INVENTORY
FOR EACH ROW
DECLARE
loc VARCHAR(100) := NULL;
BEGIN
IF(:new.LOCATION_ID IS NULL) THEN
BEGIN
SELECT LOCATION_ID INTO loc FROM
(SELECT DISTINCT LOCATION_ID FROM ALL_LOCATIONS
MINUS
SELECT DISTINCT LOCATION_ID FROM ITEM_INVENTORY)
WHERE ROWNUM = 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
loc := NULL;
END;
IF(loc IS NOT NULL) THEN
:new.LOCATION_ID := loc;
END IF;
END IF;
END;
There are several ways to do it. You could add column AVAILABLE or OCCUPIED to first table
and select data only from this table with where available = 'Y'. In this case you need also triggers
for delete and for update of location_id on second table.
Second option - when inserting data use merge or some procedure retrieving data from all_locations when item_inventory.location_id is null.
Third option - Oracle 11g introduced compound triggers
which allows better handling mutating tables. In this case trigger would look something like this:
create or replace trigger assign_plate_location
for insert on item_inventory compound trigger
loc varchar2(15) := null;
type t_locs is table of item_inventory.location_id%type;
v_locs t_locs;
i number := 1;
before statement is
begin
select location_id
bulk collect into v_locs from all_locations al
where not exists (
select location_id from item_inventory ii
where ii.location_id = al.location_id );
end before statement;
before each row is
begin
if :new.location_id is null then
if i <= v_locs.count() then
:new.location_id := v_locs(i);
i := i + 1;
end if;
end if;
end before each row;
end assign_plate_location;
I tested it on data from your example, inserts (with select) looked OK. You can give it a try, check if it's efficient, maybe this will suit you.
And last notes - in your select you do not need distinct, MINUS makes values distinct.
Also think about ordering data, now your select (and mine) may take random row from ALL_LOCATIONS.

How can i insert a variable value into a column of table using trigger and sequence for oracle 10g db

Am developing an application which helps people plan there schedule.
Lets say i have a table called 'Plan_Table' in which there are columns like
id,user_name, timestamp,place,event,plan_number.
For each day, a person can insert many records depending on his activities. I want 'plan_number' column to be populated by a trigger.
Suppose an user inserts five records at a time(in a batch). I want the plan_number field to be inserted as
plan_1
plan_1
plan_1
plan_1
plan_1
if he comes up with another plan.. and does few inserts, lets say 3 this time... I want the plan_number field to be inserted as
plan_2
plan_2
plan_2
How to achieve this using trigger and sequence?
Thanks in advance.
I think you can use the combination of the before statement level trigger for that table along with the global package variables and then use them in the Row Level trigger for that table.
Hope it gives you a heads up with the above logic
var_plan_number number := 0;
begin
if :new.plan_number is null then
select max(plan_number) into var_plan_number from plan_table where timestamp < CURRENT_TIMESTAMP - 5 --*some_treshold - ie 5 seconds*
and timestamp > CURRENT_TIMESTAMP and timestamp > trunc(sysdate) and user_name = :new.user_name;
--idea is to select max plan_number for current day and increment it by 1
--treshold should be set to time, your script is able to process batch records
var_plan_number := var_plan_number + 1;
:new.plan_number := var_plan_number;
end if;
that should do the trick...
Please consider this as a pseudo code, how your trigger should look like. There is no need for sequences.
The problem lies in the definition of "at a time (in a batch)". It will be difficult to tell the trigger when one batch ends and when a new one begins. It is possible with package variables, but the most competent place is your application.
I'd create a sequence to generate the ids, but pick up the ids in your application and feed them directly to the INSERT statement:
CREATE SEQUENCE myids;
CREATE TABLE plan_table(id int, user_name varchar2(30), mytimestamp, ...);
And in your code:
SELECT myids.nextval INTO myplanid FROM DUAL;
INSERT INTO plan_table(myplanid, myuser_name, SYSTIMESTAMP, place1 ...);
INSERT INTO plan_table(myplanid, myuser_name, SYSTIMESTAMP, place2 ...);
INSERT INTO plan_table(myplanid, myuser_name, SYSTIMESTAMP, place3 ...);
COMMIT;
Thanks for the answers you provided me. You really let me think in a purely db perspective.
As a web application developer, I thought, its a much better approach to use sequence/trigger to help me out with this problem. However, I found the solution from business logic of Application itself.
I am using Hibernate ORM for managing db tables. Hence i pulled out the max value of plan number using the following pieces of code.
Session session = getSessionFactory().openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria(Mwwp_Plan.class).setProjection(Projections.max("plan_number"));
Integer maxPlanNumber = (Integer) criteria.uniqueResult();
session.getTransaction().commit();
System.out.println(maxPlanNumber);
if(maxPlanNumber==null)
{
System.out.println("maxPlanNumber is null");
maxPlanNumber = 0;
}
else
{
}
System.out.println("maxPlanNumber:"+maxPlanNumber);
return maxPlanNumber;
This is inside a function which my app uses to get the max(plan_number). If there is no plan_number in the table. It will give a default of 1.
Note: Mwwp_Plan is the name of table i used in my application.
Hence I achieved what i wanted.
Thanks for your help.

Writing Procedure to enforce constraints + Testing

I need to set a constraint that the user is unable to enter any records after he/she has entered 5 records in a single month. Would it be advisable that I write a trigger or procedure for that? Else is that any other ways that I can setup the constraint?
Instead of writing a trigger i have opt to write a procedure for the constraint but how do i check if the procedure is working?
Below is the procedure:
CREATE OR REPLACE PROCEDURE InsertReadingCheck
(
newReadNo In Int,
newReadValue In Int,
newReaderID In Int,
newMeterID In Int
)
AS
varRowCount Int;
BEGIN
Select Count(*) INTO varRowCount
From Reading
WHERE ReaderID = newReaderID
AND Trunc(ReadDate,'mm') = Trunc(Sysdate,'mm');
IF (varRowCount >= 5) THEN
BEGIN
DBMS_OUTPUT.PUT_LINE('*************************************************');
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE(' You attempting to enter more than 5 Records ');
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('*************************************************');
ROLLBACK;
RETURN;
END;
ELSIF (varRowCount < 5) THEN
BEGIN
INSERT INTO Reading
VALUES(seqReadNo.NextVal, sysdate, newReadValue,
newReaderID, newMeterID);
COMMIT;
END;
END IF;
END;
Anyone can help me look through
This is the sort of thing that you should avoid putting in a trigger. Especially the ROLLBACK and the COMMIT. This seems extremely dangerous (and I'm not even sure whether it's possible). You might have other transactions that you wish to commit that you rollback or vice versa.
Also, by putting this in a trigger you are going to get the following error:
ORA-04091: table XXXX is mutating, trigger/function may not see it
There are ways round this but they're excessive and involve doing something funky in order to get round Oracle's insistence that you do the correct thing.
This is the perfect opportunity to use a stored procedure to insert data into your table. You can check the number of current records prior to doing the insert meaning that there is no need to do a ROLLBACK.
It depends upon your application, if insert is already present in your application many times then trigger is better option.
This is a behavior constraint. Its a matter of opinion but I would err on the side of keeping this kind of business logic OUT of your database. I would instead keep track of who added what records in the records table, and on what day/times. You can have a SP to get this information, but then your code behind should handle whether or not the user can see certain links (or functions) based on the data that's returned. Whether that means keeping the user from accessing the page(s) where they insert records, or give them read only views is up to you.
One declarative way you could solve this problem that would obey all concurrency rules is to use a separate table to keep track of number of inserts per user per month:
create table inserts_check (
ReaderID integer not null,
month date not null,
number_of_inserts integer constraint max_number_of_inserts check (number_of_inserts <= 5),
primary key (ReaderID, month)
);
Then create a trigger on the table (or all tables) for which inserts should be capped at 5:
create trigger after insert on <table>
for each row
begin
MERGE INTO inserts_check t
USING (select 5 as ReaderID, trunc(sysdate, 'MM') as month, 1 as number_of_inserts from dual) s
ON (t.ReaderID = s.ReaderID and t.month = s.month)
WHEN MATCHED THEN UPDATE SET t.number_of_inserts = t.number_of_inserts + 1
WHEN NOT MATCHED THEN INSERT (ReaderID, month, number_of_inserts)
VALUES (s.ReaderID, s.month, s.number_of_inserts);
end;
Once the user has made 5 inserts, the constraint max_number_of_inserts will fail.

Resources