SQL UPDATE from multiple rows to single row - sql

this illustrates the issue:
CREATE TABLE Table_A (id int, value char)
INSERT INTO Table_A VALUES (1, 'A')
INSERT INTO Table_A VALUES (2, 'B')
CREATE TABLE Table_B (id int, value char)
INSERT INTO Table_B VALUES (1, 'C')
INSERT INTO Table_B VALUES (1, 'D')
If you run
UPDATE a SET a.value = (SELECT b.value FROM Table_B b WHERE a.id = b.id)
FROM Table_A a, Table_B b WHERE a.id = b.id
You get an error saying
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
But if you run this
UPDATE a SET a.value = b.value
FROM Table_A a, Table_B b WHERE a.id = b.id
No error is thrown and you get the row updated, why is that?
Edit:
Sorry guys, you seem to focusing on explaining why the first query gives error, but I think that is obvious and to me that is a desire result (because setting value of Table_A for id 1 to value of Table_B with id 1 is undefined when there are multiple values in Table_B with id 1)
My question is actually asking why the second query does not give you an error, which is causing trouble to me (i.e. I want it to break if I have more than one row with the same id but different values)

You got that error because you are using subquery when you set a new value and the subquery return more than 1 result.
SET a.value = (SELECT b.value FROM Table_B b WHERE a.id = b.id)
It will error when update a value with id = 1, because there is two record that have id = 1 in table b.
So your query will look like this (this is only for illustration and of course will cause an error)
UPDATE a SET a.value = ('C', 'D')
FROM Table_A a, Table_B b WHERE a.id = b.id
When you're using this query
UPDATE a SET a.value = b.value
FROM Table_A a, Table_B b WHERE a.id = b.id
You are join the table a with table b using id field, so the result is
a.id => a.value => b.value : 1 A C
a.id => a.value => b.value : 1 A D
No entry record for id = 2 because there is no matching record in table b.
So your update query will looks like this
UPDATE a SET a.value = 'C'
FROM Table_A a, Table_B b WHERE a.id = 1
UPDATE a SET a.value = 'D'
FROM Table_A a, Table_B b WHERE a.id = 1

Because your subquery will return both more than 1 result. The Assign statement will not accept more than 1 value.
You have to use JOIN
May be something like this
UPDATE A
SET A.value = B.value
FROM Table_A A INNER JOIN Table_B B ON A.id = B.id
FIDDLE DEMO

The reason your second query does not error our is because it will assign the first available B.value to a.value where it satisfies the condition
A.id = B.id
Try the following snippet and you will see how the Update...Set works.
create table #temp (id int, name varchar(100), phone int)
create table #temp1 (id int, phone int)
insert into #temp (id, name)
select 1, 'Mark' union
select 2, 'JOhn' union
select 3, 'Jerry'
insert into #temp1 (id, phone)
select 1, 123456 union
select 1, 222564 union
select 1, 222210
select * from #temp
select * from #temp1
update t
set phone = t1.phone
from #temp1 t1, #temp t
where t.id = t1.id
select * from #temp

Related

How to select rows that have certain values present in another column

I have a table :
id
value
1
A
1
B
1
C
2
A
2
B
3
A
my goal is to have the table where I have only IDs that have A,B,C present per id,
in this case it is:
id
1
how to construct the SQL query for that ?
One canonical approach uses aggregation:
SELECT id
FROM yourTable
WHERE value IN ('A', 'B', 'C')
GROUP BY id
HAVING COUNT(DISTINCT value) = 3;
To use exists statement like this:
select id from ${table} a where value = 'A'
and exists (select 1 from ${table} b where a.id = b.id and b.value = 'B')
and exists (select 1 from ${table} c where a.id = c.id and b.value = 'C')
To create index on column id will be more nice.

Sorting data from one table based on another table

Here are my tables:
create table tableA (id int, type varchar(5))
insert into tableA (ID,TYPE)
values
(101,'A'),
(101,'C'),
(101,'D'),
(102,'A'),
(102,'B'),
(103,'A'),
(103,'C')
create table tableB (id int, type varchar(5), isActive bit)
insert into tableB (id, type, isActive)
Values
(101,'A',1),
(101,'B',0),
(101,'C',0),
(101,'D',1),
(102,'A',1),
(102,'B',0),
(102,'C',1),
(102,'D',1),
(103,'A',1),
(103,'B',1),
(103,'C',1),
(103,'D',1)
Now, I want to do two things here:
1) Find the rows that are present in tableA but isActive = 0 in tableB. (done)
select A.* from tableA A
join tableB B
on A.id = B.id and A.type = B.type
where B.isactive = 0
2) Find the rows that are missing in tableA but isActive = 1 in tableB.
For example ID 103, type B is active in tableB but is missing in tableA.
I don't care about existance of type D in tableA because I am only checking the last entry in tableA which is C.
Also, ABCD is in order for all IDs, they can be active or inactive.
Thanks for your help!
My effort: (not working)
select A.* from tableA A
where exists (
select B.* from tableA A
join tableB B
on a.id = b.id
where b.isActive = 1
order by b.id,b.type
)
SQLFiddle
I think you are looking for something like the following:
select B.* from tableB B
left join tableA A
on B.id = A.id and B.type = A.type
where B.isActive = 1
and A.id is null
order by B.id, B.type
By using the left join, it means that rows in tableB that have no rows to join with in tableA will have all A.* columns null. This then allows you to add the where clause to check where the tableA records are null thus determining what is contained within tableB that is active and not in tableA

Filtering out null values only if they exist in SQL select

Below I have sql select to retrieve values from a table. I want to retrieve the values from tableA regardless of whether or not there are matching rows in tableB. The below gives me both non-null values and null values. How do I filter out the null values if non-null rows exist, but otherwise keep the null values?
SELECT a.* FROM
(
SELECT
id,
col1,
coll2
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id= #id
AND a.col2= #arg
) AS a
ORDER BY col1 ASC
You can do this by counting the number of matches using a window function. Then, either return all rows in A if there are no matching B rows, or only return the rows that do match:
select id, col1, col2
from (SELECT a.id, a.col1, a.coll2,
count(b.id) over () as numbs
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id = #id AND a.col2= #arg
) ab
where numbs = 0 or b.id is not null;
Filter them out in WHERE clause
SELECT
id,
col1,
coll2
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id= #id
AND a.col2= #arg
AND A.Col1 IS NOT NULL -- HERE
) AS a
ORDER BY col1 ASC
For some reason, people write code like Marko that puts a filter (b.col2 = 'value') in the JOIN clause. While this works, it is not good practice.
Also, you should get in the habit of having the ON clause in the right sequence. We are joining table A to table B, why write it as B.col1 = A.col1 which is backwards.
While the above statement works, it could definitely be improved.
I created the following test tables.
-- Just playing
use tempdb;
go
-- Table A
if object_id('A') > 0 drop table A
go
create table A
(
id1 int,
col1 int,
col2 varchar(16)
);
go
-- Add data
insert into A
values
(1, 1, 'Good data'),
(2, 2, 'Good data'),
(3, 3, 'Good data');
-- Table B
if object_id('B') > 0 drop table B
go
create table B
(
id1 int,
col1 int,
col2 varchar(16)
);
-- Add data
insert into B
values
(1, 1, 'Good data'),
(2, 2, 'Good data'),
(3, NULL, 'Null data');
Here is the improved statement. I choose literals instead of variables. However, you can change for your example.
-- Filter non matching records
SELECT
A.*
FROM A LEFT OUTER JOIN B ON
A.col1 = B.col1
WHERE
B.col1 IS NOT NULL AND
A.id1 in (1, 2) AND
A.col2 = 'Good data'
ORDER BY
A.id1 DESC
Here is an image of the output.

t-sql insert - select - with parameters

I have a 4 tables. One of the tables we are going to be inserting data into (Table A).
Table A is going to receive misc data from Table B, C, D and also some unknown variable parameter data.
How do I set up the INSERT with a SELECT with also receiving parameters?
Something like this?
Insert INTO TableA (col1, col2,col3,col4)
SELECT b.col1, c.col2, d.col3, #myparam
FROM TableB as b
INNER JOIN TableC as c
ON b.id = c.id
INNER JOIN TableD as d
on c.id = d.id
Something like this:
DECLARE #a int, #b int
SET #a = 5
SET #b = 7
INSERT INTO TableA(Column1, Column2)
SELECT SomeOtherColumn, #a
FROM TableB
UNION
SELECT YetAnotherColumn, #b
FROM TableC

Updating and join on multiple rows, which row's value is used?

Let's say I have the following statement and the inner join results in 3 rows where a.Id = b.Id, but each of the 3 rows have different b.Value's. Since only one row from tableA is being updated, which of the 3 values is used in the update?
UPDATE a
SET a.Value = b.Value
FROM tableA AS a
INNER JOIN tableB as b
ON a.Id = b.Id
I don't think there are rules for this case and you cannot depend on a particular outcome.
If you're after a specific row, say the latest one, you can use apply, like:
UPDATE a
SET a.Value = b.Value
FROM tableA AS a
CROSS APPLY
(
select top 1 *
from tableB as b
where b.id = a.id
order by
DateColumn desc
) as b
Usually what you end up with in this scenario is the first row that appears in the order of the physical index on the table. In actual practice, you should treat this as non-deterministic and include something that narrows your result to one row.
Here is what I came up with using SQL Server 2008
--drop table #b
--drop table #a
select 1 as id, 2 as value
into #a
select 1 as id, 5 as value
into #b
insert into #b
select 1, 3
insert into #b
select 1, 6
select * from #a
select * from #b
UPDATE #a
SET #a.Value = #b.Value
FROM #a
INNER JOIN #b
ON #a.Id = #b.Id
It appears that it uses the top value of a basic select each time (row 1 of select * from #b). So, it possibly depends on indexing. However, I would not rely on the implementation set by SQL, as that has the possibility of changing. Instead, I would suggest using the solution presented by Andomar to make sure you know what value you are going to choose.
In short, do not trust the default implementation, create your own. But, this was an interesting academic question :)
Best option in my case for updating multiple records is to use merge Query(Supported from SQL Server 2008), in this query you have complete control of what you are updating.
Also you can use output query to do further processing.
Example: Without Output clause(only update)
;WITH cteB AS
( SELECT Id, Col1, Col2, Col3
FROM B WHERE Id > 10 ---- Select Multiple records
)
MERGE A
USING cteB
ON(A.Id = cteB.Id) -- Update condition
WHEN MATCHED THEN UPDATE
SET
A.Col1 = cteB.Col1, --Note: Update condition i.e; A.Id = cteB.Id cant appear here again.
A.Col2 = cteB.Col2,
A.Col3 = cteB.Col3;
Example: With OputPut clause
CREATE TABLE #TempOutPutTable
{
PkId INT NOT NULL,
Col1 VARCHAR(50),
Col2 VARCHAR(50)
}
;WITH cteB AS
( SELECT Id, Col1, Col2, Col3
FROM B WHERE Id > 10
)
MERGE A
USING cteB
ON(A.Id = cteB.Id)
WHEN MATCHED THEN UPDATE
SET
A.Col1 = cteB.Col1,
A.Col2 = cteB.Col2,
A.Col3 = cteB.Col3
OUTPUT
INSERTED.Id, cteB.Col1, A.Col2 INTO #TempOutPutTable;
--Do what ever you want with the data in temporary table
SELECT * FROM #TempOutPutTable; -- you can check here which records are updated.
Yes, I came up with a similar experiment to Justin Pihony:
IF OBJECT_ID('tempdb..#test') IS NOT NULL DROP TABLE #test ;
SELECT
1 AS Name, 0 AS value
INTO #test
IF OBJECT_ID('tempdb..#compare') IS NOT NULL DROP TABLE #compare ;
SELECT 1 AS name, 1 AS value
INTO #compare
INSERT INTO #compare
SELECT 1 AS name, 0 AS value;
SELECT * FROM #test
SELECT * FROM #compare
UPDATE t
SET t.value = c.value
FROM #test t
INNER JOIN #compare c
ON t.Name = c.name
Takes the topmost row in the comparison, right-side table. You can reverse the #compare.value values to 0 and 1 and you'll get the reverse. I agree with the posters above...its very strange that this operation does not throw an error message as it is completely hidden that this operation IGNORES secondary values