Update table using query result - sql

I am attempting to add the result of my query into the column of an existing table.
Thus far, the query below calculates the CAR_PRICE and displays the value. However, I want to add this value to the CAR_PAYMENT_TBL in the car_price column.
The create table commands below show the relevant table and the relationships between them. Is it possible to update the CAR_PRICE value in the CAR_PAYMENT_TBL?
SELECT C.TICKET_NO,
C.REG_ID,
C.BOOKING_ID,
(R.END_DATE-R.START_DATE) AS DAYS_STAYED,
(R.END_DATE-R.START_DATE)*5 AS CAR_PRICE
FROM CAR_TBL C
LEFT JOIN
ROOM_TBL R
ON C.BOOKING_ID = R.BOOKING_ID;
TABLE SCHEMA:
CREATE TABLE CAR_PAYMENT_TBL
(
TICKET_NO INT NOT NULL PRIMARY KEY,
CAR_PRICE NUMERIC(5,2)
);
CREATE TABLE CAR_TBL
(
REG_ID VARCHAR2(7) NOT NULL PRIMARY KEY,
TICKET_NO INT NOT NULL references CAR_PAYMENT_TBL(TICKET_NO),
BOOKING_ID INT NOT NULL references BOOKING_TBL(BOOKING_ID)
);
CREATE TABLE ROOM_TBL
(
STAY_NO INT NOT NULL PRIMARY KEY,
ROOM_NO VARCHAR2(4) NOT NULL references ROOM_DETAILS_TBL(ROOM_NO),
START_DATE DATE NOT NULL,
END_DATE DATE NOT NULL,
BOOKING_ID INT NOT NULL references BOOKING_TBL(BOOKING_ID)
);

You cannot reference other tables in an UPDATE statement in Oracle - use a subquery or a MERGE statement:
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE =
(select (ROOM_TBL.END_DATE - ROOM_TBL.START_DATE)*5 from room_tbl where ... )
WHERE CAR_PAYMENT_TBL.TICKET_NO = &TICKET_NO;
You'll also have to provide a sensible WHERE clause for the subquery (assuing &TICKET_NO is really a bind variable and not your join condition for the two tables).

If you want to update all the records of CAR_PAYMENT_TBL based on the values of ROOM_TBL
then
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE = (select (ROOM_TBL.END_DATE – ROOM_TBL.START_DATE)*5
FROM ROOM_TBL WHERE CAR_PAYMENT_TBL.TICKET_NO = ROOM_TBL.TICKET_NO)
If you want to update only specific record of CAR_PAYMENT_TBL then
UPDATE CAR_PAYMENT_TBL
SET CAR_PRICE = (select (ROOM_TBL.END_DATE – ROOM_TBL.START_DATE)*5
FROM ROOM_TBL WHERE CAR_PAYMENT_TBL.TICKET_NO = ROOM_TBL.TICKET_NO)
where CAR_PAYMENT_TBL = &ticket_num;

Related

How to use SELECT in function to compare fields

There are 3 tables: Event, Booking, and Booking_Day.
The idea is that one can book separate days of the event.
I would like to put a constraint on Booking_Day so that Day has to be within Date_Start and Date_End range of the corresponding Event. I decided to use a function that will do this
create table Event
(
Event_ID int identity
constraint Event_pk
primary key nonclustered,
Date_Start date not null,
Date_End date
)
create table Booking
(
Booking_ID int identity
constraint Booking_pk
primary key nonclustered,
Event_ID int not null
constraint Booking_Event_Event_ID_fk
references Event
)
create table Booking_Day
(
Day date not null,
Booking_ID int not null
constraint Booking_Day_Booking_Booking_ID_fk
references Booking,
constraint Booking_Day_pk
primary key nonclustered (Day, Booking_ID)
)
And the function:
CREATE FUNCTION check_if_in_range (
#Event_id int,
#Day DATE
) RETURNS int
BEGIN
declare #result TABLE (Day DATE,Booking_ID INT,Event_ID INT,Date_start DATE, Data_end DATE)
INSERT into #result
SELECT Booking_Day.Day, Booking.Event_ID, Event.Date_Start, Event.Date_End
FROM ((Booking_Day INNER JOIN Booking on Booking_Day.Booking_ID = B.Booking_ID )
INNER JOIN Event on Event.Event_ID = Booking.Event_ID) WHERE Booking_Day.Day = #Day AND B.Event_ID = #Event_id
return ((#Day >= #result.Date_start) AND (#Day <= #result.Data_end))
END
Because of the primary key constraint on Booking_day table, the above should return only one row.
When trying to add function do database I get “[[S0001][137] Must declare the scalar variable “#result".
How do I deal with it? Is my approach entirely wrong and I don’t need a table within the ​function for this?
I don't understand why you would be using a table variable for this. You cannot just refer to a table unless you specify a FROM clause -- you are confusing table variables and scalar variables.
But why bother with variables at all?
IF (EXISTS (SELECT 1
FROM Booking_Day bd INNER JOIN
Booking b
ON bd.Booking_ID = B.Booking_ID INNER JOIN
Event e
ON e.Event_ID = b.Event_ID
WHERE b.Day = #Day AND
b.Event_ID = #Event_id AND
#Day >= e.Date_Start AND
#Day <= e.Data_end
)
)
BEGIN
return 1
END;
return 0

ORACLE 12c Unique constraint failing in MERGE command which is matching all columns from the index

I have a table of aggregated data which were collected incorrectly and I am trying to merge results of select into it and I am hitting constraint violation and probably missing something obvious.
Table is simple:
CREATE TABLE "DVRA_STATS_AGG_HOURLY"
( "ID" NUMBER(*,0),
"SHIPMENTS" NUMBER,
"EVENT_DATETIME" DATE,
"COUNTRY" VARCHAR2(2 CHAR),
"DATA_TYPE" VARCHAR2(3 CHAR),
"EVENT_TYPE" CHAR(15)
)
CREATE UNIQUE INDEX "DVRA_STATS_AGG_HOURLY_PK" ON "DVRA_STATS_AGG_HOURLY" ("ID")
CREATE UNIQUE INDEX "DVRA_STATS_AGG_HOURLY_UK1" ON "DVRA_STATS_AGG_HOURLY" ("EVENT_DATETIME", "COUNTRY", "DATA_TYPE", "EVENT_TYPE")
CREATE INDEX "DVRA_STATS_AGG_HOURLY_INDEX1" ON "DVRA_STATS_AGG_HOURLY" ("EVENT_DATETIME" DESC)
With trigger sequence and trigger
CREATE OR REPLACE EDITIONABLE TRIGGER "DVRA_STATS_AGG_HOURLY_AINC"
BEFORE INSERT ON dvra_stats_agg_hourly
FOR EACH ROW
BEGIN
SELECT stats_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
Merge is not complicated either:
MERGE INTO dvra_stats_agg_hourly stats
USING(
SELECT COUNT(*) as SHIPMENTS, TRUNC(event_datetime,'HH24') as event_datetime,COUNTRY,data_type,event_type
FROM AUDIT
WHERE event_type in (<List of events>)
and TRUNC(event_datetime,'HH24') < trunc(sysdate,'HH24')
and audittable.event_datetime is not null
and audittable.COUNTRY is not null
and audittable.data_type is not null
and audittable.event_type is not null
GROUP BY TRUNC(event_datetime,'HH24'),COUNTRY,data_type,event_type
) audittable
ON (
audittable.event_datetime = stats.event_datetime
and audittable.COUNTRY = stats.COUNTRY
and audittable.data_type = stats.data_type
and audittable.event_type = stats.event_type
)
WHEN MATCHED THEN
UPDATE SET stats.SHIPMENTS = audittable.SHIPMENTS WHERE stats.SHIPMENTS <> audittable.SHIPMENTS
WHEN NOT MATCHED THEN
INSERT (SHIPMENTS,event_datetime,STATS.COUNTRY,data_type,event_type)
VALUES (audittable.SHIPMENTS,audittable.event_datetime,audittable.COUNTRY ,audittable.data_type ,audittable.event_type)
;
And I am getting error:
ORA-00001: unique constraint (DVRA_MONITORING.DVRA_STATS_AGG_HOURLY_UK1) violated
I am matching on same columns as I am using in unique constraint. I am inserting only not matching rows and I am grouping by same columns so there should be only one row for unique combination of those columns.
I am only one updating table when I am doing this operation, so no other session manipulating data during run of the query.
What am I missing here?
Edit: Data in DVRA_STATS_AGG_HOURLY were inserted with insert into select. With same select which is used in this merge. Just some data were not properly loaded yet, so I am making correction.
Edit2: added not null
Edit3: Changes in merge query after discussion with Alex
MERGE INTO dvra_stats_agg_hourly stats
USING(
SELECT COUNT(*) as SHIPMENTS, TRUNC(event_datetime,'HH24') as event_datetime,COUNTRY,data_type,event_type
FROM BCTCUSTOM.V_DVRA_AUDIT
WHERE event_type in (<List of types>)
and TRUNC(event_datetime,'HH24') < trunc(sysdate,'HH24')
and TRUNC(event_datetime,'HH24') is not null
and event_datetime is not null
and COUNTRY is not null
and data_type is not null
and event_type is not null
GROUP BY TRUNC(event_datetime,'HH24'),COUNTRY,data_type,event_type
) audittable
ON (
audittable.event_datetime = stats.event_datetime
and ((audittable.COUNTRY is null and stats.COUNTRY is null) or audittable.COUNTRY = stats.COUNTRY)
and ((audittable.data_type is null and stats.data_type is null) or audittable.data_type = stats.data_type)
and ((audittable.event_type is null and stats.event_type is null) or audittable.event_type = stats.event_type)
)
WHEN MATCHED THEN
UPDATE SET stats.SHIPMENTS = audittable.SHIPMENTS WHERE stats.SHIPMENTS <> audittable.SHIPMENTS
WHEN NOT MATCHED THEN
INSERT (SHIPMENTS,event_datetime,STATS.COUNTRY,data_type,event_type)
VALUES (audittable.SHIPMENTS,audittable.event_datetime,audittable.COUNTRY ,audittable.data_type ,audittable.event_type)
;
If any of the four columns you are comparing in your ON clause are null then they won't match with your current conditions, since null = null is unknown.
You can add explicit null checks; instead of:
ON (
audittable.event_datetime = stats.event_datetime
and audittable.COUNTRY = stats.COUNTRY
and audittable.data_type = stats.data_type
and audittable.event_type = stats.event_type
)
do something like:
ON (
audittable.event_datetime = stats.event_datetime
and ((audittable.COUNTRY is null and stats.COUNTRY is null)
or audittable.COUNTRY = stats.COUNTRY)
and ((audittable.data_type is null and stats.data_type is null)
or audittable.data_type = stats.data_type)
and ((audittable.event_type is null and stats.event_type is null)
or audittable.event_type = stats.event_type)
)
db<>fiddle without nulls - works OK.
db<>fiddle with nulls - initially fails, but works with explicit null checks added.
Another possibility is a data type mismatch, as you're using CHAR(15) in the table you showed. If the audit table has that column defined as VARCHAR2(15) instead then the comparison will also fail (because "Oracle uses nonpadded comparison semantics whenever one or both values in the comparison have the data type VARCHAR2 or NVARCHAR2"), and the implicit conversion during the insert will cause the constraint violation. In that case, you can trim the CHAR value:
ON (
audittable.event_datetime = stats.event_datetime
and audittable.COUNTRY = stats.COUNTRY
and audittable.data_type = stats.data_type
and audittable.event_type = trim(stats.event_type)
)
db<>fiddle

Oracle Update takes long time while updating primary key column

There is the table EFORMDYNAMICFIELDINSTANCE with primary key (Enterpriseid, Ownertype, Ownerid, Itemtype, Itemid).
In order to change a primary key to single column i.e EDFI_ID we want to update this EDFI_ID starting with 7000000 as increment value.
This table with hardly 50000 records takes 10 hrs to update.
This is my table defination:
ENTERPRISEID NOT NULL NUMBER(10),
OWNERTYPE NOT NULL VARCHAR2(60),
OWNERID NOT NULL NUMBER(10),
ITEMTYPE NOT NULL VARCHAR2(60),
ITEMID NOT NULL NUMBER(10),
EDFI_ID NOT NULL NUMBER(10),
FIELD1 VARCHAR2(2000),
FIELD2 VARCHAR2(2000),
...
FIELD199 VARCHAR2(2000),
FIELD200 VARCHAR2(2000)
Earlier we had (ENTERPRISEID, OWNERTYPE, OWNERID, ITEMTYPE, ITEMID) as the primary key.
Now EDFI_ID is my primary key column and we want to update this primary key with (rownumber + 7000000). This table has approximately 50000 records and EDFI_ID should update as 7000000, 7000001, 7000002....7050000.
Please suggest an UPDATE statement which will take less time. As of now my above UPDATE is taking 10 hours.
My guess is that your problem is the repeated execution of your subqueries. I'm not even sure the second one (inside the WHERE EXISTS) is doing anything useful.
Strip out the WHERE EXISTS - that will eliminate half the effort. If that doesn't help then...
create a temporary mapping table that holds all your existing primary key values, along with your new PK value. Create a unique index on your five PK columns. Then use the mapping table in the subquery
Just to elaborate on my second suggestion :
CREATE TABLE temp_pk_mapping AS
(SELECT
enterpriseid
,ownertype
,ownerid
,itemtype
,itemid
,rownum + 70000 new_pk
FROM
(SELECT DISTINCT
enterpriseid
,ownertype
,ownerid
,itemtype
,itemid
FROM
eformdynamicfieldinstance
)
)
;
CREATE UNIQUE INDEX temp_pk_mapping_u1 ON temp_pk_mapping
( enterpriseid
,ownertype
,ownerid
,itemtype
,itemid
)
;
UPDATE eformdynamicfieldinstance edfi
SET edfi.edfi_id =
(SELECT new_pk
FROM temp_pk_mapping map
WHERE edfi.enterpriseid = map.enterpriseid
AND edfi.ownertype = map.ownertype
AND edfi.ownerid = map.ownerid
AND edfi.itemtype = map.itemtype
AND edfi.itemid = map.itemid
)
;

How to enforce an "ALL-TO-ALL" relationship?

Suppose I have the following structure (SQL Server syntax):
CREATE TABLE A (
key_A int NOT NULL PRIMARY KEY CLUSTERED,
info_A nvarchar(50) NULL
);
CREATE TABLE B(
key_B int NOT NULL PRIMARY KEY CLUSTERED,
info_B nvarchar(50) NULL
);
CREATE TABLE C(
key_C int NOT NULL PRIMARY KEY CLUSTERED,
key_A int NOT NULL,
key_B int NOT NULL,
info1 nvarchar(50) NULL,
info2 nvarchar(50) NULL,
info3 nvarchar(50) NULL
);
ALTER TABLE C WITH CHECK ADD CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A);
ALTER TABLE C WITH CHECK ADD CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B);
So, table C has two primary keys to table A and table B.
Table C has to have the cartesian product of table A and table B. That means all combinations, so when a new record is inserted in table A, we have to insert in table C several rows with the new reference in A by all the rows in B. And viceversa, in the case of insertion in B.
The question is, how can you enforce the integrity of such relationship in SQL Server, in which table C has to have all combinations of A and B? Or, if you consider such a structure a bad practice, what alternative tables do you recommend that do not add the hassle of having to do DISTINCT selects and such?
Thanks!
Link to Fiddle:
Fiddle
You need to have inserted into table A / B before you can reference this new entry in table C. The only way I know of would be to create a trigger on tables A and B to populate table C when a new entry was made to either of those tables. The issue there is what do you then put into the other fields? Since these are nullable I assume you're happy defaulting them to null? If not (i.e. you want the user to input valid values) the only way to do this would be in the logic of your application, rather than at database level (or by using stored procedures to populate these tables where those procs had suitable logic to create the appropriate entries in C in addition to A / B).
Trigger Code Example:
use StackOverflowDemos
go
if OBJECT_ID('TRG_C_DELETE','TR') is not null drop trigger TRG_C_DELETE
if OBJECT_ID('TRG_A_INSERT','TR') is not null drop trigger TRG_A_INSERT
if OBJECT_ID('TRG_B_INSERT','TR') is not null drop trigger TRG_B_INSERT
if OBJECT_ID('C','U') is not null drop table C
if OBJECT_ID('A','U') is not null drop table A
if OBJECT_ID('B','U') is not null drop table B
go
CREATE TABLE A
(
key_A int NOT NULL IDENTITY(1,1) CONSTRAINT PK_A PRIMARY KEY CLUSTERED,
info_A nvarchar(50) NULL
);
go
CREATE TABLE B
(
key_B int NOT NULL IDENTITY(1,1) CONSTRAINT PK_B PRIMARY KEY CLUSTERED,
info_B nvarchar(50) NULL
);
go
CREATE TABLE C
(
key_C int NOT NULL IDENTITY(1,1) CONSTRAINT PK_C PRIMARY KEY CLUSTERED,
key_A int NOT NULL CONSTRAINT FK_C_A FOREIGN KEY(key_A) REFERENCES A (key_A),
key_B int NOT NULL CONSTRAINT FK_C_B FOREIGN KEY(key_B) REFERENCES B (key_B),
info1 nvarchar(50) NULL,
info2 nvarchar(50) NULL,
info3 nvarchar(50) NULL
);
go
CREATE TRIGGER TRG_A_INSERT
ON A
AFTER INSERT
AS
BEGIN
--add new As to C
insert C (key_A, key_B)
select key_A, key_B
from inserted
cross join B
END;
go
CREATE TRIGGER TRG_B_INSERT
ON B
AFTER INSERT
AS
BEGIN
--add new As to C
insert C (key_A, key_B)
select key_A, key_B
from inserted
cross join A
END;
go
CREATE TRIGGER TRG_C_DELETE
ON C
AFTER DELETE
AS
BEGIN
DELETE
FROM B
WHERE key_B IN
(
SELECT key_B
FROM DELETED d
--ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of B in this table
WHERE key_B NOT IN
(
SELECT key_B
FROM C
WHERE C.key_C NOT IN
(
SELECT key_C
FROM deleted
)
)
)
DELETE
FROM A
WHERE key_A IN
(
SELECT key_A
FROM DELETED d
--ths row onwards are here to cover the circumstance that the record being deleted isn't the only instance of A in this table
WHERE key_A NOT IN
(
SELECT key_A
FROM C
WHERE C.key_C NOT IN
(
SELECT key_C
FROM deleted
)
)
)
END;
go
insert A select 'X'
select * from C --no results as no Bs yet
insert A select 'Y'
select * from C --no results as no Bs yet
insert B select '1'
select * from C --2 results; (X,1) and (Y,1)
insert A select 'Z'
select * from C --3 results; the above and (Z,1)
delete from A where info_A = 'Y'
select * from C --3 results; as above since the previous statement should fail due to enforced referential integrity
insert C (key_A, key_B, info1)
select a.key_A, b.key_B, 'second entry for (Y,1)'
from A
cross join B
where a.info_A = 'Y'
and b.info_B = '1'
select * from C --4 results; as above but with a second (Y,1), this time with data in info1
delete from C where info1 = 'second entry for (Y,1)'
select * from C --3 results; as above but without the new(Y,1)
select * from A --3 results
select * from B --1 result
delete from C where key_A in (select key_A from A where info_A = 'Y')
select * from C --2 results; (X,1) and (Z,1)
select * from A --2 results; X and Z
select * from B --1 result
delete from C where key_B in (select key_B from B where info_B = '1')
select * from C --0 results
select * from A --0 results
select * from B --0 result
SQL Fiddle demo here (NB: only SQL Fiddle only shows the outputs from table C; also the error demo has been commented out since this errors the whole thing rather than just the one error line).
http://sqlfiddle.com/#!3/34d2f/4

Find all foreign key rows from table that is referenced multiple times

I have the following database structure:
CREATE TABLE LookupTable
(
PK UNIQUEIDENTIFIER PRIMARY KEY,
)
CREATE TABLE MainTable
(
Lookup1 UNIQUEIDENTIFIER FOREIGN KEY REFERENCES LookupTable(PK),
Lookup2 UNIQUEIDENTIFIER FOREIGN KEY REFERENCES LookupTable(PK),
-- ...
-- ... LookupN UNIQUEIDENTIFIER FOREIGN KEY REFERENCES LookupTable(PK),
)
MainTable references LookupTable multiple times via separate columns.
If I insert the following data:
INSERT INTO LookupTable VALUES('11111111-1111-1111-1111-111111111111')
INSERT INTO LookupTable VALUES('22222222-2222-2222-2222-222222222222')
INSERT INTO MainTable VALUES('11111111-1111-1111-1111-111111111111','22222222-2222-2222-2222-222222222222')
INSERT INTO MainTable VALUES('22222222-2222-2222-2222-222222222222','11111111-1111-1111-1111-111111111111')
I want to be able to find every record in [MainTable] where ANY of the lookup fields is equal to '11111111-1111-1111-1111-111111111111' (this should return both rows in the example).
SQL is not my strong suit. Is there a simpler way of doing this than
SELECT * FROM MainTable WHERE
Lookup1 = '11111111-1111-1111-1111-111111111111'
OR
Lookup2 = '11111111-1111-1111-1111-111111111111'
-- ...
-- OR
-- LookupN = '11111111-1111-1111-1111-111111111111'
?
This seems tedious because it requires me to specify every lookup column by name before I can retrieve the results I want, and in my database there can be 20+ lookup columns in some circumstances.
There are three options:
Query your tables the way you doing (many ORs)
Build your query dynamically and execute it (like EXEC on SQL Server)
Change your database schema and move the Lookup-columns from your MainTable to a third table
CREATE TABLE LookupTable
(
PK UNIQUEIDENTIFIER PRIMARY KEY,
)
CREATE TABLE MainTable
(
PK UNIQUEIDENTIFIER PRIMARY KEY,
)
CREATE TABLE MainTableLookup
(
MainTablePK UNIQUEIDENTIFIER FOREIGN KEY REFERENCES MainTable(PK),
Lookup UNIQUEIDENTIFIER FOREIGN KEY REFERENCES LookupTable(PK),
)
Then you can query like this:
SELECT
*
FROM
MainTable MT JOIN MainTableLookup ON MT.PK = MTL.MainTablePK
WHERE
EXISTS (SELECT 1 FROM LookupTable LT
WHERE LT.PK = MTL.Lookup
AND MTL.Lookup = '11111111-1111-1111-1111-111111111111')
One further suggestion. Doing a query with OR can lead to poor performance; it can be faster with a UNION:
SELECT * FROM MainTable WHERE
Lookup1 = '11111111-1111-1111-1111-111111111111'
UNION
SELECT * FROM MainTable WHERE
Lookup2 = '11111111-1111-1111-1111-111111111111'
...