UPDATE the same way as INSERT using VALUES? - sql

I was wondering if I can use UPDATE without explicitly stating column = value. Something like this:
UPDATE table_name SET (column1, column2, column3, ...)
VALUES (value1, value2, value, ...);
The reason I need this is that I'm getting the columns and values from an array and it's dynamic for different tables with different column names and different amounts of columns, so I want to accomplish something like this in NodeJS:
BEGIN
IF NOT EXISTS (SELECT * FROM ${t_name} WHERE ${Primary_key} = ${Primary_key_value)
BEGIN
INSERT INTO ${t_name} (${cols})
VALUES (${inputs})
END
ELSE
BEGIN
UPDATE ${t_name} SET (${cols})
VALUES (${inputs})
WHERE ${Primary_key} = ${primary_key_value)
END
END
Which is essentially replacing a row that has some id with a new row having the same id.
Is there a way to accomplish something like that?

Yes, you use CROSS APPLY. I don't follow the way you are building the query, but here is the raw SQL update:
UPDATE T SET
Col1 = X.Value1
, Col2 = X.Value2
FROM MyTable T
CROSS APPLY (VALUES ('Value1','Value2')) AS X (Value2, Value2)
WHERE T.id = #Id;

You don't actually need an explicit cross apply. Just use:
UPDATE T
SET Col1 = V.Value1,
Col2 = V.Value2
FROM (VALUES ('Value1','Value2')) V(Value2, Value2)
WHERE T.id = #Id;

Related

SQL Server: how to delete rows with matching ID that were not affected by MERGE?

I have a table with an auto incrementing id and a unique constraint across two columns, keycol1 and keycol2.
Suppose the table has this data:
H| (id, keycol1, keycol2, col1, col2)
| (1, 'A', 'B', 'A', 'E' )
| (2, 'A', 'C', 'J', 'K' )
| (3, 'A', 'D', 'H', 'I' )
I then use a MERGE statement to update all the records corresponding to keycol1:
MERGE tablename trg
USING (VALUES ('A','B','C','D'),
('A','C','E','F'),
('A','E','K','F'),
('A','F','L','M')) src(keycol1, keycol2, col1, col2)
ON trg.keycol = src.keycol AND trg.keycol2 = src.keycol2
WHEN MATCHED THEN
UPDATE
SET col1 = src.col1, col2 = src.col2
WHEN NOT MATCHED THEN
INSERT (keycol1, keycol2, col1, col2)
VALUES (src.keycol1, src.keycol2, src.col1, src.col2);
This works great to update existing records or add additional records as needed. In our example, rows 1 and 2 will be updated, and rows 4 and 5 will be inserted.
How can I modify the query to delete any rows in tablename where keycol1 = 'A' that were not affected by the MERGE statement? In our example, row 3 should be deleted.
Prior to trying merge, I attempted to solve this by using a DELETE followed by INSERTing multiple values in one transaction. This had the effect of auto incrementing the ID column each time. Since the merge operation would be frequent and there is a lot more than 3 rows involved in each transaction, I'm trying to find something more efficient and avoid the risk of overflowing the id column.
You can use the WHEN NOT MATCHED BY SOURCE THEN DELETE clause to achieve this. However, do not just do the following:
MERGE tablename trg
USING (VALUES ('A','B','C','D'),
('A','C','E','F'),
('A','E','K','F'),
('A','F','L','M')) src(keycol1, keycol2, col1, col2)
ON trg.keycol = src.keycol AND trg.keycol2 = src.keycol2
WHEN MATCHED THEN
UPDATE SET col1 = src.col1, col2 = src.col2
WHEN NOT MATCHED BY TARGET THEN
INSERT(keycol1, keycol2, col1, col2)
VALUES(src.keycol1, src.keycol2, src.col1, src.col2)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
This is something that trips up a lot of people: when using WHEN NOT MATCHED BY SOURCE THEN DELETE in a MERGE, you need to be aware that all non-matching rows will be deleted. Therefore, if you only want to delete a subset of the rows in the target table, you must pre-filter the target, either with a view or a CTE. For example:
WITH trg AS (
SELECT *
FROM tablename
WHERE keycol = 'A'
)
MERGE tablename trg
USING (VALUES ('A','B','C','D'),
('A','C','E','F'),
('A','E','K','F'),
('A','F','L','M')) src(keycol1, keycol2, col1, col2)
ON trg.keycol = src.keycol AND trg.keycol2 = src.keycol2
WHEN MATCHED THEN
UPDATE SET col1 = src.col1, col2 = src.col2
WHEN NOT MATCHED BY TARGET THEN
INSERT(keycol1, keycol2, col1, col2)
VALUES(src.keycol1, src.keycol2, src.col1, src.col2)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
Regarding issues with your IDENTITY column, I suggest you make it a bigint to give you more scope.
Try adding another column delete_flag to your table
Before running the merge block, update all the records delete_flag to 0.
In the merge block, for both insert and update make the value as 1 for the delete_flag column.
Once merge is complete, delete the records that have delete_flag as 0.
update tablename set delete_flag = 0
MERGE tablename trg
USING (VALUES ('A','B','C','D',1),
('A','C','E','F',1),
('A','E','K','F',1),
('A','F','L','M',1)) src(keycol1, keycol2, col1, col2,delete_flag)
ON trg.keycol = src.keycol AND trg.keycol2 = src.keycol2
WHEN MATCHED THEN
UPDATE SET col1 = src.col1, col2 = src.col2, delete_flag = 1
WHEN NOT MATCHED THEN
INSERT(keycol1, keycol2, col1, col2,delete_flag)
VALUES(src.keycol1, src.keycol2, src.col1, src.col2,1);
delete from tablename where delete_flag = 1
Hope this solves your problem, happy to discuss it further.

How to run an SQL UPDATE query where a field is modified and then used

Is the following posible on SQL? (Using SQLITE3)
UPDATE Table SET
Value1 = (SELECT ...)
Value2 = Value3/Value1; # The new value1!!! Not the value currently on the DB
Right now I need to do something like:
UPDATE Table SET
Value1 = (SELECT ...)
Value2 = Value3/(SELECT ...);
This makes the code quite long. Imagine it is not only Value1 I am updating but other fields too.
So the only solution I found is to do 2 UPDATES. First:
UPDATE Table SET
Value1 = (SELECT ...);
And then
UPDATE Table SET
Value2 = Value3/Value1;
Anyone with a more "beautiful" way of doing this?
Thanks,
In SQLite 3.8.3 or later, you can use a common table expression:
WITH u(v) AS (
SELECT ...
)
UPDATE MyTable
SET Value1 = (SELECT v FROM u),
Value2 = Value3 / (SELECT v FROM u);
(This even works with correlated subqueries.)
Maybe you can try replace
replace into table2
(Value1 , Value2)
select src.Value1 , src.Value3 / src.Value1
from table1 src
In sql you cant do UDPATE ... FROM as explain in this answer
You can do like this :
update Table1 set szNo = KO.nNo
from
(select nNo From Table2) KO

Two or more results of one CASE statement in SQL

Is it possible to SELECT value of two or more columns with one shot of CASE statement? I mean instead of:
select
ColumnA = case when CheckColumn='condition' then 'result1' end
,ColumnB = case when CheckColumn='condition' then 'result2' end
Something like:
select case when CheckColumn='condition' then ColumnA='result1', ColumnB='result2' end
UPDATE
Just the same as we can do with the UPDATE statement:
update CTE
set ColumnA='result1', ColumnB='result2'
where CheckColumn='condition'
It is not possible with CASE expression.
For every column you need new CASE
It is not possible, but you could use a table value constructor as a work around to this, to store each value for columna and columnb against your check column:
SELECT t.CheckColumn,
v.ColumnA,
v.ColumnB
FROM dbo.YourTable AS t
LEFT JOIN
(VALUES
('Condition1', 'Result1', 'Result2'),
('Condition2', 'Result3', 'Result4'),
('Condition3', 'Result5', 'Result6')
) AS v (CheckColumn, ColumnA, ColumnB)
ON v.CheckColumn = t.CheckColumn;
If you have more complex conditions, then you can still apply this logic, but just use a pseudo-result for the join:
SELECT t.CheckColumn,
v.ColumnA,
v.ColumnB
FROM dbo.YourTable AS t
LEFT JOIN
(VALUES
(1, 'Result1', 'Result2'),
(2, 'Result3', 'Result4'),
(3, 'Result5', 'Result6')
) AS v (ConditionID, ColumnA, ColumnB)
ON v.ConditionID = CASE WHEN <some long expression> THEN 1
WHEN <some other long expression> THEN 2
ELSE 3
END;
The equivalent select to the update is:
select 'result1', 'result2'
. . .
where CheckColumn = 'condition';
Your select is different because it produces NULL values. There is an arcane way you can essentially do this with outer apply:
select t2.*
from . . . outer apply
(select t.*
from (select 'result1' as col1, 'result2' as col2) t
where CheckColumn = 'condition'
) t2;
This will return NULL values when there is no match. And, you can have as many columns as you would like.
What I understood from your question is that you want to update multiple columns if certain condition is true.
For such situation you have to use MERGE statements.
Example of using MERGE is as given on msdn here.
Code example:
-- MERGE statement for update.
USE [Database Name];
GO
MERGE Inventory ity
USING Order ord
ON ity.ProductID = ord.ProductID
WHEN MATCHED THEN
UPDATE
SET ity.Quantity = ity.Quantity - ord.Quantity;
More MERGE statement example here.
You could solve this maybe with a CTE or a CROSS APPLY, somehting like
DECLARE #tbl2 TABLE(inx INT, val1 VARCHAR(10),val2 VARCHAR(10));
INSERT INTO #tbl2 VALUES(1,'value1a','value1b'),(2,'value2a','value2b'),(3,'value2a','value2b');
UPDATE yourTable SET col1=subTable.val1,col2=subTable.val2
FROM yourTable
CROSS APPLY(
SELECT val1,val2
FROM #tbl2
WHERE inx=1 --YourCondition
) AS subTable

INSERT INTO SELECT + 1 custom column

I need to copy data from original table and add custom column specified in query
Original table struct: col1, col2, col3
Insert table struct: x, col1, col2, col3
INSERT INTO newtable
SELECT *
FROM original
WHERE cond
and I'm getting this error
Column count doesn't match value count at row 1
HOW can I insert X value in this single query?
I tought something like this can pass
INSERT INTO newtable
SELECT 'x' = NULL, *
FROM original
WHERE cond
Any ideas?
Is it possible to use *? Because that table has so many columns and X has to be first value
I know this all is bad but I have to edit unbeliveable ugly db with even worse php code
The second statement is almost correct, but instead of 'x' = null, use null x (I'm assuming you want to store a null value in a column named x);
INSERT INTO newtable
SELECT null x, o.* FROM original o WHERE cond
Select Null as X, *
into newtable
from original
where ...
INSERT INTO newtable
SELECT null as x, col1, col2, col3 FROM original WHERE cond

How to add results of a SELECT to a table

I have a SQL select statement which is comparing two tables. I am getting the values where the rows are the same. Now I have got this in the procedure I need to add these into a new table (coftReconciliationMatches). The table has all the same columns but one additional one 'MatchOrNoMatch'. I need to pass through the values of the row that are matched and also need to pass through 'Match' to the column 'MatchOrNoMatch'
This is the current part of the SQL script I have;
SELECT *
FROM dbo.coftReconciliationFileInfo AS a
WHERE EXISTS (SELECT *
FROM dbo.coftPreReconciliationInfo AS b
WHERE a.Rec_PK = b.Rec_PK
AND a.ExtRef1 = b.ExtRef1
AND a.SedolRef = b.SedolRef
AND a.ValLatest = b.ValLatest
AND a.Totunits = b.Totunits
AND a.FundsOrCash = b.FundsOrCash )
When comparing a lot of columns, the SELECT INTERSECT and SELECT EXCEPT commands can save you a lot of effort:
INSERT dbo.coftReconcilliationMatches
(Rec_PK, ExtRef1, SedolRef, ValLatest, Totunits, FundsOrCash, MatchOrNoMatch)
select Rec_PK, ExtRef1, SedolRef, ValLatest, Totunits, FundsOrCash, 'Match'
from dbo.coftReconciliationFileInfo
intersect select Rec_PK, ExtRef1, SedolRef, ValLatest, Totunits, FundsOrCash, 'Match'
from dbo.coftPreReconciliationFileInfo
(Check for typos!)
If you are creating the table on the fly (something I wouldn't recommend doing), you'd use SELECT INTO.
INSERT INTO [table] ([col1], [col2]) SELECT colx, ...
Use Select Into for this. See here http://www.w3schools.com/sql/sql_select_into.asp
Try something like:
INSERT INTO <your table> (<list of columns>)
SELECT <list of columns from select>, '<the additional column>' FROM dbo.coftReconciliationFileInfo AS a
WHERE EXISTS(SELECT * FROM dbo.coftPreReconciliationInfo AS b
WHERE a.Rec_PK = b.Rec_PK AND a.ExtRef1 = b.ExtRef1 AND a.SedolRef = b.SedolRef AND a.ValLatest = b.ValLatest AND a.Totunits = b.Totunits AND a.FundsOrCash = b.FundsOrCash )
also see http://www.1keydata.com/sql/sqlinsert.html
you need to do:
INSERT INTO Table (column1, column2..)
(SELECT column1, column2 ....
FROM dbo.coftReconciliationFileInfo AS a
WHERE EXISTS (SELECT *
FROM dbo.coftPreReconciliationInfo AS b
WHERE a.Rec_PK = b.Rec_PK
AND a.ExtRef1 = b.ExtRef1
AND a.SedolRef = b.SedolRef
AND a.ValLatest = b.ValLatest
AND a.Totunits = b.Totunits
AND a.FundsOrCash = b.FundsOrCash ))