I have updatable view on PostgreSQL server.
Update query works fine when I execute it from pgAnmin3 console, but when I try to update this view with ResultSet.updateRow() method, I get the following error:
org.postgresql.util.PSQLException: No primary key found for table
I guess I can't specify primary key for view.
Can I specify key columns for ResultSet.updateRow() method in my client application? Or can I specify a WHERE clause for ResultSet.updateRow() method?
Here are my tables
CREATE TABLE fin.t_year
(
id serial NOT NULL,
date_begin date NOT NULL,
date_end date NOT NULL,
year_name character varying(128),
CONSTRAINT "PK_year" PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE fin.t_period
(
id serial NOT NULL,
id_year integer NOT NULL,
per_begin date NOT NULL,
per_end date NOT NULL,
per_name character varying(256),
CONSTRAINT "PK_period" PRIMARY KEY (id),
CONSTRAINT "FK_period_year" FOREIGN KEY (id_year)
REFERENCES fin.t_year (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
CREATE VIEW fin.vi_period AS
SELECT per.id,
per.per_begin AS "Begin",
per.per_end AS "End",
per.per_name AS "Name",
y.year_name AS "Year"
FROM fin.t_period per
JOIN fin.t_year y ON y.id = per.id_year;
CREATE OR REPLACE FUNCTION fin.tgfn_vi_period_update()
RETURNS trigger AS
$BODY$
DECLARE
id_row INTEGER;
id_curr INTEGER;
result RECORD;
BEGIN
id_curr = NEW.id;
-- Replace text identifier with integer primary key
IF NEW."Year" IS NOT NULL THEN
SELECT id INTO id_row
FROM fin.t_year
WHERE year_name = NEW."Year";
UPDATE fin.t_period SET id_year = id_row
WHERE id = id_curr;
END IF;
IF NEW."Begin" IS NOT NULL THEN
UPDATE fin.t_period SET per_begin = NEW."Begin"
WHERE id = id_curr;
END IF;
IF NEW."End" IS NOT NULL THEN
UPDATE fin.t_period SET per_end = NEW."End"
WHERE id = id_curr;
END IF;
IF NEW."Name" IS NOT NULL THEN
UPDATE fin.t_period SET per_name = NEW."Name"
WHERE id = id_curr;
END IF;
SELECT * INTO result FROM fin.vi_period WHERE id = id_curr;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This insert statement works fine
UPDATE fin.vi_period SET "Year" = 'new_year_name' WHERE id = 10;
But the problem with this java code
statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = statement.ExecuteQuery("SELECT * FROM fin.vi_period;");
rs.absolute(pos + 1);
rs.updateString("new_year_name");
rs.updateRow();
The problem is that you are querying a view, and that view doesn't have a primary key (I am not sure if that is even possible with PostgreSQL, but most database don't support that). The JDBC driver requires a primary key to be able to make the result set updatable.
In other words: you cannot update this view through the result set. You either need to use an explicit UPDATE statement, or do this directly on the underlying table, not through the view.
Related
I am working on java with JDBC connections and trying to perform DDL commands.
Here i had a doubt about one particular flow, can that be possible? if yes, can you explain me how and what to do with example.
I am trying to select data from item table containing item_id, sale_price, description, barcode columns and want to update barcode data for item_id = 9 and insert into item_duplicate table. With out updating the item table. But item_dupliacte table should contain the updated value for barcode.
my item_duplicate table
item table
MERGE item_duplicate AS D
USING item AS I
ON (D.item_id = I.item_id )
WHEN MATCHED
THEN UPDATE set D.part_no='new part'
WHEN NOT MATCHED BY D
THEN
INSERT (item_id,part_no,sale_price,description,barcode)
SELECT i.ITEM_ID,i.PART_NO,i.SALE_PRICE,i.DESCRIPTION,b.BARCODE
FROM item i
JOIN item_barcode b
ON b.ITEM_ID = i.ITEM_ID
WHERE i.ITEM_ID = ?
WHEN NOT MATCHED BY I
THEN DELETE;
A simple insert into select from.
CREATE TABLE dbo.TEST
(
item_id INT NOT NULL
, barcode VARCHAR (20) NULL
, sale_price DECIMAL (14, 2) NULL
, description VARCHAR (100) NULL
, PRIMARY KEY (item_id)
)
CREATE TABLE dbo.TEST_COPY
(
item_id INT NOT NULL
, barcode VARCHAR (20) NULL
, sale_price DECIMAL (14, 2) NULL
, description VARCHAR (100) NULL
, PRIMARY KEY (item_id)
)
INSERT INTO TEST_COPY (item_id, barcode, sale_price, description) SELECT item_id, '9999' as barcode, sale_price, description FROM TEST WHERE item_id = 9
I want to delete a row. My TABLES are 'goal' and 'contribute'.It shows above error.
Please tell immediately what's the problem.
Table structure is ,
//goal TABLE
CREATE TABLE `goal` (`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(555) NOT NULL,
`target_value` double NOT NULL,
`target_date` date NOT NULL,
`created_date` datetime NOT NULL,
`status` int(11) NOT NULL,
PRIMARY KEY (`id`))
ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8
COLLATE=utf8mb4_0900_ai_ci
//Contribute TABLE
CREATE TABLE `contribute`
(`id` int(11) NOT NULL AUTO_INCREMENT,`goal` int(11) NOT NULL,
`amount` double NOT NULL, `date` date NOT NULL,
PRIMARY KEY (`id`),KEY `idgoal_idx` (`goal`),
CONSTRAINT `fk` FOREIGN KEY (`goal`) REFERENCES `goal` (`id`))
ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8m
COLLATE=utf8mb4_0900_ai_ci
//Code
public static boolean delete(int id) {
try {
Connection con = DB.getConnection();
String sql = "ALTER TABLE 'goal' ADD CONSTRAINT 'fk' FOREIGN
KEY('goal') REFERENCES 'goal' ('id') ON DELETE CASCADE ";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, id);
ps.executeUpdate();
return true;
} catch (Exception ex) {
ex.printStackTrace();
}
return false;
}
In my oppinion your code is bad. You are executing this
String sql = "ALTER TABLE 'goal' ADD CONSTRAINT 'fk' FOREIGN
KEY('goal') REFERENCES 'goal' ('id') ON DELETE CASCADE ";
every time you invoke this method. You should add constraints when creating the tables.
In order to delete a row from some table i suggest you to create a database procedure or function which does it and invoke it through java.
String sql = "{? = call your_schema.your_package.delete_object(?)}";
try (CallableStatement cs = con.createCallableStatement(sql)) {
cs.setInt(1, id);
cs.executeQuery();
}
This is just an example but i think this is the correct way to do it. In this procedure you accept your ID as parameter and delete the row there.
Here's two problem:
1.your sql doesn't contain any parameter keyword : ?
You use java set parameter ps.setInt(1, id), but your sql doesn't contain keyword ?
Example for parameter using ? :
PreparedStatement p = con.prepareStatement("select * from xxxTable where xxx = ?");
p.setString(1, xxx);
More details you can learn from mysql - java.sql.SQLException Parameter index out of range (1 > number of parameters, which is 0) - Stack Overflow
2.Your SQL maybe wrong
ALTER TABLE 'goal' ADD CONSTRAINT 'fk' FOREIGN
KEY('goal') REFERENCES 'goal' ('id') ON DELETE CASCADE
'goal' table doesn't contain 'goal' column.
it should be changed like:
ALTER TABLE `contribute`
ADD FOREIGN KEY (`goal`) REFERENCES `goal`(`id`) ON DELETE CASCADE ;
SQL Fiddle Demo Link
We got strange non-permanent error: MySQLIntegrityConstraintViolationException inside java transaction.
Imagine next code:
sql:
CREATE TABLE test (
ruleID INT(11) NOT NULL AUTO_INCREMENT,
name varchar(250),
PRIMARY KEY (ruleID)
) engine=InnoDB;
CREATE TABLE test2 (
ruleID INT(11) NOT NULL,
name varchar(250),
PRIMARY KEY (ruleID),
CONSTRAINT _FK1 FOREIGN KEY (ruleID)
REFERENCES test (ruleID)
ON DELETE CASCADE
) engine=InnoDB;
CREATE PROCEDURE proc1(
inout p_ruleID INT (11),
p_name varchar(250)
)
proc: begin
IF (p_ruleID < 1) THEN
INSERT INTO test (ruleID, name) values (null, p_name);
SELECT LAST_INSERT_ID() INTO p_ruleID;
else
UPDATE test
SET name = p_name
WHERE ruleID = p_ruleID;
SELECT p_ruleID;
END IF;
END //
CREATE PROCEDURE proc2(
p_ruleID INT (11),
p_name varchar(250)
)
BEGIN
DELETE FROM test2
WHERE ruleID = p_ruleID;
INSERT INTO test2(ruleID, name)
VALUES (p_ruleID, p_name);
END //
java:
Connection conn;
int id;
String name;
String subName;
...
conn.setAutoCommit(false);
id=1;
CallableStatement cs1 = conn.prepareCall("{call proc1(?,?)}");
cs1.setInt(1, id);
cs1.setString(2, name);
cs1.registerOutParameter(1, java.sql.Types.INTEGER);
cs1.execute();
int parentID = cs1.getInt(1);
cs1.close();
cs1 = conn.prepareCall("{call proc2(?,?)}");
cs1.setInt(1, parentID);
cs1.setString(2, subName);
cs1.execute();
...
So once during the "cs1.execute()" for "proc2" we got exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row:
a foreign key constraint fails (test2, CONSTRAINT '_FK1' FOREIGN KEY ('ruleID') REFERENCES 'test' ('ruleID') ON DELETE CASCADE)
We got it only once and can't repeat it.
One "guru" said - java transaction, performed by Connection, don't garanty the visibility of results between CallableStatementS.
Is it true? Or may be the truth is out there?
Right now I have created a sequence and a trigger to auto increment the ID value like this:
CREATE OR REPLACE TRIGGER "RTH"."TBL_USER_TRIGGER"
BEFORE INSERT ON TBL_USER
FOR EACH ROW
BEGIN
SELECT TBL_USER_SEQ.nextval
INTO :new.USR_ID
FROM dual;
END;
ALTER TRIGGER "RTH"."TBL_USER_TRIGGER" ENABLE;
Let say I have three rows:
User ID FIRSTNAME LASTNAME
====================================
1 John smith
2 James smith
3 Pat smith
When I delete the first row(1) I want the ID values to auto correct itself to correct numbers so that second row ID values becomes 1 and third row ID values becomes 2
Is it possible in oracle? or do I have do it through code as I am using Java to insert records into table when user submits a form.
Java DAO class:
public class RegistrationDAO {
public void insert(User user) {
try {
Connection con = DBConnection.getConnection();
String query = "insert into TBL_USER(USR_FIRST_NAME,USR_LST_NAME,USR_PRIMARY_EMAIL,USR_PASSWORD) values(?,?,?,?)";
PreparedStatement pst = con.prepareStatement(query);
pst.setString(1, user.getFirstName());
pst.setString(2, user.getLastName());
pst.setString(3, user.getEmail());
pst.setString(4, user.getPassword());
pst.executeUpdate();
} catch (Exception e) {
System.out.println("####Record insertion error in Registration DAO####");
System.out.println(e);
}
}
}
The simple answer is: "No, you don't want to do that." The purpose of an id is to uniquely identify each row. A sequential id also has the feature that it provides insertion order. It is not intended to change over time. Row 1 is Row 1 is Row 1.
If you want ordering, then declare the id to be the primary key and use a query such as this:
select t.*, row_number() over (order by usr_id) as seqnum
from t;
Ideally, you should not be changing USER ID values. But still if there is requirement. Then below are the steps.
Create a procedure to reset the sequence TBL_USER_SEQ. This is needed as you are resetting the USER ID values. So after resetting, whenever you insert new record, Sequence should start with proper value ( i.e. MAX USER_ID value of the table). So that there will be no gap in USER_ID
Write Delete Trigger on table which will adjust USER_ID values.
Procedure to reset sequence
CREATE OR REPLACE PROCEDURE reset_sequence_p (p_seq IN VARCHAR2, p_new_seq NUMBER)
AS
l_value NUMBER;
BEGIN
-- Select the next value of the sequence
EXECUTE IMMEDIATE 'SELECT ' || p_seq || '.NEXTVAL FROM DUAL' INTO l_value;
-- Alter the sequnce to increment by the difference
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_seq || ' INCREMENT BY ' || ( p_new_seq - l_value ) ;
-- Increment to next value after diference
EXECUTE IMMEDIATE 'SELECT ' || p_seq || '.NEXTVAL FROM DUAL' INTO l_value;
-- Set the increment back to 1
EXECUTE IMMEDIATE 'ALTER SEQUENCE ' || p_seq || ' INCREMENT BY ' || 1 ;
END reset_sequence_p;
Delete Trigger
CREATE OR REPLACE TRIGGER "RTH"."TBL_USER_TRIG_DEL"
BEFORE DELETE
ON TBL_USER
FOR EACH ROW
DECLARE
v_new_user_id_seq NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
-- Update USER_ID value for all remaining records greater than USER_ID that got deleted.
UPDATE TBL_USER
SET USER_ID = USER_ID - 1
WHERE USER_ID > :old.USER_ID;
-- Retrieve max USER_ID available in table.
SELECT MAX (user_id) INTO v_new_user_id_seq FROM TBL_USER;
-- Call procedure to reset sequence
reset_sequence_p ('TBL_USER_SEQ', v_new_user_id_seq);
COMMIT;
END;
NOTE : Make sure that Primary key on table is disabled before execution of Delete statement.
I hope this helps.
I wrote MySQL StoredProcedure to create and return new ID for each table value, however, it gets wrong value on last_insert_id() from MySQL WorkBench and Java application.
This procedure will be called from multiple sessions.
CALL `GET_NEW_ID`('test', #test);
select #test;
It gives me "141215000000" and this means last_insert_id() returns 0 all the time.
I see it correctly inserts new data into seq_data as supposed though.
CREATE PROCEDURE `GET_NEW_ID`(IN V_TABLE VARCHAR(10), OUT V_ID VARCHAR(12))
BEGIN
INSERT INTO seq_data ( id, `name`, `stat_date`)
SELECT IFNULL(MAX(id), 0)+1, V_TABLE, DATE_FORMAT(NOW(),'%Y%m%d') FROM seq_data WHERE name = V_TABLE AND stat_date = DATE_FORMAT(NOW(),'%Y%m%d');
SET V_ID = concat(DATE_FORMAT(NOW(),'%y%m%d'),LPAD(LAST_INSERT_ID(), 6, '0'));
END
Table looks like this.
CREATE TABLE `seq_data` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(20) COLLATE utf8_bin NOT NULL,
`stat_date` varchar(8) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`,`name`,`stat_date`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
My goal is like...
CALL `GET_NEW_ID`('test', #test);
select #test;
return 141215000001
CALL `GET_NEW_ID`('test', #test);
select #test;
return 141215000002
CALL `GET_NEW_ID`('hello', #test);
select #test;
return 141215000001
As stated in the MySQL documentation, LAST_INSERT_ID() returns a BIGINT (64-bit) value representing the first automatically generated value that was set for an AUTO_INCREMENT column by the most recently executed INSERT statement to affect such a column.
In your case, you are inserting the id, so an AUTO_INCREMENT value is not generated, thus LAST_INSERT_ID returns 0.
You may try something like:
CREATE PROCEDURE `GET_NEW_ID`(IN V_TABLE VARCHAR(10), OUT V_ID VARCHAR(12))
BEGIN
INSERT INTO seq_data (`name`, `stat_date`)
SELECT V_TABLE, DATE_FORMAT(NOW(),'%Y%m%d') FROM seq_data WHERE name = V_TABLE AND stat_date = DATE_FORMAT(NOW(),'%Y%m%d');
SET V_ID = concat(DATE_FORMAT(NOW(),'%y%m%d'),LPAD(LAST_INSERT_ID(), 6, '0'));
END