Insert if Update fails - sql

Using H2, I want to attempt to update a row. If it doesn't exist, I'd like to insert it. I'd like to do it all in one single SQL statement, if possible, to avoid concurrency issues.
So far I have the update:
UPDATE RATING
SET NUM_RATINGS = (SELECT NUM_RATINGS + 1 FROM RATING WHERE EVENT_UID = :eventUid)
, SUM_RATINGS = (SELECT SUM_RATINGS + :newRating FROM RATING WHERE EVENT_UID = :eventUid)
WHERE EVENT_UID = :eventUid AND EXISTS ( SELECT * FROM RATING WHERE EVENT_UID = :eventUid)
The table definition is:
CREATE TABLE RATING (
ID BIGINT NOT NULL,
EVENT_UID VARCHAR(255) NOT NULL,
SUM_RATINGS BIGINT NOT NULL,
NUM_RATINGS INT NOT NULL,
PRIMARY KEY (ID),
FOREIGN KEY (EVENT_UID) REFERENCES EVENT(UID)
)
Can anyone improve the Update statement?
How do I, in the same SQL statement, add an Insert like the following, if the row does not exist?
INSERT INTO RATING ( ID , EVENT_UID , NUM_RATINGS , SUM_RATINGS )
VALUES (2, 'BWEIY-A4', 1, 4)

Related

Dynamically created Tables JOIN in SQL Server

I have 3 types of tables
Major table as follows
CREATE TABLE #InitialTable
(
Id int PRIMARY KEY IDENTITY(1,1),
RP varchar(20)
)
INSERT INTO #InitialTable
VALUES ('R1', 'R2', 'R3')
GO
Table contains dynamically created tables information as follows
CREATE TABLE #DynamicTablesInfo
(
Id int PRIMARY KEY IDENTITY(1,1),
RPId int FOREIGN KEY REFERENCES #InitialTable(Id),
TableName varchar(100)
)
GO
INSERT INTO #DynamicTablesInfo
VALUES (1, 'Table_X1'), (2, 'Table_X2'), (3, 'Table_X3')
GO
Dynamically created tables these tables can be any number of tables and the tables info is available in above table.
CREATE TABLE #Table_X1
(
Id int PRIMARY KEY IDENTITY,
Version_Value varchar(100)
)
GO
INSERT INTO #Table_X1
VALUES ('Val_X1_1'), ('Val_X1_2'), ('Val_X1_3')
GO
CREATE TABLE #Table_X2
(
Id int PRIMARY KEY IDENTITY,
Version_Value varchar(100)
)
GO
INSERT INTO #Table_X2
VALUES ('Val_X2_1'), ('Val_X2_2'), ('Val_X2_3')
GO
CREATE TABLE #Table_X3
(
Id int PRIMARY KEY IDENTITY,
Version_Value varchar(100)
)
GO
INSERT INTO #Table_X3
VALUES ('Val_X3_1'), ('Val_X3_2'), ('Val_X3_3')
GO
Now I wanted to join InitialTable with dynamically created tables (Table_X1, Table_X2, Table_X3,....) with the help of DynamicTablesInfo table - how to do that?
Note: for easy update, delete, insert I created them as temporary tables but in my application all are real tables.
Instead of doing this:
CREATE TABLE #Table_X1
(
Id int PRIMARY KEY IDENTITY
,Version_Value varchar(100)
)
GO
CREATE TABLE #Table_X2
(
Id int PRIMARY KEY IDENTITY
,Version_Value varchar(100)
)
GO
CREATE TABLE #Table_X3
(
Id int PRIMARY KEY IDENTITY
,Version_Value varchar(100)
)
GO
Do this one time:
CREATE TABLE Table_X
(
Id int PRIMARY KEY IDENTITY
,Version_Value varchar(100)
,X_number INT
)
GO
Then instead of doing this:
INSERT INTO #Table_X1 VALUES ('Val_X1_1'),('Val_X1_2'),('Val_X1_3')
INSERT INTO #Table_X2 VALUES ('Val_X2_1'),('Val_X2_2'),('Val_X2_3')
INSERT INTO #Table_X3 VALUES ('Val_X3_1'),('Val_X3_2'),('Val_X3_3')
Do this:
INSERT INTO Table_X VALUES ('Val_X1_1',1),('Val_X1_2',1),('Val_X1_3',1)
INSERT INTO Table_X VALUES ('Val_X2_1',2),('Val_X2_2',2),('Val_X2_3',2)
INSERT INTO Table_X VALUES ('Val_X3_1',3),('Val_X3_2',3),('Val_X3_3',3)
Much easier to query without dynamics:
--no
SELECT * FROM Table_X1
--yes
SELECY * FROM Table_X WHERE X_Number = 1
You've indicated you're stuck with it how it is, so you'll need to create and run your queries dynamically too. This is c#/vb flavored pseudocode:
string sql = "SELECT * FROM sometable"
for int x = 1 to 3
sql = sql + " table_x{x} on sometable.id = table_x{x}.id"
Or perhaps build a Union:
string sql = "WITH allx AS (SELECT * FROM table_x1"
for int x = 2 to 10
sql = sql + " UNION ALL SELECT * FROM table_x{x}"
sql = sql + ") select * from sometable inner join allx on..."
But I echo larnu's sentiments in the comments.. if you truly cannot change the tables that are created, consider creating a VIEW in a similar way to the UNION code above, that will sit alongside X number of tables and will provide a way to query without dynamic:
CREATE VIEW AllX AS(
SELECT x.*, 1 as Which FROM TABLE_X1 x
UNION ALL SELECT x.*, 2 as Which FROM TABLE_X2 x
UNION ALL SELECT x.*, 3 as Which FROM TABLE_X3 x
...
Use the same technique that creates 10 tables to string together a CREATE VIEW statement that views over the 10 tables, then you can query the view without Dynamic sql generation

I have a scenario when i need to insert into table from same table after changing some column. Issue is key Column

let say,
insert into A select * from A where col1 = "ABC"
leads to an error as there would be the same primary key column, I want to increment automatically from the max id the table have
CREATE TABLE A(
[WFID] [int] NOT NULL PRIMARY KEY,
[EntityID1] [int] NOT NULL,
[EntityID2] [int] NULL);
INSERT INTO WFCustom
SELECT * FROM WFCustom
WHERE EntityID2 = 6008 ,
getting an error as WFID is a primary key :
Violation of PRIMARY KEY constraint 'PK_WF_Custom'. Cannot insert duplicate key in object 'dbo.WFCustom'.
The statement has been terminated.
You can try following query using ROW_NUMBER()
INSERT INTO wfcustom
SELECT (SELECT Max(wfid)
FROM WFCustom) + (Row_number() OVER(ORDER BY (SELECT 1))) AS id,
entityid1,
entityid2
FROM wfcustom
Online Demo
Note:
As suggested by #DanGuzman, insert can fail in scenarios when same query is running from multiple sessions under read committed isolation level. So it's always advisable to use Identity column for this type of scenarios.
My first suggestion is to fix your data model. This would look like:
CREATE TABLE A (
[WFID] int IDENTITY(1, 1) PRIMARY KEY,
[EntityID1] [int] NOT NULL,
[EntityID2] [int] NULL
);
INSERT INTO WFCustom (EntityID1, EntityID2)
SELECT EntityID1, EntityID2
FROM WFCustom
WHERE EntityID2 = 6008;
This is the safest method for being sure that primary keys are unique -- the database takes care of it.
If that doesn't work, you can assign a new one. The method proposed by PSK is fine, although I would write it as:
INSERT INTO WFCustom (WFID, EntityID1, EntityID2)
SELECT (COALESCE(MAX_WFID, 0) +
ROW_NUMBER() OVER (ORDER BY WFID)
) as new_WFID,
EntityID1, EntityID2
FROM WFCustom CROSS JOIN
(SELECT MAX(WFID) as MAX_WFID FROM WFCustom) m
WHERE EntityID2 = 6008;

SQL violation of Primary Key constraint on update

--EDITED--
I think I figured out the problem. The original row is being taken to update instead of the latest row therefore the CreationDate is the same and since ID + CreationDate is the primary key it returns a violation. Is there any way to select the latest row instead of the original row when updating records?
Thanks :D
----------------
I got the error violation of primary key constraint but I don't know why because my primary key values are unique.
I can add the record for 'Darren' and update it once. After that I get the error. My trigger works such that when I update an existing record, both the original and edited record are inserted into the table ProcessList so that I am able to see all the changes made to all records.
Tables:
CREATE TABLE dbo.ProcessList
(
TransactionID integer IDENTITY,
IsEdited bit DEFAULT 'FALSE',
ID integer NOT NULL,
Name varchar(30) NOT NULL,
Amount smallmoney NOT NULL,
CreationDate datetime DEFAULT GETDATE(),
ModificationDate datetime,
PRIMARY KEY (ID, CreationDate)
)
CREATE TABLE dbo.ProcessListHist
(
TransactionID integer IDENTITY,
IsEdited bit DEFAULT 'FALSE',
ID integer NOT NULL,
Name varchar(30) NOT NULL,
Amount smallmoney NOT NULL,
CreationDate datetime DEFAULT GETDATE(),
ModificationDate datetime,
PRIMARY KEY (ID, CreationDate)
)
Trigger:
CREATE TRIGGER CloneAfterUpdate ON ProcessList
AFTER UPDATE
AS
IF (UPDATE (Amount) OR UPDATE (NAME))
BEGIN
INSERT INTO ProcessListHist (ID, Name, Amount, CreationDate, ModificationDate, IsEdited)
SELECT
ID, Name, Amount, CreationDate, GETDATE(), 'True'
FROM deleted
UPDATE PL
SET PL.CreationDate = PLH.ModificationDate
FROM ProcessList PL
INNER JOIN deleted ON PL.ID = deleted.ID
AND PL.CreationDate = deleted.CreationDate
INNER JOIN ProcessListHist PLH ON PL.ID = PLH.ID
AND PLH.CreationDate = deleted.CreationDate
INSERT INTO ProcessList (ID, Name, Amount, CreationDate, ModificationDate, IsEdited)
SELECT
ID, Name, Amount, CreationDate, ModificationDate, IsEdited
FROM ProcessListHist
END
Insert/Update statements:
INSERT INTO ProcessList (ID, Name, Amount) VALUES ('1020', 'Darren', '300')
UPDATE ProcessList SET Amount = 1000 WHERE Name = 'Darren'
You logging a transaction on your history table right ? , therefore you need to remove the ID of history table from being unique. Make it TransactionID INT only, not an identity, your problem is because every time you perform an UPDATE , the record is inserted into history table, so if the record already exist meaning to say, if the ID is already existing in the history table , you are not allowed to insert the same ID, that's why you will receiving that error.

SQL performance width versus depth

I have a customers table that holds information about customer prefrences like if he wants to receive a newsletter and so on. If he/she wants to receive a newsletter, the value is stored in the column "customerNewsletter" and set to true. However I have a couple of these bit values and parameters that are in a column of there own. I store dates, true/false, integers and tekst like this for each customer.
I find that about 80% of my customers wants to receive a newsletter and that makes that 80% of the values is set to true. I now store a value for each customer set to false or true. What if I only should have to store the 20% set to false ??
There is a list of about 20 of these parameters that I could include as a column (they are now), but I was wondering if there is a better way.
So I create 3 tables to hold these parameter values, a param table holding the actual value, a paramsNames table, that holds the names of the values and a params table that connects the parameters to a customerID
SELECT
customerParamsName as [Name],
customerParamText as [Text],
customerParamINT as [int],
customerParamsDateTime as [Date]
FROM db14.customerParams
INNER JOIN db14.customerParam ON customerParamsChildID = customerParamID
INNER JOIN db14.customerParamsNames ON customerParamNameID = customerParamsNameID
This would give me
Name Text int Date
Phonenumber NULL 615164898 2013-09-20 00:00:00.000
Can anyone tell me if this is a good way to go, or are there more common ways of storing Multi-Type parameters more efficiently ?
AFTER some MORE consideration
I have created 2 tables
customerParam
paramID paramNameID ParamParentID paramChildID paramText paramINT paramDate
INT TINYINT INT INT varchar(24) INT DATETIME
PRIMARY INDEXED
customerParamNames
paramNameID paramName
TINYINT VARCHAR(24)
PRIMARY
1 'FirstName'
2 'LastName'
3 'Email Address'
4 'Phonenumber'
5 etc..
Let's say I want to store the firstName and LastName
I create records in customerParam for both values ;
paramID paramNameID ParamParentID paramChildID paramText paramINT paramDate
17456 1 'John'
17467 2 'Doo'
17468 1 752 17456
17469 2 752 17467
As I expect more occurrences for the name ‘John’ I am storing it as an independent value, then joining it using the parentID/ChildID combination.
and for the phoneNumber
17470 4 752 31615164899
17471 5 752 'me#here.com'
The phonenumber is very explicit to this customer, I am using the parentID to join it straight to the customer. The same goes for the emailaddress.
At this time this solution looks like the way to go... I am also still looking at the xml approach but I don’t have a good understanding on how to use XQuery and xmlDocuments stored in a database.
And It seems like a lot of overhead.
I will move forward with the solution above... until someone gives me a better one.
Example SQL
DECLARE #paramNames TABLE (paramNameID TINYINT, paramName varchar(24))
DECLARE #param TABLE (paramID INT, paramNameID TINYINT, paramParentID INT, paramChildID INT, paramText varchar(24), paramINT INT, paramDate datetime)
INSERT INTO #paramNames VALUES ( 1, 'firstname')
INSERT INTO #paramNames VALUES ( 2, 'lastname')
INSERT INTO #paramNames VALUES ( 3, 'emailaddress')
INSERT INTO #paramNames VALUES ( 4, 'phonenumber')
select * from #paramNames
INSERT INTO #param VALUES (1, 1, Null, Null, 'John' , Null, Null)
INSERT INTO #param VALUES (2, 2, Null, Null, 'Doo' , Null, Null)
INSERT INTO #param VALUES (3, 1, 752, 1, Null , Null, Null)
INSERT INTO #param VALUES (4, 2, 752, 2, Null , Null, Null)
INSERT INTO #param VALUES (5, 4, 752, Null, Null , 615164899, Null)
INSERT INTO #param VALUES (5, 3, 752, Null, 'me#here.com' , Null, Null)
select
a.paramParentID, b.paramName, c.paramText, c.paramINT, c.paramDate
from #param a
inner join #paramNames b on a.paramNameID = b.paramNameID
inner join #param c on a.paramChildID = c.paramID
UNION ALL
select
a.paramParentID, b.paramName, a.paramText, a.paramINT, a.paramDate
from #param a
inner join #paramNames b on a.paramNameID = b.paramNameID
WHERE paramParentID IS NOT NULL
AND paramChildID IS NULL
giving the result
paramParentID paramName paramText paramINT paramDate
752 firstname John NULL NULL
752 lastname Doo NULL NULL
752 phonenumber NULL 615164899 NULL
752 emailaddress me#here.com NULL NULL
I would approach this a little differently if you have performance and flexibility in mind.
USE Test;
CREATE TABLE Customers
(
CustomerID INT NOT NULL CONSTRAINT PK_Customers
PRIMARY KEY CLUSTERED IDENTITY(1,1)
, CustomerName NVARCHAR(255)
);
CREATE TABLE CustomersReceivingEmails
(
CustomerID INT NOT NULL CONSTRAINT FK_CustomerID
FOREIGN KEY REFERENCES Customers (CustomerID)
ON DELETE CASCADE ON UPDATE CASCADE
, EmailAddress NVARCHAR(255) NOT NULL
CONSTRAINT PK_CustomersReceivingEmails
PRIMARY KEY CLUSTERED (CustomerID, EmailAddress)
);
INSERT INTO Customers (CustomerName) VALUES ('Max');
INSERT INTO Customers (CustomerName) VALUES ('Mike');
INSERT INTO CustomersReceivingEmails (CustomerID, EmailAddress)
VALUES (1, 'us#them.com');
INSERT INTO CustomersReceivingEmails (CustomerID, EmailAddress)
VALUES (1, 'us#me.com');
/* ALL Customers */
SELECT * FROM Customers;
/* Only customers who wish to receive Emails, allows a given customer
to have multiple email addresses */
SELECT C.CustomerName, E.EmailAddress
FROM Customers C
INNER JOIN CustomersReceivingEmails E ON C.CustomerID = E.CustomerID
ORDER BY C.CustomerName, E.EmailAddress;
The SELECT returns rows like this:
This allows the Customers table to contain all customers regardless of their preference for emails.
The CustomersReceivingEmails table has a foreign key to Customers.CustomerID for customers who want to receive emails.
Your second solution is a variant of what is commonly known as Entity-Attribute-Value data model. This approach appears to be flexible. However, it essentially generates a schema within schema and is very slow to query as the number of attributes increases
If you're storing a lot of identical values, have a look at columnstore indexes. They work well in scenarios where selectivity is low (lots of rows & only a small number of distinct values)

Need Help Writing SQL Query in Postgresql

I've been trying to write this query but can't seem to get it right for some reason or another..
What I need to do is:
Change the status of a question to 'closed' if there has not been an update associated with this question inserted into the qUpdateTable in the last 24 hours.
I only want it to be closed if a staff member has replied to it at least once.
You can determine if a staff member or a user has replied to the question by checking the qUpdateTable and seeing if a the StaffID field is empty or has a value for that particular tickets updates. If there is a staffID then it has been updated by a staff member, however if it does not then the qUpdate was done by a user.
Essentialy the way this works is a user posts a question by inserting into the Question table, and replies are made by inserting into the qUpdate table and linked to the original question using the foreign key - "QuestionID".
The tables:
CREATE TABLE Staff
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL
);
CREATE TABLE Customer
(
ID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL,
Email VARCHAR(40) NOT NULL
);
CREATE TABLE Product
(
ID INTEGER NOT NULL PRIMARY KEY,
Name TEXT NOT NULL
);
CREATE TABLE Question
(
ID INTEGER NOT NULL PRIMARY KEY,
Problem VARCHAR(1000),
Status VARCHAR(20) NOT NULL DEFAULT 'open',
Priority INTEGER NOT NULL,
LoggedTime TIMESTAMP NOT NULL,
CustomerID INTEGER NOT NULL,
ProductID INTEGER NOT NULL,
FOREIGN KEY (ProductID) REFERENCES Product(ID),
FOREIGN KEY (CustomerID) REFERENCES Customer(ID),
CHECK (Status IN ('open','closed') AND Priority IN (1,2,3))
);
CREATE TABLE qUpdate
(
ID INTEGER NOT NULL PRIMARY KEY,
Message VARCHAR(1000) NOT NULL,
UpdateTime TIMESTAMP NOT NULL,
QuestionID INTEGER NOT NULL,
StaffID INTEGER,
FOREIGN KEY (StaffID) REFERENCES Staff(ID),
FOREIGN KEY (QuestionID) REFERENCES Question(ID)
);
Some sample inserts:
INSERT INTO Customer (ID, Name, Email) VALUES (1, 'testname1', 'testemail1');
INSERT INTO Customer (ID, Name, Email) VALUES (2, 'testname2', 'testemail2');
INSERT INTO Staff (ID, Name) VALUES (1, 'Don Keigh');
INSERT INTO Product (ID, Name) VALUES (1, 'Xbox');
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (1, 'testproblem1', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO Question (ID, Problem, Status, Priority, LoggedTime, CustomerID, ProductID)
VALUES (2, 'testproblem2', 'open', 3, '2012-04-14 09:30', 2, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, StaffID, QuestionID) VALUES (2, 'testmessage1','2012-07-12 14:27', 1, 1);
INSERT INTO qUpdate (ID, Message, UpdateTime, QuestionID) VALUES (3, 'testmessage1','2012-06-18 19:42', 2);
What I've done so far (which obviously doesn't work)
UPDATE Question
SET Status = 'closed'
WHERE EXISTS
(SELECT qUpdate.QuestionID
MAX(qUpdate.UpdateTime - Now() = INTERVAL '1 day') FROM qUpdate
LEFT JOIN Question ON qUpdate.QuestionID = Question.ID
WHERE qUpdate.StaffID IS NOT NULL);
I realise my explanation may be a bit confusing so if you need more info post and I'll reply ASAP
UPDATE Question
SET Status = 'closed'
where
-- this clause asserts there's at least one staff answer
exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and StaffID is not null
)
-- this clause asserts there's been no update in the last 24 hours
and not exists (
select null from qUpdate
where qUpdate.QuestionID = Question.ID
and qUpdate.UpdateTime > (now() - interval '24 hours')
)
and Status = 'open';
You'll almost certainly want an index on qUpdate(QuestionID) or possibly qUpdate(QuestionID,UpdateTime) or qUpdate(QuestionID,StaffID) to get good performance on the subselects in the exists() tests.