I'm developing a java application that saves information in DB using procedures. I will give an example to show my doubt cause i'm kinda lost!
Lets pretend that i have this 2 different classes
public class Seg{
//variables
....
public class Dur{
//variables
private List list<Seg> //Lets pretend that Dur1 has 3 seg, and Dur1's PK = 1
....
And i want to save the information in DB. As the Dur1 has 3 seg and code PK=1 , so i will have 3 insert in seg that has a FK = 1 = Dur's PK
And my question is how can i automically, using a procedure, put a FK in the three seg inserts, assuming that (in java) i know all the matches between Seg and Dur(i have the list that connect them)
//Note: The pk is a attribute defined in the procedures with a sequence
I fear that some may not understand the question but in fact im a little bit confuse
Thanks all
Your example (and the focus on the FK) makes it not clear if you try define a plain PL/SQL layer to handle elementary CRUD (in PL/SQL called also TAPI) or if you intend to encapsulate some kind of business logic.
In the former case you may rethink your approach and have a look on some kind of ORM.
Don't understand me incorrect, I'm not trying to answer your question with "do something else". My point is, there are tons of experience with your situation (database assigned keys) in ORM, so simple search links similar to the above and adapt it to your PL/SQL solution.
In my opinion, you will need to provide an output parameter in the procedure storing the parent class returning the sequence assigned PK and pass this value in the procedure for storing the child classes.
Hey i tried to illustrate your scenario with an example. Hope it
helps. Please pardon any syntax error since i dont have workspace
currently.
--Drop any existing object with same name
DROP TABLE A1PK;
DROP SEQUENCE A1PK_seq;
-- Seq creation
CREATE SEQUENCE A1PK_seq START WITH 1 INCREMENT BY 1;
-- Provding req privileges
GRANT SELECT ON INFRA_OWNER.A1PK_seq TO PUBLIC;
--Root table creation
CREATE TABLE AIK
(PK_ID NUMBER PRIMARY KEY,
PK_NAME VARCHAR2(100));
--Drop existing object
DROP TABLE FK1;
--Create Child table
CREATE TABLE FK1
(
PK_ID NUMBER,
PK_ADD1 VARCHAR2(100),
PK_ADD2 VARCHAR2(100)
);
--Drop any existing constraints if any with same name
ALTER TABLE FK1
DROP CONSTRAINT FK_PK;
--Adding foreign key for child table
ALTER TABLE FK1
ADD CONSTRAINT FK_PK FOREIGN KEY(PK_ID) REFERENCES AIK(PK_ID);
CREATE OR REPLACE PROCEDURE insert_into_child_tables
(p_seg1 IN VARCHAR2,
p_seg2 IN VARCHAR2,
p_seg3 IN VARCHAR2,
p_root_val IN VARCHAR2)
AS
lv_long LONG;
lv_seq PLS_INTEGER;
BEGIN
SELECT INFRA_OWNER.A1PK_SEQ.NEXTVAL
INTO lv_seq
FROM DUAL;
INSERT
INTO INFRA_OWNER.AIK VALUES
(
lv_seq,
p_root_val
);
FOR I IN
(SELECT a1.OWNER,
a1.CONSTRAINT_NAME,
a1.TABLE_NAME
FROM ALL_CONSTRAINTS a1
WHERE A1.R_CONSTRAINT_NAME IN
(SELECT a2.CONSTRAINT_NAME
FROM ALL_CONSTRAINTS a2
WHERE a2.TABLE_NAME = 'AIK'
AND a2.constraint_type = 'P'
)
ORDER BY A1.TABLE_NAME
)
LOOP
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
EXECUTE IMMEDIATE 'INSERT INTO '||I.OWNER||'.'||I.TABLE_NAME||' VALUES ('||lv_seq||','||''''||lv_seg1||''''||','||''''||lv_seg2||''''||')';
END LOOP;
END;
Related
I'm trying to make a blog system of sort and I ran into a slight problem.
Simply put, there's 3 columns in my article table:
id SERIAL,
category VARCHAR FK,
category_id INT
id column is obviously the PK and it is used as a global identifier for all articles.
category column is well .. category.
category_id is used as a UNIQUE ID within a category so currently there is a UNIQUE(category, category_id) constraint in place.
However, I also want for category_id to auto-increment.
I want it so that every time I execute a query like
INSERT INTO article(category) VALUES ('stackoverflow');
I want the category_id column to be automatically be filled according to the latest category_id of the 'stackoverflow' category.
Achieving this in my logic code is quite easy. I just select latest num and insert +1 of that but that involves two separate queries.
I am looking for a SQL solution that can do all this in one query.
This has been asked many times and the general idea is bound to fail in a multi-user environment - and a blog system sounds like exactly such a case.
So the best answer is: Don't. Consider a different approach.
Drop the column category_id completely from your table - it does not store any information the other two columns (id, category) wouldn't store already.
Your id is a serial column and already auto-increments in a reliable fashion.
Auto increment SQL function
If you need some kind of category_id without gaps per category, generate it on the fly with row_number():
Serial numbers per group of rows for compound key
Concept
There are at least several ways to approach this. First one that comes to my mind:
Assign a value for category_id column inside a trigger executed for each row, by overwriting the input value from INSERT statement.
Action
Here's the SQL Fiddle to see the code in action
For a simple test, I'm creating article table holding categories and their id's that should be unique for each category. I have omitted constraint creation - that's not relevant to present the point.
create table article ( id serial, category varchar, category_id int )
Inserting some values for two distinct categories using generate_series() function to have an auto-increment already in place.
insert into article(category, category_id)
select 'stackoverflow', i from generate_series(1,1) i
union all
select 'stackexchange', i from generate_series(1,3) i
Creating a trigger function, that would select MAX(category_id) and increment its value by 1 for a category we're inserting a row with and then overwrite the value right before moving on with the actual INSERT to table (BEFORE INSERT trigger takes care of that).
CREATE OR REPLACE FUNCTION category_increment()
RETURNS trigger
LANGUAGE plpgsql
AS
$$
DECLARE
v_category_inc int := 0;
BEGIN
SELECT MAX(category_id) + 1 INTO v_category_inc FROM article WHERE category = NEW.category;
IF v_category_inc is null THEN
NEW.category_id := 1;
ELSE
NEW.category_id := v_category_inc;
END IF;
RETURN NEW;
END;
$$
Using the function as a trigger.
CREATE TRIGGER trg_category_increment
BEFORE INSERT ON article
FOR EACH ROW EXECUTE PROCEDURE category_increment()
Inserting some more values (post trigger appliance) for already existing categories and non-existing ones.
INSERT INTO article(category) VALUES
('stackoverflow'),
('stackexchange'),
('nonexisting');
Query used to select data:
select category, category_id From article order by 1,2
Result for initial inserts:
category category_id
stackexchange 1
stackexchange 2
stackexchange 3
stackoverflow 1
Result after final inserts:
category category_id
nonexisting 1
stackexchange 1
stackexchange 2
stackexchange 3
stackexchange 4
stackoverflow 1
stackoverflow 2
Postgresql uses sequences to achieve this; it's a different approach from what you are used to in MySQL. Take a look at http://www.postgresql.org/docs/current/static/sql-createsequence.html for complete reference.
Basically you create a sequence (a database object) by:
CREATE SEQUENCE serials;
And then when you want to add to your table you will have:
INSERT INTO mytable (name, id) VALUES ('The Name', NEXTVAL('serials')
I am unable to grasp the concept of the lookup table.
I am currently working on a project wherein I am using two tables.
The first table consists of two columns- name(varchar) and value(varchar).
The second table also has two rows- Result(varchar) and value(varchar).
Result is used to store the values which are obtained from a Java code. Whenever the Result of the Java code matches the name in the first table, I need to update the second table with the corresponding value in the first table.
Does using lookup table help in any way? If it does, can it be explained with an example?If not, is there any other way?
Just imagine a table person with a column GenderIsMale BIT. You can set this value to 1 (yes, it is a boy) or to 0 (no, a girl). This was easy in earlier days.
Now we have more categories. According to this link facebook offers more than 50 differing categories...
There the lookup-table comes into play: You create a table which has - as minium - a unique key and a value. In most cases this is an ID INT IDENTITY and a Content VARCHAR(100) NOT NULL. You can add more columns like Abbreviation or any other additional content (e.g. other languages or codes of external code systems read about mapping tables also) directly bound to this value.
The next step is, to take the GenderIsMale-column away and replace it with a
GenderID INT NOT NULL
CONSTRAINT FK_Person_GenderID FOREIGN KEY REFERENCES GenderLookUpTable(GenderID)
The person table will store the GenderID only, the related values are stored in the side table and can be looked up.
The simple lookup table is the basic construct of how to create a relational database model in min. 3.NF or BCNF (which should be a minium reuqirement for professional database design).
Whenever the Result of the Java code matches the name in the first
table, I need to update the second table with the corresponding value
in the first table.
That's a perfect use case for database trigger, which can be used to perform various things when a change (insert, update, delete) happens in a table.
Assuming you're inserting the value of your Java calculations to your (result, value) table (let's call it foo, and the other table is bar), you can write a trigger that replaces the value being written with the value from the other table. Example given for Postgres, if using another db refer to your particular RDBMS manual to see the syntax.
CREATE FUNCTION get_value_from_lookup_table() RETURNS trigger AS $$
BEGIN
IF EXISTS (SELECT 1 FROM bar WHERE name = NEW.result) THEN
RETURN SELECT name, value FROM bar WHERE name = NEW.result;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER lookup_value
INSTEAD OF INSERT ON foo
FOR EACH ROW
EXECUTE PROCEDURE get_value_from_lookup_table();
Every time an INSERT is done on foo, a check is done to see if a row exists in bar where name=result. If so, that row is inserted, otherwise the insert goes on normally. That's the basic gist of it. The actual solution depends on table constraints, whether you need to handle inserts and updates, etc.
I am a beginner PLSQL user, and I have what might be a rather simple question.
I have created the following SQL Function, which returns the created date of the process whose corporate ID matches the corporate ID that I have given it. I have this connected to my JDBC, and it returns values just fine.
However, I just realized I overlooked an important issue--it's entirely possible that more than one process will have a corporate ID that matches the ID value I've inputted, and in cases like that I will need to be able to access all of the created date values where the IDs return a match.
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN INT
AS
p_date process.date_created%TYPE;
BEGIN
SELECT process.date_created
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
RETURN p_id;
END FUNCTION_1;
/
Is there a way that I can modify my function to return multiple rows from the same column, and then call that function to return some sort of array using JDBC? Or, if that isn't possible, is there a way I can return what I need using PLSQL procedures or just plain SQL combined with JDBC? I've looked through other questions here, but none seemed to be about quite what I need to know.
Thanks to anyone who can help!
you need make some changes in your function. on java side it will be simple select
you need change type of your function from int to collection
read about the table functions here Table Functions
user oracle table() function to convert result of your function to table
it let you use your function in queries. read more about the syntax here: Table Collections: Examples
here the example how to call your function from java:
select t.column_value as process_id
from table(FUNCTION_1(1)) t
--result
PROCESS_ID
1 1
2 2
--we need create new type - table of integers
CREATE OR REPLACE TYPE t_process_ids IS TABLE OF int;
--and make changes in function
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN t_process_ids
AS
l_ids t_process_ids := t_process_ids();
BEGIN
--here I populated result of select into the local variables
SELECT process.id
bulk collect into l_ids
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
--return the local var
return l_ids;
END FUNCTION_1;
--the script that I used for testing
create table process(id int, corporate_id int, date_created date);
insert into process values(1, 1, sysdate);
insert into process values(2, 1, sysdate);
insert into process values(3, 2, sysdate);
I have 3-4 tables in my database which I want to track the changes for.
I am mainly concerned about updates.
Whenever updates happen, I want to store previous entry (value or complete row) in audit table.
Basic columns I was thinking of are as following:
AuditId, TableName, PK1, PK2, PK3, PKVal1, PKVal2, PKVal3, UpdateType, PrevEntryJSON
JSON will be of format: Key:Value and I preferred to go with it as columns keep on changing and I want to keep all values even if they don't change.
Other option is to remove JSON with 100's of columns which will have names same as different columns (cumulative of all tables).
I wanted to hear people's views on this. How could I improve on it and what issues could I face?
Going through triggers might not be preferable way but I am open to it.
Thanks,
I have seen a very effective implementation of this which goes as follows:
TABLE audit_entry (
audit_entry_id INTEGER PRIMARY KEY,
audit_entry_type VARCHAR2(10) NOT NULL,
-- ^^ stores 'INSERT' / 'UPDATE' -- / 'DELETE'
table_name VARCHAR2(30) NOT NULL,
-- ^^ stores the name of the table that is changed
column_name VARCHAR2(30) NOT NULL,
-- ^^ stores the name of the column that is changed
primary_key_id INTEGER NOT NULL,
-- ^^ Primary key ID to identify the row that is changed
-- Below are the actual values that are changed.
-- If the changed column is a foreign key ID then
-- below columns tell you which is new and which is old
old_id INTEGER,
new_id INTEGER,
-- If the changed column is of any other numeric type,
-- store the old and new values here.
-- Modify the precision and scale of NUMBER as per your
-- choice.
old_number NUMBER(18,2),
new_number NUMBER(18,2),
-- If the changed column is of date type, with or without
-- time information, store it here.
old_ts TIMESTAMP,
new_ts TIMESTAMP,
-- If the changed column is of VARCHAR2 type,
-- store it here.
old_varchar VARCHAR2(2000),
new_varchar VARCHAR2(2000),
...
... -- Any other columns to store data of other types,
... -- e.g., blob, xmldata, etc.
...
)
And we create a simple sequence to give us new incremental integer value for audit_entry_id:
CREATE SEQUENCE audit_entry_id_seq;
The beauty of a table like audit_entry is that you can store information about all types of DMLs- INSERT, UPDATE and DELETE in the same place.
For e.g., for insert, keep the old_* columns null and populate the new_* with your values.
For updates, populate both old_* and new_* columns whenever they are changed.
For delete, just populate the old_* columns and keep the new_* null.
And of course, enter the appropriate value for audit_entry_type. ;0)
Then, for example, you have a table like follows:
TABLE emp (
empno INTEGER,
ename VARCHAR2(100) NOT NULL,
date_of_birth DATE,
salary NUMBER(18,2) NOT NULL,
deptno INTEGER -- FOREIGN KEY to, say, department
...
... -- Any other columns that you may fancy.
...
)
Just create a trigger on this table as follows:
CREATE OR REPLACE TRIGGER emp_rbiud
-- rbiud means Row level, Before Insert, Update, Delete
BEFORE INSERT OR UPDATE OR DELETE
ON emp
REFERENCING NEW AS NEW OLD AS OLD
DECLARE
-- any variable declarations that deem fit.
BEGIN
WHEN INSERTING THEN
-- Of course, you will insert empno.
-- Let's populate other columns.
-- As emp.ename is a not null column,
-- let's insert the audit entry value directly.
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
new_varchar)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'ENAME',
:new.empno,
:new.ename);
-- Now, as date_of_birth may contain null, we do:
IF :new.date_of_birth IS NOT NULL THEN
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
new_ts)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'DATE_OF_BIRTH',
:new.empno,
:new.date_of_birth);
END IF;
-- Similarly, code DML statements for auditing other values
-- as per your requirements.
WHEN UPDATING THEN
-- This is a tricky one.
-- You must check which columns have been updated before you
-- hurry into auditing their information.
IF :old.ename != :new.ename THEN
INSERT INTO audit_entry(audit_entry_id,
audit_entry_type,
table_name,
column_name,
primary_key,
old_varchar,
new_varchar)
VALUES(audit_entry_id_seq.nextval,
'INSERT',
'EMP',
'ENAME',
:new.empno,
:old.ename,
:new.ename);
END IF;
-- Code further DML statements in similar fashion for other
-- columns as per your requirement.
WHEN DELETING THEN
-- By now you must have got the idea about how to go about this.
-- ;0)
END;
/
Just one word of caution: be selective with what tables and columns you choose to audit, because anyways, you this table will have a huge number of rows. SELECT statements on this table will be slower than you may expect.
I would really love to see any other sort of implementation here, as it would be a good learning experience. Hope your question gets more answers, as this is the best implementation of an audit table that I have seen and I'm still looking for ways to make it better.
This the syntax I use for creating a temporary table:
create temp table tmpTable (id bigint not null, primary key (id)) on commit drop;
I know this means that at the end of each transaction, this table will be dropped.
My question is, if two or more threads on the same session create and insert values into a temporary table, will they each get their own instance or is the temporary instance shared across the session? If it's shared, is there a way to make it local per thread?
Thanks
Netta
Temporary tables are visible to all operations in the same session. So you cannot create a temporary table of the same name in the same session before you drop the one that exists (commit the transaction in your case).
You may want to use:
CREATE TEMP TABLE tmptbl IF NOT EXISTS ...
More about CREATE TABLE in the manual.
Unique temp tables
To make the temp table local per "thread" (in the same session) you need to use unique table names. One way would be to use an unbound SEQUENCE and dynamic SQL - in a procedural language like plpgsql or in a DO statement (which is basically the same without storing a function.
Run one:
CREATE SEQUENCE myseq;
Use:
DO $$
BEGIN
EXECUTE 'CREATE TABLE tmp' || nextval('myseq') ||'(id int)';
END;
$$
To know the latest table name:
SELECT 'tmp' || currval('myseq');
Or put it all into a plpgsql function and return the table or reuse the table name.
All further SQL commands have to be executed dynamically, though, as plain SQL statements operate with hard coded identifiers. So, it is probably best, to put it all into a plpgsql function.
Unique ID to use same temp table
Another possible solution could be to use the same temp table for all threads in the same session and add a column thread_id to the table. Be sure to index the column, if you make heavy use of the feature. Then use a unique thread_id per thread (in the same session).
Once only:
CREATE SEQUENCE myseq;
Once per thread:
CREATE TEMP TABLE tmptbl(thread_id int, col1 int) IF NOT EXISTS;
my_id := nextval('myseq'); -- in plpgsql
-- else find another way to assign unique id per thread
SQL:
INSERT INTO tmptbl(thread_id, col1) VALUES
(my_id, 2), (my_id, 3), (my_id, 4);
SELECT * FROM tmptbl WHERE thread_id = my_id;