Copy Data To Existing Rows Within Same Table in SQL Server - sql

In SQL Server 2008, I want to update some of the rows with data from another row. For example, given the sample data below:
ID | NAME | PRICE
---------------------------------------
1 | Yellow Widget | 2.99
2 | Red Widget | 4.99
3 | Green Widget | 4.99
4 | Blue Widget | 6.99
5 | Purple Widget | 1.99
6 | Orange Widget | 5.99
I want to update rows with ID 2, 3, and 5 to have the price of row 4.
I found a nice solution to update a single row at Update the same table in SQL Server that basically looks like:
DECLARE #src int = 4
,#dst int = 2 -- but what about 3 and 5 ?
UPDATE DST
SET DST.price = SRC.price
FROM widgets DST
JOIN widgets SRC ON SRC.ID = #src AND DST.ID = #dst;
But since I'm need to update multiple rows I'm not sure how the JOIN should look like. SRC.ID = #src AND DST.ID IN (2, 3, 5) ? (not sure if that's even valid SQL?)
Also, if anyone can explain how the solution above does not update all the rows in the table since there is no WHERE clause, that would be great!
Any thoughts? TIA!

You can use table variables to store the IDs to be updated:
DECLARE #tbl TABLE(ID INT PRIMARY KEY);
INSERT INTO #tbl VALUES (2), (3), (5);
DECLARE #destID INT = 4
UPDATE widgets
SET price = (SELECT price FROM widgets WHERE ID = #destID)
WHERE
ID IN(SELECT ID FROM #tbl)
Alternatively, you can store the source ID and destination ID in a single table variable. For this case, you need to store (2, 4), (3, 4) and (5, 4).
DECLARE #tbl TABLE(srcID INT, destID INT, PRIMARY KEY(srcID, destID));
INSERT INTO #tbl VALUES (2, 4), (3, 4), (5, 4);
UPDATE s
SET s.Price = d.Price
FROM widgets s
INNER JOIN #tbl t ON t.srcID = s.ID
INNER JOIN widgets d
ON d.ID = t.destID

Related

How to update multiple SQL records with same condition

I have this table
id | attributeId | value
--------------------------
1 | 1 | abc
2 | 1 | def
I want to update this table where "attributeId = 1" with these values {"123", "456", "789"} so the table will look like this:
id | attributeId | value
--------------------------
1 | 1 | 123
2 | 1 | 456
3 | 1 | 789
My idea is to delete all the old records and then add new records but I think there are more better method to do this. Is there any better way?
Consider following:
Alter table Your_Table DROP COLUMN VALUE
CREATE TABLE TEMP (ID INT, VALUE VARCHAR(3));
INSERT INTO TMP VALUES (1, '123'), (1, '456'), (1, '789');
SELECT A.*, B.VALUE INTO NEW_TABLE FROM Your_Table a join TMP b on a.id = b.id;
The new_table will have your requested structure.
If your goal is to replace the table, then just delete all the rows and insert new values:
truncate table t;
insert into t (id, attributeId, value)
values (1, 1, 123),
(2, 1, 456),
(3, 1, 789);
If you don't want the original rows that are not in the new data, I would not bother trying to figure out the differences between the tables. The truncate should be pretty fast and bulk updates are usually faster than update some records and insert some others.

Select one set of duplicate data

Using SQL Server, how do I select only one "set" of relationships? To explain, here is an example. Also if you can help me figure out how to "say" this problem to make it more google-able, that would be stellar. For example, the single table contains two identical, inverted rows.
CREATE TABLE #A (
EmployeeID INT,
EmployeeName NVARCHAR(100),
CoworkerID INT,
CoworkerName NVARCHAR(100)
)
INSERT INTO #A VALUES (1, 'Alice', 2, 'Bob')
INSERT INTO #A VALUES (2, 'Bob', 1, 'Alice')
INSERT INTO #A VALUES (3, 'Charlie', 4, 'Dan')
INSERT INTO #A VALUES (4, 'Dan', 3, 'Charlie')
SELECT *
FROM #A
// THIS IS WHAT WE WANT TO PROGRAMATICALLY DETERMINE - which ID's to keep and which to delete.
DELETE FROM #A WHERE EmployeeID = 2
DELETE FROM #A WHERE EmployeeID = 4
SELECT *
FROM #A
So, the net result of the final query is:
|------------|---------------|-------------|--------------|
| EmployeeID | EmployeeName | CoworkerID | CoworkerName |
|------------|---------------|-------------|--------------|
| 1 | Alice | 2 | Bob |
|------------|---------------|-------------|--------------|
| 3 | Charlie | 4 | Dan |
|------------|---------------|-------------|--------------|
Try this:
DELETE FROM #A
WHERE #A.EmployeeID > #a.CoworkerID
AND EXISTS(SELECT 1 FROM #A A2
where a2.CoworkerID = #A.EmployeeID
and a2.employeeID = #a.coworkerID)
See SqlFiddle
This will only delete those with a circular reference. If you have more complex chains, this will not affect them.
DELETE a
FROM #A a
WHERE EXISTS (
SELECT *
FROM #A t
WHERE t.CoworkerID = a.EmployeeID
AND t.EmployeeID < a.EmployeeID
)
WITH t2CTE AS
(
SELECT*, ROW_NUMBER() over (PARTITION BY employeename ORDER BY employeename) as counter1
FROM #a
)
DELETE FROM t2CTE WHERE counter1 >1

I dont understand what column ORA-01779 is refering too

I have a table with POI, you can have multiple POI on each city.
SQL DEMO
CREATE TABLE POI
("poi_id" int GENERATED BY DEFAULT AS IDENTITY,
"city_id" int,
PRIMARY KEY("poi_id")
);
Now there were some changes on the cities polygons and now have to reassign some POI
CREATE TABLE newCities
("city_id" int, "new_city_id" int)
;
DATA
INSERT ALL
INTO POI ("poi_id", "city_id")
VALUES (10, 1)
INTO POI ("poi_id", "city_id")
VALUES (11, 1)
INTO POI ("poi_id", "city_id")
VALUES (12, 2)
INTO POI ("poi_id", "city_id")
VALUES (13, 2)
INTO POI ("poi_id", "city_id")
VALUES (14, 5)
SELECT * FROM dual
;
INSERT ALL
INTO newCities ("city_id", "new_city_id")
VALUES (1, 100)
INTO newCities ("city_id", "new_city_id")
VALUES (2, 200)
INTO newCities ("city_id", "new_city_id")
VALUES (3, 200)
SELECT * FROM dual
;
When I do a JOIN:
SELECT *
FROM poi p
JOIN newCities nc
ON p."city_id" = nc."city_id";
OUTPUT
+--------+---------+---------+-------------+
| poi_id | city_id | city_id | new_city_id |
+--------+---------+---------+-------------+
| 10 | 1 | 1 | 100 |
| 11 | 1 | 1 | 100 |
| 12 | 2 | 2 | 200 |
| 13 | 2 | 2 | 200 |
+--------+---------+---------+-------------+
But when I try to do the update but got the error:
ORA-01779: cannot modify a column which maps to a non key-preserved table
UPDATE (
SELECT p.*, nc."new_city_id"
FROM poi p
JOIN newCities nc
ON p."city_id" = nc."city_id"
) t
SET t."city_id" = t."new_city_id";
I know city_id isn't a PK but the row is match with a row including a PK. So why isn't working?
I know I can do a sub query to get the value:
UPDATE poi p
SET "city_id" = COALESCE((SELECT "new_city_id"
FROM newCities c
WHERE c."city_id" = p."city_id")
, p."city_id");
But still want to know what cases the UPDATE JOIN would work because looks like only can work to update the PK:
After doing the sample test decide try creating a PK on the second table and works:
SQL DEMO
CREATE TABLE newCities
("city_id" int GENERATED BY DEFAULT AS IDENTITY,
"new_city_id" int,
PRIMARY KEY("city_id")
);

Create a table with unique values from another table

I am using MS SQL Server Management Studio. I have table -
+--------+----------+
| Num_ID | Alpha_ID |
+--------+----------+
| 1 | A |
| 1 | B |
| 1 | C |
| 2 | B |
| 2 | C |
| 3 | A |
| 4 | C |
| 5 | A |
| 5 | B |
+--------+----------+
I want to create another table with 2 columns from this table so that column_1 gives Unique values in Num_ID (i.e. 1,2,3,4 and so on) and column_2 gives Unique values in Alpha_ID (A, B, C and so on).
But if an alphabet has already occurred, it should not occur again. So the output will be something like this -
Col_1 Col_2
================
1 - A
----------------
2 - B
----------------
3 - NULL (as A has been chosen by 1, it cannot occur next to 3)
----------------
4 - C
----------------
5 - NULL (both 5 A and 5 B cannot be chosen as A and B were picked up by 1 and 2)
----------------
Hope that makes sense.
I would like to clarify that the IDs in the input table are not numerical as I have shown, but both Num_ID and Alpha_ID are complex strings. I have simplified them to 1,2,3,... and A, B, C .... for the purpose of this question
I don't think this could be done without a cursor.
I added few more rows to your sample data to test how it works with other cases.
The logic is straight-forward. At first get a list of all distinct values of Num_ID. Then loop through them and with each iteration add one row to the destination table. To determine the Alpha_ID value to add I'll use EXCEPT operator that takes all available Alpha_ID values for the current Num_ID from the source table and removes from them all values that have been used before.
It is possible to write that INSERT without using explicit variable #CurrAlphaID, but it looks a bit cleaner with variable.
Here is SQL Fiddle.
DECLARE #TSrc TABLE (Num_ID varchar(10), Alpha_ID varchar(10));
INSERT INTO #TSrc (Num_ID, Alpha_ID) VALUES
('1', 'A'),
('1', 'B'),
('1', 'C'),
('2', 'B'),
('2', 'C'),
('3', 'A'),
('3', 'C'),
('4', 'A'),
('4', 'C'),
('5', 'A'),
('5', 'B'),
('5', 'C'),
('6', 'D'),
('6', 'E');
DECLARE #TDst TABLE (Num_ID varchar(10), Alpha_ID varchar(10));
DECLARE #CurrNumID varchar(10);
DECLARE #CurrAlphaID varchar(10);
DECLARE #iFS int;
DECLARE #VarCursor CURSOR;
SET #VarCursor = CURSOR FAST_FORWARD
FOR
SELECT DISTINCT Num_ID
FROM #TSrc
ORDER BY Num_ID;
OPEN #VarCursor;
FETCH NEXT FROM #VarCursor INTO #CurrNumID;
SET #iFS = ##FETCH_STATUS;
WHILE #iFS = 0
BEGIN
SET #CurrAlphaID =
(
SELECT TOP(1) Diff.Alpha_ID
FROM
(
SELECT Src.Alpha_ID
FROM #TSrc AS Src
WHERE Src.Num_ID = #CurrNumID
EXCEPT
SELECT Dst.Alpha_ID
FROM #TDst AS Dst
) AS Diff
ORDER BY Diff.Alpha_ID
);
INSERT INTO #TDst (Num_ID, Alpha_ID)
VALUES (#CurrNumID, #CurrAlphaID);
FETCH NEXT FROM #VarCursor INTO #CurrNumID;
SET #iFS = ##FETCH_STATUS;
END;
CLOSE #VarCursor;
DEALLOCATE #VarCursor;
SELECT * FROM #TDst;
Result
Num_ID Alpha_ID
1 A
2 B
3 C
4 NULL
5 NULL
6 D
Having index on (Num_ID, Alpha_ID) on the source table would help. Having index on (Alpha_ID) on the destination table would help as well.
I think I've made something not through a recursion (cursor or a while)
First, I created a table with rows.
create table #tmptest
(
Num_ID int
, Alpha_ID varchar(50)
)
insert into #tmptest (Num_ID, Alpha_ID) values
(1,'A'),
(1,'B'),
(1,'C'),
(2,'B'),
(2,'C'),
(3,'A'),
(4,'C'),
(5,'A'),
(5,'B')
// this one, with row column
SELECT
ROW_NUMBER() OVER (PARTITION BY Num_ID ORDER BY Num_ID ASC) as row
, *
INTO #tmp_withrow
FROM #tmptest
and these were the results
Lastly, I made an inner query (could possibly be a left join or better).
SELECT DISTINCT
Num_ID
, (
SELECT
TOP 1
Alpha_ID
FROM #tmp_withrow in1
WHERE
in1.Num_ID = t.Num_ID
AND in1.Alpha_ID NOT IN (
SELECT
Alpha_ID
FROM #tmp_withrow in2
WHERE
in2.Num_ID < in1.Num_ID
AND in2.row = 1
)
ORDER BY in1.Num_ID ASC
) AS [NonRepeatingAlpha]
from #tmptest t
and these were the results
Note : I created a flag (row) which will allow you to query all less than the ID's you're in (in2.Num_ID < in1.Num_ID) then find out what letters where already used (in2.row = 1) and then select / avoid all letters that has already been used from the other Num_ID (
WHERE in1.Num_ID = t.Num_ID
AND in1.Alpha_ID NOT IN (
SELECT
Alpha_ID
FROM #tmp_withrow in2
WHERE
in2.Num_ID < in1.Num_ID
AND in2.row = 1
)
I hope this helps. Thanks!

CONCAT(column) OVER(PARTITION BY...)? Group-concatentating rows without grouping the result itself

I need a way to make a concatenation of all rows (per group) in a kind of window function like how you can do COUNT(*) OVER(PARTITION BY...) and the aggregate count of all rows per group will repeat across each particular group. I need something similar but a string concatenation of all values per group repeated across each group.
Here is some example data and my desired result to better illustrate my problem:
grp | val
------------
1 | a
1 | b
1 | c
1 | d
2 | x
2 | y
2 | z
And here is what I need (the desired result):
grp | val | groupcnct
---------------------------------
1 | a | abcd
1 | b | abcd
1 | c | abcd
1 | d | abcd
2 | x | xyz
2 | y | xyz
2 | z | xyz
Here is the really tricky part of this problem:
My particular situation prevents me from being able to reference the same table twice (I'm actually doing this within a recursive CTE, so I can't do a self-join of the CTE or it will throw an error).
I'm fully aware that one can do something like:
SELECT a.*, b.groupcnct
FROM tbl a
CROSS APPLY (
SELECT STUFF((
SELECT '' + aa.val
FROM tbl aa
WHERE aa.grp = a.grp
FOR XML PATH('')
), 1, 0, '') AS groupcnct
) b
But as you can see, that is referencing tbl two times in the query.
I can only reference tbl once, hence why I'm wondering if windowing the group-concatenation is possible (I'm a bit new to TSQL since I come from a MySQL background, so not sure if something like that can be done).
Create Table:
CREATE TABLE tbl
(grp int, val varchar(1));
INSERT INTO tbl
(grp, val)
VALUES
(1, 'a'),
(1, 'b'),
(1, 'c'),
(1, 'd'),
(2, 'x'),
(2, 'y'),
(2, 'z');
In sql 2017 you can use STRING_AGG function:
SELECT STRING_AGG(T.val, ',') AS val
, T.grp
FROM #tbl AS T
GROUP BY T.grp
I tried using pure CTE approach: Which is the best way to form the string value using column from a Table with rows having same ID? Thinking it is faster
But the benchmark tells otherwise, it's better to use subquery(or CROSS APPLY) results from XML PATH as they are faster: Which is the best way to form the string value using column from a Table with rows having same ID?
DECLARE #tbl TABLE
(
grp INT
,val VARCHAR(1)
);
BEGIN
INSERT INTO #tbl(grp, val)
VALUES
(1, 'a'),
(1, 'b'),
(1, 'c'),
(1, 'd'),
(2, 'x'),
(2, 'y'),
(2, 'z');
END;
----------- Your Required Query
SELECT ST2.grp,
SUBSTRING(
(
SELECT ','+ST1.val AS [text()]
FROM #tbl ST1
WHERE ST1.grp = ST2.grp
ORDER BY ST1.grp
For XML PATH ('')
), 2, 1000
) groupcnct
FROM #tbl ST2
Is it possible for you to just put your stuff in the select instead or do you run into the same issue? (i replaced 'tbl' with 'TEMP.TEMP123')
Select
A.*
, [GROUPCNT] = STUFF((
SELECT '' + aa.val
FROM TEMP.TEMP123 AA
WHERE aa.grp = a.grp
FOR XML PATH('')
), 1, 0, '')
from TEMP.TEMP123 A
This worked for me -- wanted to see if this worked for you too.
I know this post is old, but just in case, someone is still wondering, you can create scalar function that concatenates row values.
IF OBJECT_ID('dbo.fnConcatRowsPerGroup','FN') IS NOT NULL
DROP FUNCTION dbo.fnConcatRowsPerGroup
GO
CREATE FUNCTION dbo.fnConcatRowsPerGroup
(#grp as int) RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #val AS VARCHAR(MAX)
SELECT #val = COALESCE(#val,'')+val
FROM tbl
WHERE grp = #grp
RETURN #val;
END
GO
select *, dbo.fnConcatRowsPerGroup(grp)
from tbl
Here is the result set I got from querying a sample table:
grp | val | (No column name)
---------------------------------
1 | a | abcd
1 | b | abcd
1 | c | abcd
1 | d | abcd
2 | x | xyz
2 | y | xyz
2 | z | xyz