I have a table named customers, that has many columns: 'id', 'name', 'age', 'weight', 'hight', 'blood', 'sex', 'last name', 'datetime'.
I want to add a checking logic before inserting, which is if the datetime already exists, then update/overwrite the whole existing row.
My code:
INSERT INTO customers (id,name,age,weight,hight,blood,sex,last name,datetime)
VALUES('1','2','3','4','5','6','7','8','2022-12-30')
ON CONFLICT (datetime)
DO
UPDATE SET name = EXCLUDED.name || ';' || customers.name;
....
UPDATE SET name = EXCLUDED.datetime || ';' || customers.datetime;
Because the table has many columns, and I want to update/overwrite the entire row if the datetime is already exists, so is there any easier way to do this instead of UPDATE SET for each column?
You can do this...
INSERT INTO customers (id,name,age,weight,hight,blood,sex,last name,datetime)
VALUES('1','2','3','4','5','6','7','8','2022-12-30')
ON CONFLICT (datetime)
DO
UPDATE SET
name = EXCLUDED.name || ';' || customers.name,
datetime = EXCLUDED.datetime || ';' || customers.datetime,
...and so on...
But it's a bad idea for a number of reasons.
You have to store everything as a varchar. You have no type protection and can't use any functions.
Searching will be complex and inefficient, as every search has to first parse the value.
The size of the data will continuously grow.
There's no indication of when the values are updated.
Assuming this is to record changes for auditing later, use an audit table to store the old values. This can be a specific table which mirrors the original column by column, but it's easier to use a single table and JSON.
create table audits (
id bigserial primary key,
table_name text not null,
data jsonb not null,
event text not null,
changed_at timestamp default(current_timestamp)
)
Then you can have a trigger write to the audit table on every change. event can be used to record whether it was an insert, update, or delete. You can also record what user made the change.
Use merge
Good examples here
https://www.sqlshack.com/understanding-the-sql-merge-statement/
Your target table will be the Customers table, and you will need to create a virtual table with the record you want to insert/update, let's call it Record
MERGE Customers AS Target
USING Record AS Source
ON Source.datetime = Target.datetime And id Source.id = Target.id
-- For Inserts
WHEN NOT MATCHED BY Target THEN
-- insert the record here
-- For Updates
WHEN MATCHED THEN UPDATE SET
-- Update here
Solution not complete, you need to put your values into a virtual table and update the insert and the update statements, but you get the idea.
Related
Assuming I want to update a JSONB column contacts (array of objects) in a customers table, and I want to update a value of an object inside an array based on its index thanks to a subquery, do I need to lock the table from update during the execution to avoid concurrency problems?
In other words, could the table be altered between my two query execution, and thus the index I selected thanks to the subquery would be obsolete?
with contact_email as (
select ('{' || index - 1 || ', value}')::text[] as path from customers
cross join jsonb_array_elements(contacts) with ordinality arr(contact, index)
where contact->>type = 'email' and name = 'john'
)
update customers
set contacts = jsonb_set(contacts,contact_email.path,'"john#example.com"', false)
from contact_email
where and name = 'john'
-- `customers` table has a `name` column and a `contacts` column (jsonb)
-- `contacts` column contains things like `[{"type":"email","value":"x#y.z", …}]`
In the example above, if the array in the contacts column is altered between the table reading (subquery) and the update (main query), then the index selected would become wrong: then I would update the wrong array entry.
If something is unclear I can edit my question and add more details.
Both parts of the query will see the same snapshot of the database, so the data are always consistent.
If some concurrent transaction changes the row between the time it is read and the time it is written, the outcome depends on your isolation level:
if you are running with the default READ COMMITTED isolation, the update will either overwrite that change or do nothing (the latter if name has changed)
if you are running with REPEATABLE READ or higher, you will get a serialization error and have to repeat the statement
I have a table with 400.000 rows and I added a new column on it. That table has an index id and I want to update each row with a different value that I have already calculated.
For example, I have the update statements like these:
UPDATE TABLE SET SECOND_NAME = 'Alfred' WHERE id = 510675;
UPDATE TABLE SET SECOND_NAME = 'Pedro' WHERE id = 123123;
UPDATE TABLE SET SECOND_NAME = 'Robert' WHERE id = 123123;
SECOND_NAME is the new column that I want to populate, lets say I have around 400.000 update statements, is there a way to massive update them in a faster way? If not, is there a way to know beforehand how long it could take to update them in that way?
I think your best bet, is probably to use an EXTERNAL TABLE. This AskTom answer basically gives you everything you need: AskTom. However, to customize it for your question. You could take the information you have, save it to a CSV and load it in as follows.
CREATE TABLE new_names (id NUMBER(10) PRIMARY KEY, second_name VARCHAR2(50))
ORGANIZATION EXTERNAL
(type oracle_loader
default directory data_dir
access parameters ( fields terminated by ',' )
location ('names.csv')
)
/
UPDATE (SELECT t.second_name empty_name, n.second_name loaded_name
FROM table_name t
INNER JOIN new_names n ON t.id = n.id)
SET empty_name = loaded_name;
/
All you need to do to make that work is create the directory and place your file in it.
After you are done, you can drop the the external table.
When I run the below UPDATE command after I run the trigger, I get the following error
Error Code: 1054 Unknown Column 'action' in field list
Any assistance would be much appreciated.
DELIMITER $$
CREATE TRIGGER Trigger_9
BEFORE UPDATE ON Customers
FOR EACH ROW
BEGIN
INSERT INTO Customers
SET action = 'update',
Email = NEW.Email,
CustomerID = NEW.CustomerID;
END$$
DELIMITER ;
UPDATE Customers
SET Email = 'Update'
WHERE CustomerID = 12345;
SELECT * FROM Customers
It looks like your trigger is being used for auditing changes to the Customer.Email field. You would be better off inserting that info into a different (audit) table, maybe named something like Customer_Audit or something like that.
There are two ways of setting this up:
1) If you want to keep the entire history, then only do INSERTs into your Audit table (no updates, because they destroy history). In that table, make sure the PK is either a (new) auto-number col (not the CustomerID) or a compound PK consisting of CustomerID + a (new) datetime col. I usually recommend autonumber because it is easier and guarantees uniqueness.
-or-
2) If you only want to keep 1 history record per customer, the easy way would be to delete (just 1 customer row) and re-insert into to your Audit table. The hard way would be to use logic like IF Exists(SELECT CustomerID FROM HistoryTable WHERE CustomerID=#####) UPDATE ... ELSE INSERT ...
I have 6 tables:
Staff ( StaffID, Name )
Product ( ProductID, Name )
Faq ( FaqID, Question, Answer, ProductID* )
Customer (CustomerID, Name, Email)
Ticket ( TicketID, Problem, Status, Priority, LoggedTime, CustomerID* , ProductID* )
TicketUpdate ( TicketUpdateID, Message, UpdateTime, TicketID* , StaffID* )
Question to be answered:
Given a Product ID, remove the record for that Product. When a product is removed all associated FAQ can stay in the database but should have a null reference in the ProductID field. The deletion of a product should, however, also remove any associated tickets and their updates. For completeness deleted tickets and their updates should be copied to an audit table or a set of tables that maintain historical data on products, their tickets and updates. (Hint: you will need to define a additional table or set or tables to maintain this audit information and automatically copy any deleted tickets and ticket updates when a product is deleted). Your audit table/s should record the user which requested the deletion and the timestamp for the deletion operation.
I have created additional maintain_audit table:
CREATE TABLE maintain_audit(
TicketID INTEGER NOT NULL,
TicketUpdateID INTEGER NOT NULL,
Message VARCHAR(1000),
mdate TIMESTAMP NOT NULL,
muser VARCHAR(128),
PRIMARY KEY (TicketID, TicketUpdateID)
);
Addittionally I have created 1 function and trigger:
CREATE OR REPLACE FUNCTION maintain_audit()
RETURNS TRIGGER AS $BODY$
BEGIN
INSERT INTO maintain_audit (TicketID,TicketUpdateID,Message,muser,mdate)
(SELECT Ticket.ID,TicketUpdate.ID,Message,user,now() FROM Ticket, TicketUpdate WHERE Ticket.ID=TicketUpdate.TicketID AND Ticket.ProductID = OLD.ID);
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER maintain_audit
BEFORE DELETE
ON Product
FOR EACH ROW
EXECUTE PROCEDURE maintain_audit()
DELETE FROM Product WHERE Product.ID=30;
When I run this all I get this :
ERROR: null value in column "productid" violates not-null constraint
CONTEXT: SQL statement "UPDATE ONLY "public"."faq" SET "productid" = NULL WHERE $1 OPERATOR(pg_catalog.=) "productid""
GUYS,Could you help me in sorting out this problem?
What you probably want is triggers. Not sure what RDBMS you are using, but that's where you should start. I started from zero and had triggers up and running in a somewhat similar situation within an hour.
In case you don't already know, triggers do something after a specific type of query happens on a table, such as an insert, update or delete. You can do any type of query.
Another tip I would give you is not to delete anything, since that could break data integrity. You could just add an "active" boolean field, set active to false, then filter those out in most of your system's queries. Alternatively, you could just move the associated records out to a Products_archive table that has the same structure. Easy to do with:
select * into destination from source where 1=0
Still, I would do the work you need done using triggers because they're so automatic.
create a foreign key for Ticket.product_id, and TicketUpdate.Ticket_id which has ON DELETE CASCADE. This will automatically delete all tickets and ticketupdates when you delete the product.
create an audit table for Product deleters with product_id, user and timestamp. audit tables for ticket,ticketUpdate should mirror them exactly.
create a BEFORE DELETE TRIGGER for table Ticket which copies tickets to the audit table.
Do the same for TicketUpdate
Create an AFTER DETETE Trigger on Products to capture who requested a product be deleted in the product audit table.
In table FAQ create Product_id as a foreign key with ON DELETE SET NULL
Let's say that I have a table of items, and for each item, there can be additional information stored for it, which goes into a second table. The additional information is referenced by a FK in the first table, which can be NULL (if the item doesn't have additional info).
TABLE item (
...
item_addtl_info_id INTEGER
)
CONSTRAINT fk_item_addtl_info FOREIGN KEY (item_addtl_info)
REFERENCES addtl_info (addtl_info_id)
TABLE addtl_info (
addtl_info_id INTEGER NOT NULL
GENERATED BY DEFAULT
AS IDENTITY (
INCREMENT BY 1
NO CACHE
),
addtl_info_text VARCHAR(100)
...
CONSTRAINT pk_addtl_info PRIMARY KEY (addtl_info_id)
)
What is the "best practice" to update an item's additional info (in IBM DB2 SQL, preferably)?
It should be an UPSERT operation, meaning that if additional info does not yet exist then a new record is created in the second table, but if it does, then it is only updated, and the FK in the first table does not change.
So imperatively, this is the logic:
UPSERT(item, item_info):
CASE WHEN item.item_addtl_info_id IS NULL THEN
INSERT INTO addtl_info (item_info)
UPDATE item.item_addtl_info_id (addtl_info.addtl_info_id)
^^^^^^^^^^^^^
ELSE
UPDATE addtl_info (item_info)
END
My main problem is how to get the newly inserted addtl_info row's id (underlined above). In a stored proc I can request the id from a sequence and store it in a variable, but maybe there is a more straightforward way. Isn't it something that comes up all the time when programming databases?
I mean, I'm really not interested in what the id of the addtl_info record is as long as it remains unique and is referenced properly. So using sequences seems a bit of an overkill to me in this case.
As a matter of fact, this UPSERT operation should be part of the SQL language as a standard operation (maybe it is, and I just don't know about it?)...
The syntax I was looking for is:
SELECT * FROM NEW TABLE ( INSERT INTO phone_book VALUES ( 'Peter Doe','555-2323' ) )
from Wikipedia (http://en.wikipedia.org/wiki/Insert_%28SQL%29)
This is how to refer to the record that was just inserted in the table.
My colleague called this construct an "in-place trigger", which what it really is...
Here is the first version that I put together as a compound SQL statement:
begin atomic
declare addtl_id integer;
set addtl_id = (select item_addtl_info_id from item where item.item_id = XXX);
if addtl_id is null
then
set addtl_id = (select addtl_info_id from new table
(insert into addtl_info
(addtl_info_text)
values ('My brand new additional info')
)
);
update item set item.item_addtl_info_id = addtl_id
where item.item_id = XXX;
else
update addtl_info set addtl_info_text = 'My updated additional info'
where addtl_info.addtl_info_id = addtl_id;
end if;
end
XXX being equal to the item id to be updated - this code can now be easily inserted into a sproc, and XXX can be converted to an input parameter.
I also tried using MERGE INTO, but I couldn't figure out a syntax for updating a table different from what was specified as the target.