Updating duplicate values; - sql

I have a table which contains unique indexes;lets say
A B
1 1
1 2
1 3
1 4
And i want to update the B column,since sql updates them one by one if it tries to update the first column to 2 for ex i get::
A B
1 2
1 2
1 3
1 4
And as a result i would get two duplicate values at the 1st and 2nd row and of course an error message.
And i should update the table like that:
A B
1 2
1 1
1 3
1 4
So whats the course of action i should follow in case of this scenario?
Regards.
Maybe i should update the question abit:
What if i wanted to change the b column completely; such as:
A B
1 4
1 2
1 3
1 1

Try this
UPDATE tbl
SET B = 3 - B
WHERE A = 1 AND B IN (1, 2)
Or, generaly, you can use something like that:
UPDATE tbl
SET B = CASE B
WHEN 1 THEN 2
WHEN 2 THEN 1
END
WHERE A = 1 AND B IN (1, 2)
Another way:
add column C
through your loop fill C column with new values
update field B from C:
UPDATE tbl
SET B = C

The solution is to do the swap in a single statement:
UPDATE YOUR_TABLE
SET B = (CASE B WHEN 1 THEN 2 ELSE 1 END)
WHERE A = 1 AND B IN (1, 2)
--- EDIT ---
To update several rows at once, you can do a JOIN-update from the temporary table:
CREATE TABLE #ROWS_TO_UPDATE (
A int,
B int,
NEW_B int,
PRIMARY KEY (A, B)
);
INSERT INTO #ROWS_TO_UPDATE (A, B, NEW_B) VALUES (1, 1, 4);
INSERT INTO #ROWS_TO_UPDATE (A, B, NEW_B) VALUES (1, 2, 3);
INSERT INTO #ROWS_TO_UPDATE (A, B, NEW_B) VALUES (1, 3, 2);
INSERT INTO #ROWS_TO_UPDATE (A, B, NEW_B) VALUES (1, 4, 1);
UPDATE YOUR_TABLE
SET B = NEW_B
FROM
YOUR_TABLE JOIN #ROWS_TO_UPDATE
ON YOUR_TABLE.A = #ROWS_TO_UPDATE.A AND YOUR_TABLE.B = #ROWS_TO_UPDATE.B;
DROP TABLE #ROWS_TO_UPDATE;
The above code transforms the following data...
A B
1 1
1 2
1 3
1 4
...to this:
A B
1 4
1 2
1 3
1 1

If I understand well, you have a primary key composed of two columns, and you want to swap the two first rows' PK.
If you don't have foreing keys which refers to this primary key, simply change one of the keys to a temporary unused value:
A B
1 10000
1 2
then change the second row:
A B
1 10000
1 1
Finally, change the first one:
A B
1 2
1 1
If you have objects depending on this primary key, you would have to make a copy of the other columns (for example the other columns of 1 1) to a "temporary row", copy the data of the second (1 2) to the first (1 1) and finally copy the "temporary row" to the second (1 2)
This all dependes on how and what you're exactly trying to do. Is it a stored procedure, is it a query... You should show more context.
You could apply this technique to an unlimited number of rows. You can also create a temporary table with key equivalences, and update your table from tha temporary table. So, it will be done in an atomic operation, and it won't violate the PK.
create table T
(A int, B int, C char(5),
primary key (A,B))
insert into T values(1,1,'first')
insert into T values(1,2,'secon')
insert into T values(1,3,'third')
create table #KeyChanges
(A int, B int, newA int, newB int)
insert into #KeyChanges values(1,1,1,3)
insert into #KeyChanges values(1,2,1,1)
insert into #KeyChanges values(1,3,1,2)
update T set T.A = KC.newA, T.B = KC.newB
from T
left join #KeyChanges as KC on T.A = KC.A and T.B = KC.B

Related

How to UPSERT multiple rows with individual values in one statement?

I've created the following table:
CREATE TABLE t1(
a INT UNIQUE,
b varchar(100) NOT NULL,
c INT,
d INT DEFAULT 0,
PRIMARY KEY (a,b));
On a single row this SQL statement works great (the SQL is generated in code):
INSERT INTO t1 (a, b, c, d)
VALUES($params.1, '${params.2}', $params.3, params.4)
ON CONFLICT (a,b) DO
UPDATE SET d=params.4
Is it possible to upsert multiple rows at once? For each update the value of params.4 is different.
var sqlStr = 'INSERT INTO t1 (a, b, c, d) VALUES '
for(let i =0 i < params.length; i++){
sqlStr += `(${params[i].1}, '${params[i].2}', ${params[i].3}, ${params[i].4}),`
}
sqlStr = sqlStr.substring(0, sqlStr .length - 2) +')';
sqlStr += 'ON CONFLICT (a,b) DO UPDATE SET **d=???**' <-- this is the problem
params[i].4 has different value for each row and the ON CONFLICT statement appears only once (not per row) and SET doesn't support a WHERE.
Example, if my table has the following rows:
a | b | c | d
---+---+---+---
1 | 1 | 1 | 1
2 | 2 | 2 | 2
And my new input is [(1,'1',1,11),(2,'2',2,22),(3,'3',3,3)].
There are two conflicts - (1,1) and (2,2). The result should be:
a | b | c | d
---+---+---+---
1 | 1 | 1 | 11
2 | 2 | 2 | 22
3 | 3 | 3 | 3
UPSERT (INSERT ... ON CONFLICT ... DO UPDATE) keeps track of excluded rows in the special table named EXCLUDED automatically. The manual:
Note that the special excluded table is used to reference values originally proposed for insertion
So it's really very simple:
INSERT INTO t1 (a, b, c, d)
VALUES (...)
ON CONFLICT (a,b) DO UPDATE
SET d = EXCLUDED.d; -- that's all !!!
Besides being much simpler and faster, there is a subtle corner-case difference from your proposed solution. The manual:
Note that the effects of all per-row BEFORE INSERT triggers are
reflected in excluded values, since those effects may have contributed
to the row being excluded from insertion.
Plus, column DEFAULT values are already applied in the EXCLUDED row, where no input was given. (Like DEFAULT 0 for your column d.)
Both are typically what you want.
Writing the solution for future users dealing with the same issue
Step 1: Create a temporary table (clone of t1)
CREATE TABLE t2(
a INT UNIQUE,
b varchar(100) NOT NULL,
c INT,
d INT DEFAULT 0,
PRIMARY KEY (a,b));
OR
create table t2 as (select * from t1) with no data;
Step 2: Insert the new input to t2
INSERT INTO t2 (a, b, c, d)
values (1,'1',1,1),(2,'2',2,2),(3,'3',3,3)`
Step 3: UPSERT to t1 in case of conflict select d from t2
INSERT INTO t1 (a, b, c, d)
VALUES (1,'1',1,1),(2,'2',2,2),(3,'3',3,3)
ON CONFLICT(a,b) DO UPDATE
SET d=(SELECT d FROM t2 WHERE t2.a=t1.a);
Step 4: delete t2
DROP TABLE t2;

Updating a list of data in a normalised table postgres

I have two tables. One is table A which contains an id. Table B is a normalised table that contains a foreign key to table A and some other column called value.
e.g.
Table B
| id | fk| value
Table A
|pk| ... |
Basically I have a list (of n length) of values that I want to insert into table B that are to one foreignKey e.g list = [a, b, c, d] key = 1. The problem is table B might already have these values so I only want to insert the ones that aren't already in that table, as well as delete the ones that aren't in my list.
list = [a, b, c, d], key = 1
table B
| id |fk | value
| 1 | 1 | a
| 2 | 1 | b
| 3 | 1 | e
Is there a way that I can insert only c and d from the list into the table and delete e from the table in one statement? My current attempt is to delete every entry that matches the key and then insert them all but I don't think this is the efficient way to do this.
Why not just truncate b and insert the new values?
truncate table b;
insert into b (fk, value)
<your list here>;
Or if key is a column in b and you want to delete all keys with a given value:
delete from b where key = 1;
insert into b (fk, value, key)
<your list here with "1" for the key>
This doesn't preserve the id column from b, but your question does not mention that as being important.
An alternative method would use CTEs:
with data(fk, value) as (
<your list here>
),
d as (
delete from b
where (b.fk, b.value) not in (select d.fk, d.value from data d)
)
insert into b (fk, value)
select d.fk, d.value
from data d
where (d.fk, d.value) not in (select b.fk, b.value from b);

how to remove duplicate records but need to keep their child table rows and tag them to survived row

can you please help me in writing the query for both table
database : sql server
master_table
primary name
1 a
2 a
3 a
4 b
5 b
6 c
7 c
foreign
key
reference
above table
1 aa
2 aaa
3 aaaa
4 bb
5 bbb
6 cc
7 ccc
expected output
now I need to remove duplicate from above table based upon name
after removing duplicates name
master_table
primary name
1 a
4 b
6 c
to remove duplicate records but need to keep their child table rows and tag them to survived row
foreign
key
reference
above table
fk name_city
1 aa
1 aaa
1 aaaa
4 bb
4 bbb
6 cc
6 ccc
can you please help me in writing the query for both table
database : sql server
Thanks Gordon Linoff for reply
Let me add more detail
how I think it can be done
added rownum to master_table based upon duplicated on name
primary name row_num
1 a 1
2 a 2
3 a 3
4 b 1
5 b 2
6 c 1
7 c 2
foreign
key
reference
above
table
fk name_city (map_name |get primarykey from above
based | table with joining condition
upon |map_name=name
matching |and rownum = 1)
fk
with
primary )
-----------------------------------------------------------------------
1 aa a 1
2 aaa a 1
3 aaaa a 1
4 bb b 4
5 bbb b 4
6 cc c 6
7 ccc c 6
Please suggest if this is the right way
Thanks a lot for your time and kind
CREATE TABLE #master (ID INT, Name VARCHAR(50)); --DROP TABLE #master
INSERT INTO #master VALUES (1, 'a')
INSERT INTO #master VALUES (2, 'a')
INSERT INTO #master VALUES (3, 'a')
INSERT INTO #master VALUES (4, 'b')
INSERT INTO #master VALUES (5, 'b')
INSERT INTO #master VALUES (6, 'c')
INSERT INTO #master VALUES (7, 'c')
-- create temporary mapping table
;WITH cte AS
(
SELECT ID, MIN(ID) OVER (PARTITION BY Name) AS [MinID]
FROM #master
)
SELECT *
INTO #TempMapping -- DROP TABLE #TempMapping
FROM cte
WHERE cte.ID <> cte.MinID;
-- check to make sure that the IDs mapped as expected
SELECT * FROM #TempMapping;
-- change FKed values to their respective MIN mappings
UPDATE nc
SET nc.fk = tmp.MinID
FROM name_city nc
INNER JOIN #TempMapping tmp
ON tmp.ID = nc.fk;
-- remove non-MIN IDs from master now that nothing references them
DELETE mstr
FROM #master mstr
INNER JOIN #TempMapping tmp
ON tmp.ID = mstr.ID;
If there are a lot of rows in the [name_city] table or concurrency issues (i.e. blocking), then the #TempMapping table should probably be a real table (e.g. "dbo.TempMasterMappings") instead of a temp table. At that point, you can do this one ID at a time in a loop to keep the transactions smaller and quicker. Just replace the UPDATE and DELETE queries above with the following (which can even be run from a stored procedure). This method will work for any number of millions of rows (assuming that there is an index on the [fk] field, which there should be anyway).
DECLARE #BatchSize INT; -- this can be an input param for a proc
SET #BatchSize = 5000;
DECLARE #CurrentIDtoChange INT,
#CurrentNewID INT;
BEGIN TRY
WHILE (1 = 1)
BEGIN
SELECT TOP (1)
#CurrentIDtoChange = map.ID,
#CurrentNewID = map.MinID
FROM dbo.TempMasterMappings map
ORDER BY map.ID ASC;
IF (##ROWCOUNT = 0)
BEGIN
DROP TABLE dbo.TempMasterMappings; -- clean up!
BREAK; -- exit outer loop
END;
WHILE (1 = 1)
BEGIN
UPDATE TOP (#BatchSize) nc
SET nc.fk = #CurrentNewID
FROM dbo.name_city nc
WHERE nc.fk = #CurrentIDtoChange
OPTION (OPTIMIZE FOR UNKNOWN);
IF (##ROWCOUNT = 0)
BEGIN
DELETE mstr -- clean up PK record
FROM dbo.[Master] mstr
WHERE mstr.ID = #CurrentIDtoChange;
DELETE tmm -- remove ID as it is fully migrated!
FROM dbo.TempMasterMappings tmm
WHERE tmm.ID = #CurrentIDtoChange;
BREAK; -- exit inner loop
END;
END;
END TRY
BEGIN CATCH
DECLARE #Message NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(#Message, 16, 1);
END CATCH;
You need to replace all the ids in the second table with the minimum matching id in the first, if I understand correctly.
This query should return the result set you want:
select mt.minid, name_city
from (select t.*, min(id) over (partition by name) as minid
from master_table t
) mt join
table2 t2
on t2.id = t.id;
It is unclear from the question whether you just want to get the right output, or whether you want to modify the tables. Updating the tables would basically be changing the above select to a similar update query and then deleting the extra rows from the master table.

sql query logic

I have following data set
a b c
`1` 2 3
3 6 9
9 2 11
As you can see column a's first value is fixed (i.e. 1), but from second row it picks up the value of column c of previous record.
Column b's values are random and column c's value is calculated as c = a + b
I need to write a sql query which will select this data in above format. I tried writing using lag function but couldn't achieve.
Please help.
Edit :
Column b exists in table only, a and c needs to calculated based on the values of b.
Hanumant
SQL> select a
2 , b
3 , c
4 from dual
5 model
6 dimension by (0 i)
7 measures (0 a, 0 b, 0 c)
8 rules iterate (5)
9 ( a[iteration_number] = nvl(c[iteration_number-1],1)
10 , b[iteration_number] = ceil(dbms_random.value(0,10))
11 , c[iteration_number] = a[iteration_number] + b[iteration_number]
12 )
13 order by i
14 /
A B C
---------- ---------- ----------
1 4 5
5 8 13
13 8 21
21 2 23
23 10 33
5 rows selected.
Regards,
Rob.
Without knowing the relation between the rows ,how can we calculate the sum of the previous row a and b column to current row a column .I have created two more column id and parent in the table to find the relation between the two rows.
parent is the column which tell us about the previous row ,and id is the primary key of the row .
create table test1 (a number ,b number ,c number ,id number ,parent number);
Insert into TEST1 (A, B, C, ID) Values (1, 2, 3, 1);
Insert into TEST1 (B, PARENT, ID) Values (6, 1, 2);
Insert into TEST1 (B, PARENT, ID) Values (4, 2, 3);
WITH recursive (a, b, c,rn) AS
(SELECT a,b,c,id rn
FROM test1
WHERE parent IS NULL
UNION ALL
SELECT (rec.a+ rec.b) a
,t1.b b
,(rec.a+ rec.b+t1.b) c
,t1.id rn
FROM recursive rec,test1 t1
WHERE t1.parent = rec.rn
)
SELECT a,b,c
FROM recursive;
The WITH keyword defines the name recursive for the subquery that is to follow
WITH recursive (a, b, c,rn) AS
Next comes the first part of the named subquery
SELECT a,b,c,id rn
FROM test1
WHERE parent IS NULL
The named subquery is a UNION ALL of two queries. This, the first query, defines the starting point for the recursion. As in my CONNECT BY query, I want to know what is the start with record.
Next up is the part that was most confusing :
SELECT (rec.a+ rec.b) a
,t1.b b
,(rec.a+ rec.b+t1.b) c
,t1.id rn
FROM recursive rec,test1 t1
WHERE t1.parent = rec.rn
This is how it works :
WITH query: 1. The parent query executes:
SELECT a,b,c
FROM recursive;
This triggers execution of the named subquery. 2 The first query in the subquery's union executes, giving us a seed row with which to begin the recursion:
SELECT a,b,c,id rn
FROM test1
WHERE parent IS NULL
The seed row in this case will be for id =1 having parent is null. Let's refer to the seed row from here on out as the "new results", new in the sense that we haven't finished processing them yet.
The second query in the subquery's union executes:
SELECT (rec.a+ rec.b) a
,t1.b b
,(rec.a+ rec.b+t1.b) c
,t1.id rn
FROM recursive rec,test1 t1
WHERE t1.parent = rec.rn

How to do I update existing records using a conditional clause?

I'm new to Oracle SQL so I have a question .. I have two tables, Table A and Table B .. Now Table A and Table B have the same column names, but in table A, only one column (named 'tracker') actually has data in it .. The rest of the columns in Table A are empty ... What I need to do is update each record in Table A, so that values for other columns are copied over from Table B, with the condition that the the 'tracker' columns value from Table A is matched with the 'tracker' column in Table B ..
Any ideas ?
MERGE INTO tableA a
USING tableB b
ON (a.tracker=b.tracker)
WHEN MATCHED THEN UPDATE SET
a.column1=b.column1,
a.column2=b.column2;
And if exist rows in B that does not exist in A:
MERGE INTO tableA a
USING tableB b
ON (a.tracker=b.tracker)
WHEN MATCHED THEN UPDATE SET
a.column1=b.column1,
a.column2=b.column2
WHEN NOT MATCHED THEN INSERT VALUES
a.tracker,a.column1,a.column2; --all columns
create table a (somedata varchar2(50), tracker number , constraint pk_a primary key (tracker));
create table b (somedata varchar2(50), tracker number, constraint pk_b primary key (tracker));
/
--insert some data
insert into a (somedata, tracker)
select 'data-a-' || level, level
from dual
connect by level < 10;
insert into b (somedata, tracker)
select 'data-b-' || -level, level
from dual
connect by level < 10;
select * from a;
SOMEDATA TRACKER
-------------------------------------------------- -------
data-a-1 1
data-a-2 2
data-a-3 3
data-a-4 4
data-a-5 5
data-a-6 6
data-a-7 7
data-a-8 8
data-a-9 9
select * from b;
SOMEDATA TRACKER
-------------------------------------------------- -------
data-b--1 1
data-b--2 2
data-b--3 3
data-b--4 4
data-b--5 5
data-b--6 6
data-b--7 7
data-b--8 8
data-b--9 9
commit;
update (select a.somedata a_somedata, b.somedata b_somedata
from a
inner join
b
on a.tracker = b.tracker)
set
a_somedata = b_somedata;
select * from a; --see below for results--
--or you can do it this way: (issuing rollback to get data back in previous state)
--for a one column update, either way will work, I would prefer the former in case there is a multi-column update necessary
-- merge *as posted by another person* will also work
update a
set somedata = (select somedata
from b
where a.tracker = b.tracker
);
select * from A; --see below for results--
-- clean up
-- drop table a;
-- drop table b;
this will give you the results:
SOMEDATA TRACKER
-------------------------------------------------- -------
data-b--1 1
data-b--2 2
data-b--3 3
data-b--4 4
data-b--5 5
data-b--6 6
data-b--7 7
data-b--8 8
data-b--9 9
here is a link to oracle's documentation on UPDATE