I'm trying to make a INSERT INTO with a SELECT and values, but no works.
TABLE SOURCE:
CREATE TABLE "MICV_PRE"."TS$SEQUENCES"
(
"ID_NODE" NUMBER DEFAULT 1 NOT NULL ENABLE,
"ID_TASK" NUMBER DEFAULT 1 NOT NULL ENABLE,
"ID_DOCUMENT" NUMBER DEFAULT 1 NOT NULL ENABLE,
"ID_WORD" NUMBER DEFAULT 1 NOT NULL ENABLE,
"ID_TEAM" NUMBER DEFAULT '1' NOT NULL ENABLE)
TABLE TO MODIFY:
CREATE TABLE TS$SEQUENCES_NEW(
"ID_CODE" VARCHAR(255 CHAR) NOT NULL ENABLE,
"CODE_SUBSEQUENCE" VARCHAR2(255 CHAR) NOT NULL ENABLE,
"VALUE" NUMBER(10,0) NOT NULL ENABLE
);
table source:
id_task | id_node | id_word
10 | 20 | 30
table to modify:
id_code | code_subsequence | value
"id_task" | "empty" | 10
"id_node" | "empty" | 20
"id_word" | "empty" | 30
So, the SQL you tried is this:
SQL> INSERT INTO TS$SEQUENCES_NEW
2 SELECT TS$SEQUENCES.ID_TASK AS "VALUE", 'ID_TASK' AS "ID_CODE", 'VACIO' AS "CODE_SUBSEQUENCE"
3 FROM TS$SEQUENCES
4 /
*
ERROR at line 2:
ORA-01722: invalid number
SQL>
This fails because the datatypes in the projection of the query don't match the order of the columns in the table. So either change the SELECT statement or define the order in the INSERT clause:
SQL> INSERT INTO TS$SEQUENCES_NEW ("VALUE", "ID_CODE","CODE_SUBSEQUENCE" )
2 SELECT TS$SEQUENCES.ID_TASK AS "VALUE", 'ID_TASK' AS "ID_CODE", 'VACIO' AS "CODE_SUBSEQUENCE"
3 FROM TS$SEQUENCES
4 /
1 row created.
SQL>
Try this:
INSERT INTO TS$SEQUENCES_NEW (VALUE, ID_CODE, CODE_SUBSEQUENCE)
SELECT TS$SEQUENCES.ID_TASK AS "VALUE", 'ID_TASK' AS "ID_CODE", 'VACIO' AS "CODE_SUBSEQUENCE"
FROM TS$SEQUENCES;
Related
I created a table in PostgreSQL and Oracle as
CREATE TABLE temp(
seqnr smallint NOT NULL,
defn_id int not null,
attr_id int not null,
input CHAR(50) NOT NULL,
CONSTRAINT pk_id PRIMARY KEY (defn_id, attr_id, seqnr)
);
This temp table has primary key as (defn_id,attr_id,seqnr) as a whole!
Then I inserted the record in the temp table as
INSERT INTO temp(seqnr,defn_id,attr_id,input)
VALUES (1,100,100,'test1');
INSERT INTO temp(seqnr,defn_id,attr_id,input)
VALUES (2,100,100,'test2');
INSERT INTO temp(seqnr,defn_id,attr_id,input)
VALUES (3,100,100,'test3');
INSERT INTO temp(seqnr,defn_id,attr_id,input)
VALUES (4,100,100,'test4');
INSERT INTO temp(seqnr,defn_id,attr_id,input)
VALUES (5,100,100,'test5');
in both Oracle and Postgres!
The table now contains:
seqnr | defn_id | attr_id | input
1 | 100 | 100 | test1
2 | 100 | 100 | test2
3 | 100 | 100 | test3
4 | 100 | 100 | test4
5 | 100 | 100 | test5
When I run the command:
UPDATE temp SET seqnr=seqnr+1
WHERE defn_id = 100 AND attr_id = 100 AND seqnr >= 1;
In case of ORACLE it is Updating 5 Rows and the O/p is
seqnr | defn_id | attr_id | input
2 | 100 | 100 | test1
3 | 100 | 100 | test2
4 | 100 | 100 | test3
5 | 100 | 100 | test4
6 | 100 | 100 | test5
But in case of PostgreSQL it is giving an error!
DETAIL: Key (defn_id, attr_id, seqnr)=(100, 100, 2) already exists.
Why does this happen and how can I replicate the same result in Postgres as Oracle?
Or how can the same result be achieved in Postgres without any errors?
UNIQUE an PRIMARY KEY constraints are checked immediately (for each row) unless they are defined DEFERRABLE - which is the solution you demand.
ALTER TABLE temp
DROP CONSTRAINT pk_id
, ADD CONSTRAINT pk_id PRIMARY KEY (defn_id, attr_id, seqnr) DEFERRABLE
;
Then your UPDATE just works.
db<>fiddle here
This comes at a cost, though. The manual:
Note that deferrable constraints cannot be used as conflict
arbitrators in an INSERT statement that includes an ON CONFLICT DO UPDATE clause.
And for FOREIGN KEY constraints:
The referenced columns must be the columns of a non-deferrable unique
or primary key constraint in the referenced table.
And:
When a UNIQUE or PRIMARY KEY constraint is not deferrable,
PostgreSQL checks for uniqueness immediately whenever a row is
inserted or modified. The SQL standard says that uniqueness should be
enforced only at the end of the statement; this makes a difference
when, for example, a single command updates multiple key values. To
obtain standard-compliant behavior, declare the constraint as
DEFERRABLE but not deferred (i.e., INITIALLY IMMEDIATE). Be aware
that this can be significantly slower than immediate uniqueness
checking.
See:
Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?
I would avoid a DEFERRABLE PK if at all possible. Maybe you can work around the demonstrated problem? This usually works:
UPDATE temp t
SET seqnr = t.seqnr + 1
FROM (
SELECT defn_id, attr_id, seqnr
FROM temp
WHERE defn_id = 100 AND attr_id = 100 AND seqnr >= 1
ORDER BY defn_id, attr_id, seqnr DESC
) o
WHERE (t.defn_id, t.attr_id, t.seqnr)
= (o.defn_id, o.attr_id, o.seqnr);
db<>fiddle here
But there are no guarantees as ORDER BY is not specified for UPDATE in Postgres.
Related:
UPDATE with ORDER BY
By "disjoint" I mean mutually exclusive sets of ID values. No overlap between both tables.
For example, the sequence generator for the id column on both tables should work in conjunction to make sure they are always disjoint. I am not sure if this is possible. So, I thought I would just ask here.
Table A
id
name
0
abc
1
cad
2
pad
3
ial
Table B
id
name
40
pal
50
sal
A different hack-around:
CREATE TABLE odd(
id INTEGER GENERATED ALWAYS AS IDENTITY (START 1 INCREMENT 2)
, val integer
);
CREATE TABLE even(
id INTEGER GENERATED ALWAYS AS IDENTITY (START 2 INCREMENT 2)
, val integer
);
INSERT INTO odd (val)
SELECT GENERATE_SERIES(1,10);
INSERT INTO even (val)
SELECT GENERATE_SERIES(1,20);
SELECT * FROM odd;
SELECT * FROM even;
Result:
CREATE TABLE
CREATE TABLE
INSERT 0 10
INSERT 0 20
id | val
----+-----
1 | 1
3 | 2
5 | 3
7 | 4
9 | 5
11 | 6
13 | 7
15 | 8
17 | 9
19 | 10
(10 rows)
id | val
----+-----
2 | 1
4 | 2
6 | 3
8 | 4
10 | 5
12 | 6
14 | 7
16 | 8
18 | 9
20 | 10
22 | 11
24 | 12
26 | 13
28 | 14
30 | 15
32 | 16
34 | 17
36 | 18
38 | 19
40 | 20
(20 rows)
A very simple way is to share the same SEQUENCE:
CREATE TABLE a (
id serial PRIMARY KEY
, name text
);
CREATE TABLE b (
id int PRIMARY KEY
, name text
);
SELECT pg_get_serial_sequence('a', 'id'); -- 'public.a_id_seq'
ALTER TABLE b ALTER COLUMN id SET DEFAULT nextval('public.a_id_seq'); -- !
db<>fiddle here
This way, table a "owns" the sequence, while table b draws from the same source. You can also create an independent SEQUENCE if you prefer.
Note: this only guarantees mutually exclusive new IDs (even under concurrent write load) while you don't override default values and also don't update them later.
Related:
Creating a PostgreSQL sequence to a field (which is not the ID of the record)
Safely rename tables using serial primary key columns
Auto increment table column
PostgreSQL next value of the sequences?
Welcome to the painful world of inter-table constraints or assertions - this is something that ISO SQL and pretty much every RDBMS out there does not handle ergonomically...
(While ISO SQL does describe both deferred-constraints and database-wide assertions, as far as I know only PostgreSQL implements deferred-constraints, and no production-quality RDBMS supports database-wide assertions).
One approach is to have a third-table which is the only table with SERIAL (aka IDENTITY aka AUTO_INCREMENT) with a discriminator column which combined forms the table's primary-key, then the other two tables have an FK constraint to that PK - but they'll also need the same discriminator column (enforced with a CHECK constraint), but you will never need to reference that column in most queries.
As your post doesn't tell us what the real table-names are, I'll use my own.
Something like this:
CREATE TABLE postIds (
postId int NOT NULL SERIAL,
postType char(1) NOT NULL, /* This is the discriminator column. It can only contain ONLY either 'S' or 'G' which indicates which table contains the rest of the data */
CONSTRAINT PK_postIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType IN ( 'S', 'G' ) )
);
CREATE TABLE shitposts (
postId int NOT NULL,
postType char(1) DEFAULT('S'),
foobar nvarchar(255) NULL,
etc int NOT NULL,
CONSTRAINT PK_shitpostIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType = 'S' ),
CONSTRAINT FK_shitpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
);
CREATE TABLE goldposts (
postId int NOT NULL,
postType char(1) DEFAULT('G'),
foobar nvarchar(255) NULL,
etc int NOT NULL,
CONSTRAINT PK_goldpostIds PRIMARY KEY ( postId, postType ),
CONSTRAINT CK_type CHECK ( postType = 'G' ),
CONSTRAINT FK_goldpost_ids FOREIGN KEY ( postId, postType ) REFERENCES postIds ( postId, postType )
)
With this design, it is impossible for any row in shitposts to share a postId value with a post in goldposts and vice-versa.
However it is possible for a row to exist in postIds without having any row in both goldposts and shitposts. Fortunately, as you are using PostgreSQL you could add a new FK constraint from postIds to both goldposts and shitposts but use it with deferred-constraints.
I'm learning PL/SQL right now and I have a doubt.
I have created the following table called tbProducts:
CREATE TABLE tbProducts (
nIDProduct NUMBER(2) NOT NULL,
vDescription VARCHAR2(20 CHAR),
nQuantity NUMBER(3),
nPrice NUMBER(6,2),
dLastDate DATE)
And I have inserted some values so the table is like this:
nIDProduct | vDescription | nQuantity | nPrice | dLastDate
1 | 'Hammer' | 50 | 3.25 | 13-MAY-2021
2 | 'Nails' | 100 | 0.75 | 28-AUG-2021
3 | 'Screws' | 250 | 0.16 | 21-JUL-2021
Now what I'm looking for is a boolean variable that can be called bUpdate that returns FALSE if today's date (26-AUG-2021) is greater than dLastDate and returns TRUE if it's less or equal so the table would look like this:
nIDProduct | vDescription | nQuantity | nPrice | dLastDate | bUpdate
1 | 'Hammer' | 50 | 3.25 | 13-MAY-2021 | FALSE
2 | 'Nails' | 100 | 0.75 | 28-AUG-2021 | TRUE
3 | 'Screws' | 250 | 0.16 | 21-JUL-2021 | FALSE
I am trying doing the following:
DECLARE
bUpdate BOOLEAN;
BEGIN
SELECT t.*, bUpdate(
IF SYSDATE > dLastDate THEN
bUpdate := FALSE;
ELSE
bUpdate := TRUE;
END IF
FROM tbProducts t
;
END;
I get an error saying that a FROM was expected after the SELECT statement.
Since I'm still learning I don't know what it's wrong in this statement, could someone help me? Is there a way to do it with a CURSOR too?
Thank you all!
What you try wouldn't work. If you want an additional column, you need to add the column to the table with an ALTER TABLE command - but in this case you're adding a non-deterministic expression and that cannot be added as a virtual column.
The easiest way to achieve what you want is to create a view on top of the table with the case statement as illustrated below:
CREATE TABLE tbproducts (
nidproduct NUMBER(2) NOT NULL,
vdescription VARCHAR2(20 CHAR),
nquantity NUMBER(3),
nprice NUMBER(6,2),
dlastdate DATE);
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (1,'Hammer', 50,3.25,TO_DATE('13-MAY-2021','DD-MON-YYYY'));
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (2, 'Nails',100,0.75,TO_DATE('28-AUG-2021','DD-MON-YYYY'));
INSERT INTO tbproducts (nidproduct,vdescription,nquantity,nprice,dlastdate) VALUES (3,'Screws',250,0.16,TO_DATE('21-JUL-2021','DD-MON-YYYY'));
CREATE VIEW tbproducts_v
AS
SELECT
nidproduct
,vdescription
,nquantity
,nprice
,dlastdate
,CASE WHEN SYSDATE > dlastdate THEN 'TRUE' ELSE 'FALSE' END as status
FROM tbproducts;
select * from tbproducts_v;
NIDPRODUCT VDESCRIPTION NQUANTITY NPRICE DLASTDATE STATU
---------- -------------------- ---------- ---------- ----------- -----
1 Hammer 50 3.25 13-MAY-2021 TRUE
2 Nails 100 .75 28-AUG-2021 FALSE
3 Screws 250 .16 21-JUL-2021 TRUE
If you insist on adding a new column then this is what you'd do.
ALTER TABLE tbproducts ADD status VARCHAR2(100);
UPDATE tbproducts t
SET (t.status) =
(SELECT
CASE WHEN SYSDATE > dlastdate THEN 'TRUE' ELSE 'FALSE' END
FROM tbproducts st
WHERE st.nidproduct = t.nidproduct);
if you just want to display the true/false in the console with a pl/sql block then this is an option (using implicit cursor for loop):
set serveroutput on size 999999
clear screen
DECLARE
BEGIN
FOR r IN (SELECT * FROM tbproducts) LOOP
dbms_output.put_line('Product '||r.vdescription ||', status: '||CASE WHEN SYSDATE > r.dlastdate THEN 'TRUE' ELSE 'FALSE' END);
IF r.dlastdate < SYSDATE THEN
dbms_output.put_line('Product '||r.vdescription ||' is expired !');
END IF;
END LOOP;
END;
/
note 1 : pl/sql has a boolean datatype, but sql does NOT have one. So in your table you'll need to store a string (1/0, T/F, Y/N, TRUE/FALSE).
note 2 : it's not a good practice to use camelcase in table names or column names. If you're creating them without quotes the names are case insensitive anyway.
Is it possible to use a sequence for a batch of rows, versus getting a new ID on each insert? I'm keeping track of a set of details, and I want the sequence to apply for the set, not each individual row. So my data should look like so:
id batch_id name dept
1 99 John Engineering
2 99 Amy Humanities
3 99 Bill Science
4 99 Jack English
It's the batch_id that I want Postgres to issue as a sequence. Is this possible?
Define batch_id as batch_id bigint not null default currval('seqname') and call nextval('seqname') manually before inserting batch of rows.
Or, for the full automation:
1) Create sequence for the batch id:
create sequence mytable_batch_id;
2) Create your table, declare batch id field as below:
create table mytable (
id bigserial not null primary key,
batch_id bigint not null default currval('mytable_batch_id'),
name text not null);
3) Create statement level trigger to increment the batch id sequence:
create function tgf_mytable_batch_id() returns trigger language plpgsql
as $$
begin
perform nextval('mytable_batch_id');
return null;
end $$;
create trigger tg_mytablebatch_id
before insert on mytable
for each statement execute procedure tgf_mytable_batch_id();
Now each single statement when you inserting data into the table will be interpreted as next single batch.
Example:
postgres=# insert into mytable (name) values('John'), ('Amy'), ('Bill');
INSERT 0 3
postgres=# insert into mytable (name) values('Jack');
INSERT 0 1
postgres=# insert into mytable (name) values('Jimmmy'), ('Abigail');
INSERT 0 2
postgres=# table mytable;
id | batch_id | name
----+----------+-------------
1 | 1 | John
2 | 1 | Amy
3 | 1 | Bill
4 | 2 | Jack
5 | 3 | Jimmy
6 | 3 | Abigail
(6 rows)
This question already has answers here:
Multiple insert SQL oracle
(2 answers)
Closed 3 years ago.
I can not use the insert all to insert values into the first_name,last_name and phone columns.
CREATE TABLE accounts (
account_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
first_name VARCHAR2(25) NOT NULL,
last_name VARCHAR2(25) NOT NULL,
email VARCHAR2(100),
phone VARCHAR2(12) ,
full_name VARCHAR2(51) GENERATED ALWAYS AS(
first_name || ' ' || last_name
),
PRIMARY KEY(account_id)
);
INSERT ALL
INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197')
INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198')
INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199')
SELECT* FROM DUAL;
This is the error message I get when I try to Run the code:
ORA-00001: unique constraint (BTMDATABASE.SYS_C0086595925) violated
ORA-06512: at "SYS.DBMS_SQL", line 1721
1. INSERT ALL
2. INTO accounts(first_name,last_name,phone)VALUES('Trinity','Knox','410-555-0197')
3. INTO accounts(first_name,last_name,phone)VALUES('Mellissa','Porter','410-555-0198')
Exactly, you can not. The way you decided to create unique values for the account_id column won't work in insert all as all rows get the same value which violates the primary key constraint.
Two workarounds:
don't use insert all but separate insert statements
switch to a sequence in order to set primary key column's values
And, here's an example (if you need it):
SQL> create table accounts
2 (account_id number primary key,
3 first_name varchar2(20) not null
4 );
Table created.
SQL> create sequence seq_acc;
Sequence created.
SQL> create or replace trigger trg_acc_seq
2 before insert on accounts
3 for each row
4 begin
5 :new.account_id := seq_acc.nextval;
6 end;
7 /
Trigger created.
SQL> insert all
2 into accounts (first_name) values ('John')
3 into accounts (first_name) values ('Ted')
4 into accounts (first_name) values ('Leeanna')
5 select * from dual;
3 rows created.
SQL> select * from accounts;
ACCOUNT_ID FIRST_NAME
---------- --------------------
1 John
2 Ted
3 Leeanna
SQL>
Your statement:
INSERT ALL
INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197')
INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198')
INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199')
SELECT* FROM DUAL;
Will try to give all the rows the same account_id which will violate your primary key.
Instead, you can use separate INSERT statements; or, if you want a single statement/transaction then you can wrap the INSERT statements in an anonymous PL/SQL block:
BEGIN
INSERT INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199');
END;
/
or, you can also use INSERT INTO ... SELECT ... UNION ALL ...:
INSERT INTO accounts(first_name,last_name,phone)
SELECT 'Trinity', 'Knox', '410-555-0197' FROM DUAL UNION ALL
SELECT 'Mellissa','Porter','410-555-0198' FROM DUAL;
Output:
SELECT * FROM accounts;
ACCOUNT_ID | FIRST_NAME | LAST_NAME | EMAIL | PHONE | FULL_NAME
---------: | :--------- | :-------- | :---- | :----------- | :--------------
2 | John | Mobsey | null | 410-555-0197 | John Mobsey
3 | Ted | Scherbats | null | 410-555-0198 | Ted Scherbats
4 | Leeanna | Bowman | null | 410-555-0199 | Leeanna Bowman
5 | Trinity | Knox | null | 410-555-0197 | Trinity Knox
6 | Mellissa | Porter | null | 410-555-0198 | Mellissa Porter
Note: account_id of 1 is the failed INSERT ALL.
db<>fiddle here
INSERT INTO accounts(first_name,last_name,phone)VALUES('John','Mobsey','410-555-0197');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Ted','Scherbats','410-555-0198');
INSERT INTO accounts(first_name,last_name,phone)VALUES('Leeanna','Bowman','410-555-0199');