SQL update statement with different table in from clause - sql

Out of accident I noticed that the following query is actually valid:
UPDATE bikes
SET price = NULL
FROM inserted
WHERE inserted.owner_id = 123456
This is part of a trigger where someone forgot to join the original table to the inserted table. The result is that when the trigger is executed, all prices are set to NULL.
The correct SQL statement is this:
UPDATE bikes
SET price = NULL
FROM inserted
INNER JOIN bikes ON bikes.id=inserted.id
WHERE inserted.owner_id = 123456
How/why is this first statement valid?

Why wouldn't it be valid? SQL Server doesn't know what you're trying to do. It thinks you want to update all of the fields where some condition exists on another table. See the last update below.
SETUP
declare #table table
(
id int,
name varchar(10)
)
declare #itable table
(
id int,
name varchar(10)
)
insert into #table (id, name)
select 1,'abc' union
select 2,'def' union
select 3,'ghi' union
select 4,'jkl' union
select 5,'mno' union
select 6,'pqr'
insert into #itable (id, name)
select 1,'abc' union
select 2,'def' union
select 3,'ghi' union
select 4,'jkl' union
select 5,'mno' union
select 6,'pqr'
All names on #table will change to zzz
update #table
set name = 'zzz'
from #itable i
where i.id = 1
select * from #itable
select * from #table
All names where id = 1 on #table becomes yyy
update #table
set name = 'yyy'
from #itable i
inner join #table t on i.id = t.id
where i.id = 1
select * from #itable
select * from #table
This will NOT update anything
update #table
set name = 'aaa'
from #itable i
where i.id = 133
select * from #itable
select * from #table

The first statement does not work as expected because it is missing the entire INNER JOIN line with the bikes and inserted table. Without that SQL Server will update all rows as all rows will qualify for an update when the inserted.owner_id = 123456.
You can reproduce this outside of the trigger in TSQL like :
update bikes set price =null
from SomeOtherTable
where SomeOtherTable.SomeColumn = 'some_value_that_exists'
This is syntactically valid statement in SQL Server. If the Intention is to update bikes table based on existance of a row in some unrelated table that cant be joined because the 2 tables arent related then this is how you would do it. But that is not your requirement. Hence why it updates all records instead of only those that match the bikes.id In programming terms this is called as a logical bug.
The inner join makes it more restrictive and forces to to update only those rows that match the join condition between the 2 tables (bikes.id=inserted.id comparison )

In Simple terms the from clause is optional..Consider below query..
update table
set id=10
This has one table right after update clause ,sql just updates it..
now consider below query..
update table1
set id=40
from table2..
What do you think SQL does,it updates all the rows same as first query..
unless you refer to another table in from clause and join like below
update t1
set t1.id=40
from
table1 t1
join
table2 t2
on t1.id=t2.id
below is the from clause explanation in update syntax stripped down to show only to relevant parts..
If the object being updated is the same as the object in the FROM clause and there is only one reference to the object in the FROM clause, an object alias may or may not be specified. If the object being updated appears more than one time in the FROM clause, one, and only one, reference to the object must not specify a table alias. All other references to the object in the FROM clause must include an object alias
As long as above rules are valid (as in your case),SQL will happily update table found immediately after update clause

Related

Update table with using NEWID() function

CREATE TABLE Products(Id INT, Name CHAR(100), DefaultImageId INT NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(1, 'A', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(2, 'A', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(3, 'A', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(4, 'A', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(5, 'A', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(1, 'B', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(2, 'B', NULL);
INSERT INTO Products (Id, Name, DefaultImageId) VALUES(3, 'B', NULL);
In general, I would update a table randomly like the following scripts.
update a
set DefaultImageId=1
from Products as a
where name = 'A'
and id in (
select top 2 id
from Products as b
where a.name = b.name
order by newid()
)
However, I get some issue. It would update more/less then 2 rows. I try execute the following scripts many times for debug.
The results are not always only two records. If I remove the order by newid(), the number of output result will be fine. It seems like the problem in newid(). How can I solve this problem? Thanks
select *
from Products as a
where name = 'A'
and id in (
select top 2 id
from Products as b
where a.name = b.name
order by newid()
)
You can try this:
UPDATE U SET DefaultImageId = 1
FROM(
SELECT TOP 2 * FROM dbo.Products WHERE Name = 'A' ORDER BY NEWID()
) AS U
However if you filter out the DefaultImageId=1 in inner query, it would be even better too. You should note that in this case, the inner query might produce less than 2 records.
UPDATE U SET DefaultImageId = 1
FROM(
SELECT TOP 2 * FROM dbo.Products WHERE Name = 'A' AND DefaultImageId <> 1 ORDER BY NEWID()
) AS U
I think you're just looking for a simple JOIN as
UPDATE A
SET DefaultImageId = 1
FROM Products A
JOIN
(
SELECT TOP 2 Id, Name
FROM Products B
ORDER BY NEWID()
) B ON A.Id = B.Id AND A.Name = B.Name;
Demo
SQL SERVER BUG:
I think you just hit the following SQL server bug, that is still under review since 2018.
https://feedback.azure.com/forums/908035-sql-server/suggestions/33272404-cte-containing-top-and-newid-producing-incorrect
WORK AROUND:
The only thing that seems to work, based on my stesting, is if you eliminate the outside reference from the subquery, things works as expected. You can eliminate the outside reference by doing any of the following as suggested by others here.
Option 1: Keeping the query as-is, except hard-coding the filter criteria inside the subquery instead of using an outside reference:
SELECT ...
FROM t1
WHERE
t1.id IN (SELECT TOP 2 id subquery with NO outside reference WHERE name = 'A' ORDER BY ORDER BY newid() )
AND t1.name = 'A'
....
Option 2: Using a JOIN with proper ON matching criteria instead of using an outside reference inside the subquery:
SELECT ...
FROM t1
INNER JOIN (SELECT TOP 2 id subquery with NO outside reference ORDER BY newid() ) t2 ON t1.id = t2.id and t1.name = t2.name
WHERE
t1.name = 'A'
....
OR
DETAILS:
Based on my testing, when all of the following are aligned, the SQL Engine returns an unpredictable result:
An outside table is referenced inside the subquery (or through CTE) as a filtering criteria
NEWID() function is used in the ORDER BY with a SELECT TOP statement (It does NOT happen with other non-deterministic functions like RAND() for example. Something specific to NEWID())
The SELECT TOP statement uses a number less than the number of records exists in that table
This seems to throw off the SQL engine. The result changes at every run: The number of records returned varies at every run between zero records and max number of records that would have been returned if SELECT TOP was not used.
The query plan does not make sense at all. See the screenshot here. TOP N returns more than it is supposed to and id IN () predicate returns random number of records (zero in this example). How could that be possible? I have no clue. Also, note that the id IN () predicate is applied before the left table scan, which shouldn't happen. I think the unreliable result has something to do with that. We'll have to wait until SQL Server team fixes the bug to be able write the queries the way you wrote.
As you are using Correlated subquery, where outer query name is referred by subquery name, you are getting different result set.
I would suggest you to use the name parameter twice to avoid this problem.
select *
from Products as a
where name='A'
and id in
(select top 2 id
from Products as b
where b.name = 'A'
order by newid())
Similarly, for update also, you change the query
update a
set DefaultImageId=1
from Products as a
where name = 'A'
and id in (
select top 2 id
from Products as b
where b.name = 'A'
order by newid()
)
Something like this
This query selects 2 rows at random.
with top_2_cte(id, [name]) as (
select top 2 id, [name]
from Products
where name='A'
order by newid())
select a.*
from Products a
join
top_2_cte ttc on a.Id=ttc.id
and a.[Name]=ttc.[name];
Update statement
;with top_2_cte(id, [name]) as (
select top 2 id, [name]
from Products
where name='A'
order by newid())
update a
set DefaultImageId=1
from Products a
join
top_2_cte ttc on a.Id=ttc.id
and a.[Name]=ttc.[name];

ORACLE - Update a value from two different columns in differente tables with a filter

I'have a little question about a query.
I have to update a column from a table where there are only record of expense(integer).
I must increase the expense of 5% if the client is from a specific state, the column of the state is in a different table and the key in common is the address.
This is my query below :
UPDATE table 1 a
SET expense_vl = (
SELECT expense*1.05 FROM table 1
LEFT JOIN table2 b ON b.ADDRESS_ID=a.ADDRESS_ID
WHERE description_state IN 'lollyland'
)
I'd recommend using a semi-join:
update table_1 a
set expense_v1 = expense * 1.05
where exists (
select null
from table2 b
where
a.address_id = b.address_id and
b.description_state = 'lollyland'
)
Althought I must add that it would help if you include the DDL for your table. We're sort of guessing at which table "description" came from.
Also, when possible, include sample input for each table and desired output. We don't need a million records, just an example that illustrates your issue.
Try below
UPDATE table1 a SET expense_vl = (SELECT expense*1.05
FROM table2 b
WHERE b.ADDRESS_ID=a.ADDRESS_ID)
WHERE description_state IN 'lollyland'
Or try with subselect:
UPDATE table1
SET expense_vl = expense*1.05
WHERE ADDRESS_ID IN (SELECT ADDRESS_ID FROM table2 WHERE description_state IN 'lollyland')
I think you need to change your query like below :
UPDATE table 1 A
SET expense_vl=expense*1.05 FROM table 1
LEFT JOIN table2 B ON B.ADDRESS_ID=A.ADDRESS_ID
WHERE B.description_state IN 'lollyland'

SQL select from column where column equals table variable?

I'm a serious SQL noob so any help is appreciated. I'm having a hard time even explaining what I'm trying to do so I'll lay out what I have so far:
DECLARE #UserIDInt table (ID int);
INSERT into #UserIDInt
SELECT UserId
FROM [LcsCDR].[dbo].[Users]
WHERE [LcsCDR].[dbo].[Users].[UserUri] LIKE '%example';
SELECT *
FROM [LcsCDR].[dbo].[SessionDetails]
WHERE [LcsCDR].[dbo].[SessionDetails].[User1Id] = #UserIDInt;
"DECLARE #UserIDInt table (ID int);"
This creates my variable with a column called "ID"
INSERT into #UserIDInt
SELECT UserId
FROM [LcsCDR].[dbo].[Users]
WHERE [LcsCDR].[dbo].[Users].[UserUri] LIKE '%example';
This adds numeric values into the ID column based on whether or not the WHERE statement matched
SELECT *
FROM [LcsCDR].[dbo].[SessionDetails]
WHERE [LcsCDR].[dbo].[SessionDetails].[User1Id] = #UserIDInt;
This is where I am lost. I am trying to return all rows from [LcsCDR].[dbo].[SessionDetails] if the column [LcsCDR].[dbo].[SessionDetails].[User1Id] matches anything in my variable. The problem (I think) I'm having is that SQL can't look within the variable's column to find multiple values. Basically, the ID column in my variable #UserIDInt will contain a bunch of numeric values.
How do I perform the final SELECT statement and have SQL return all results if [LcsCDR].[dbo].[SessionDetails].[User1Id] matches anything within my #UserIDInt.ID column?
I am using SQL Server 2014.
Apologies if I explained it badly. Not sure how else to ask the question :)
using inner join:
select sd.*
from [lcscdr].[dbo].[sessiondetails] sd
inner join #useridint i
on i.id = sd.user1id;
or using exists():
select sd.*
from [lcscdr].[dbo].[sessiondetails] sd
where exists (
select 1
from #useridint i
where i.id = sd.user1id
);
or using in():
select sd.*
from [lcscdr].[dbo].[sessiondetails] sd
where sd.user1id in (
select id
from #useridint i
);
rextester demo: http://rextester.com/UVCB28056
Use EXISTS:
SELECT T1.*
FROM [lcscdr].[dbo].[sessiondetails] T1 WHERE EXISTS (
SELECT 1
FROM #useridint T2
WHERE T2.id = T1.user1id
);

SQL Update subquery returns no results run different sub query

I am trying to do an update and i'm having problems (using microsoft sql server)
update mytable
set myvalue=
(
(select myvalue from someothertable
where someothertable.id=mytable.id)
)
from table mytable
where mytable.custname='test'
Basically the subquery could return no results if that does happen i want to call a different subquery:
(select myvalue from oldtable
where oldtable.id=mytable.id)
well, you could run the second query first, and then, the first query.
In that way, you will override the values only when the first query can bring them, and when the (original) first query won't bring any result, they will have the result of first query.
Also, I think you have a typo in your second query with the table name.
update mytable
set myvalue=
(
select myvalue from oldtable
where oldtable.id=mytable.id
)
from table mytable
where mytable.custname='test'
and exists (select 1 from oldtable
where oldtable.id=mytable.id)
update mytable
set myvalue=
(
select myvalue from someothertable
where someothertable.id=mytable.id
)
from table mytable
where mytable.custname='test'
and exists ( select 1 from someothertable
where someothertable.id=mytable.id)
edit: you will need to add the exists clause, cause if not it will update with null values, I think
You can simply join both tables,
UPDATE a
SET a.myValue = b.myValue
FROM myTable a
INNER JOIN someOtherTable b
ON a.ID = b.ID
WHERE a.CustName = 'test'

Avoiding multiple if exists, else if exits statement for different joins used inside

Inserting in one common table with the different scenarios
E.g:
if exists(select id from tablename where id =1 )
insert into Temp
select * from tbl inner join ........
else if exists(select id from tablename where id=2)
insert into Temp
select * from tb2 inner join ........
else if exists(select id from tablename where id=3 )
insert into Temp
select * from tb3 inner join ........
....
it is like using else condition for joining different tables for different id's...
now i want to write a single query without this else if,else if... but based on the id value the respective select statement should be called
Perhaps you're looking to define a query (or view) using SELECT, WHERE, and UNION ALL. Consult the documentation (postgreSQL, MySQL, or whatever SQL dialect) for details.