update assignment applied to set vs row - sql

I was trying to randomly update stateid in [address], so i started with this query
update [address]
set stateid = (select top 1 id
from lookupvalue
where lookuptypeid = 3 and code = 1
order by newid()),
countryid = 1
select *
from [address]
but as it appears, all the rows get the same value, when I tried referencing [address] table from the inner select query, the update is ran per row (and I got the desired effect).
update [address]
set stateid = (select top 1 id
from lookupvalue
where lookuptypeid = 3 and [address].id = [address].id
and code = 1
order by newid()),
countryid = 1
select *
from [address]
Can someone elaborate on the above behavior, is it related to query plan, do I have to make a dummy reference in the inner select to force the update assignment to be evaluated per row?

Yes, it is related to the query plan. SQL Server (and other databases too) see the subquery and decide that it can be optimized away. I consider this an error because newid() is a volatile function, so the subquery cannot be optimized away. But, there are arguments on the other side as well.
Putting in the outer reference fixes the problem, so you know how to get around this "optimization".

To evaluate result per row you can also use CROSS APPLY

Related

Using ISNULL in SQL LEFT JOIN to check if the result is null, and if it is, use another value to join

I have a SQL select with a where clause where i want to check if the result is null, and if it is null I want to use another value in the where clause, but i get 0 rows results, even though i know i should get a row as result.
Heres my (updated) SQL code:
DECLARE #LanguageCode NVARCHAR(3);
SET #LanguageCode = 'FR'
SELECT
wi.WorkItemId,
ds.DisplayString AS Team
FROM dbo.WorkItem AS wi
LEFT JOIN dbo.DisplayString AS ds ON ds.ElementID = wi.TierId AND ds.LocaleID = ISNULL(#LanguageCode, 'ENU')
The code above returns data for "#LanguageCode" when there is data to return, but it does not switch to use 'ENU' when there is no data. Thats the problem!
This is also just a sample since this is part of a larger query with lots of left joins where i need the same functionality against "LocaleID". I'm hoping there would be something easy solution to this like the code above.
To clarify what i want to achieve, if the c.LocaleID = #LanguageCode returns null rows i want to use the hardcoded value as in c.LocaleID = ENU.
If i don't use the ISNULL function and only use 'ENU' it returns the expected result.
I would appreciate any help. Thanks.
If I understand correctly, you want one row, either with the specified language code or 'ENU'. If so, use filtering and ORDER BY:
SELECT TOP (1) c.*
FROM dbo.column1 c
WHERE c.rowID = '1234-1234-1234' AND
c.LocaleID IN (#LanguageCode, 'ENU')
ORDER BY (CASE WHEN c.LocaleID = #LanguageCode THEN 1 ELSE 2 END)
I think you're looking for this
select coalesce((SELECT rowID FROM dbo.column1 where c.rowID = '1234-1234-1234' and LocaleID = #LanguageCode),
(SELECT rowID FROM dbo.column1 where c.rowID = '1234-1234-1234' and LocaleID = 'ENU'));
I believe you are looking for something like this:
SELECT *
FROM table_name c
WHERE c.LocaleID =
case when (select count(*)
from table_name tn
where tn.LocaleID = #LanguageCode) = 0 then
'ENU'
else
#LanguageCode
end;
Here is the demo.
Here is a new demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=624100ee44f89decc4c6383e92d0a016
In the new demo I have added some more code to show how to use it in left join as a part of the join condition and also I have added the code to show how to use it in where clause when joining two tables and I believe it belongs in the where clause...

PostgreSQL Update and return

Let's say I have a table called t in Postgres:
id | group_name | state
-----------------------------
1 | group1 | 0
2 | group1 | 0
3 | group1 | 0
I need to update the state of a row by ID, while also returning some things:
The old state
The remaining number of rows in the same group that have state = 0
I've got a query to do this as follows:
UPDATE t AS updated SET state = 1
FROM t as original
WHERE
updated.id = original.id AND
updated.id = :some_id
RETURNING
updated.state AS new_state,
original.state AS old_state,
(
SELECT COUNT(*) FROM t
WHERE
group_name = updated.group_name AND
state = 0
) as remaining_count;
However, it seems like the subquery within RETURNING is executed before the update has completed, leaving me with a remaining_count that is off by 1.
Additionally, I'm not sure how this behaves when concurrent queries are run. If we update two of these rows at the same time, is it possible that they would both return the same remaining_count?
Is there a more elegant solution to this? Perhaps some sort of window/aggregate function?
The subquery is indeed run without seeing the change from the UPDATE, because it's running before the UPDATE has committed, and therefore it's not visible. Nevertheless, it's an easy fix; just add a where clause to filter out the ID you just updated in the subquery, making your query something like this:
UPDATE t AS updated SET state = 1
FROM t as original
WHERE
updated.id = original.id AND
updated.id = :some_id
RETURNING
updated.state AS new_state,
original.state AS old_state,
(
SELECT COUNT(*) FROM t
WHERE
group_name = updated.group_name AND
state = 0 AND
t.id <> :some_id /* this is what I changed */
) as remaining_count;
Concurrency-wise, I'm not sure what the behavior would be, TBH; best I can do is point you at the relevant docs.
You could try (non-recursive) WITH queries, aka Common Table Expressions (CTEs). Their general structure is as follows:
WITH auxiliary_query_name AS (
auxiliary_query_expression;
)
[, WITH ...]
primary_query_expression;
Normally, auxiliary_query_expression and primary_query_expression run concurrently, and if they refer to the same underlying tables, the result is unpredictable. However, you can refer to auxiliary_query_name from within primary_query_expression, and from other auxiliary queries, thus enforcing a run sequence, where the referring query has to wait for the referred one to complete. Some finer points may apply, but that's the gist of it. CTEs also come with the advantage of being computed only once.
Regarding your query specifically, assuming that what you want in the end is the ID of the updated item, its old state, new state, the group it belongs to, and how many other items of that group are left to update, I believe the following would achieve this. I slightly modified the original query to update multiple items at once, to show how this approach shines (beside the clear sequence, it's performance advantages are moot if you update only a single item at a time).
WITH updated_t AS (
UPDATE t AS updated SET state = 1
FROM t as original
WHERE
updated.id = original.id AND
updated.id in :array_of_IDs -- I changed this
RETURNING
updated.id,
original.state AS old_state,
updated.state AS new_state,
updated.group_name
),
WITH remaining AS (
SELECT t.group_name, count(*) as remaining_count
-- we need to JOIN then filter out the updated rows because
-- all WITH in a statement share the same snapshot, thus have
-- the same starting "view" of base tables.
FROM t LEFT JOIN updated_t
ON t.id = updated_t.id
WHERE updated_t.id is NULL
AND t.group_name in (SELECT DISTINCT group_name from updated_t)
AND t.state = 0
GROUP BY group_name
)
SELECT
updated_t.id,
updated_t.group_name,
updated_t.old_state,
updated_t.new_state,
remaining.remaining_count
FROM updated_t, remaining
WHERE
updated_t.group_name = remaining.group_name;

sql server if statement not working

Can anyone advise me as to what is wrong with the following SQL server update statement:
IF (SELECT * FROM TBL_SystemParameter WHERE code='SOUND_WRONG_GARMENT') = ''
GO
UPDATE TBL_SystemParameter
SET [Value] = 'Ping.wav'
WHERE ID = (SELECT ID
FROM TBL_SystemParameter
WHERE code = 'SOUND_WRONG_GARMENT')
You don't need an if statement - you can just run the update statement, and if the subquery returns no rows, no rows will be updated. The if won't really save anything - you're performing two queries instead of one.
You either want
UPDATE TBL_SystemParameter
SET [Value] = 'Ping.wav'
WHERE ID In (SELECT ID
FROM TBL_SystemParameter
WHERE code = 'SOUND_WRONG_GARMENT')
if there are multiple ID's with that code OR use
UPDATE TBL_SystemParameter
SET [Value] = 'Ping.wav'
WHERE code = 'SOUND_WRONG_GARMENT'
either way and lose the IF statement as #Mureinik said.
Although Mureinik's answer is the logical solution to this, I will answer why this isn't actually working. Your condition is wrong, and this approach will work instead using IF EXISTS:
IF EXISTS (SELECT * FROM TBL_SystemParameter WHERE code='SOUND_WRONG_GARMENT')
BEGIN
UPDATE TBL_SystemParameter
SET [Value] = 'Ping.wav'
WHERE ID IN (SELECT ID
FROM TBL_SystemParameter
WHERE code = 'SOUND_WRONG_GARMENT')
END
As a side note, you're using an = sign instead of IN, which means you'll be matching to an arbitrary singular ID and only update 1 row based on this. To use a set based operation, use the IN clause.
You could actually 'golf' this by doing away with the derived query altogether, and using a simple WHERE code='SOUND_WRONG_GARMENT' on the table you're updating on.

Randomly Select a Row with SQL in Access

I have a small access database with some tables. I am trying the code in the sql design within access. I just want to randomly select a record within a table.
I created a simple table called StateAbbreviation. It has two columns: ID and Abbreviation. ID is just an autonumber and Abbreviation are different abbreviations for states.
I saw this thread here. So I tried
SELECT Abbreviation
FROM STATEABBREVIATION
ORDER BY RAND()
LIMIT 1;
I get the error Syntax error (missing operator) in query expresion RAND() LIMIT 1. So I tired RANDOM() instead of RAND(). Same error.
None of the others worked either. What am I doing wrong? Thanks.
Ypercude provided a link that led me to the right answer below:
SELECT TOP 1 ABBREVIATION
FROM STATEABBREVIATION
ORDER BY RND(ID);
Note that for RND(), I believe that it has to be an integer value/variable.
You need both a variable and a time seed to not get the same sequence(s) each time you open Access and run the query - and to use Access SQL in Access:
SELECT TOP 1 Abbreviation
FROM STATEABBREVIATION
ORDER BY Rnd(-Timer()*[ID]);
where ID is the primary key of the table.
Please try this, it is helpful to you
It is possible by using a stored procedure and function, which I created it's have a extra column which you could be create in your table FLAG name and column all field value should be 0 Then it works
create Procedure proc_randomprimarykeynumber
as
declare #Primarykeyid int
select top 1
#Primarykeyid = u.ID
from
StateAbbreviation u
left join
StateAbbreviation v on u.ID = v.ID + 1
where
v.flag = 1
if(#Primarykeyid is null )
begin
UPDATE StateAbbreviation
SET flag = 0
UPDATE StateAbbreviation
SET flag = 1
WHERE ID IN (SELECT TOP 1 ID
FROM dbo.StateAbbreviation)
END
ELSE
BEGIN
UPDATE StateAbbreviation
SET flag = 0
UPDATE StateAbbreviation
SET flag = 1
WHERE ID IN (#Primarykeyid)
END
SET #Primarykeyid = 1
SELECT TOP 1
ID, Abbreviation
FROM
StateAbbreviation
WHERE
flag = 1
It is made in stored procedure run this and get serial wise primary key
exec proc_randomprimarykeynumber
Thanks and regard
Try this:
SELECT TOP 1 *
FROM tbl_name
ORDER BY NEWID()
Of course this may have performance considerations for large tables.

Create table from SQL query

This is one annoying issue and I can't figure out how to solve it. I'm Using Microsoft SQL Server 2008.
So I have two tables and I need to update both of them. They share a common key, say id. I want to update Table1 with some stuff and then update the Table2 rows which were respectively modified in Table1.
The issue is that I don't quite know which rows were modified, because I'm picking them randomly with ORDER BY NEWID() so I probably cannot use a JOIN on Table2 in any way. I am trying to save the necessary details which were modified in my query for Table1 and pass them to Table2
This is what I'm trying to do
CREATE TABLE IDS (id int not null, secondid int)
SELECT [Table1].[id], [Table1].[secondid]
INTO IDS
FROM
(
UPDATE [Table1]
SET [secondid]=100
FROM [Table1] t
WHERE t.[id] IN
(SELECT TOP 100 PERCENT t.[id] FROM [Table1]
WHERE (SOME_CONDITION)
ORDER BY NEWID()
)
)
UPDATE [Table2]
SET some_column=i.secondid
FROM [Table2] JOIN IDS i ON i.id = [Table2].[id]
But I get
Incorrect syntax near the keyword 'UPDATE'.
So the question is: how can I solve the syntax error or is it a better way to do this?
Note: the query enclosed between the parentheses of the first FROM worked well before this new requirement, so I doubt there's a problem in there. Or maybe?
EDIT: Changing the second UPDATE as skk suggested still leads to the same error (on exactly the below line which contains UPDATE):
UPDATE [Table2]
SET some_column=i.secondid
FROM [Task] JOIN IDS i on i.[id]=[Table2].[id]
WHERE i.id=some_value
Instead of creating a new table manually, SQL server has the OUTPUT clause to help with this
It's complaining because you aren't aliasing the derived table used in the first query, immediately preceding UPDATE [Table2].
If you add an alias, you'll get a different error:
A nested INSERT, UPDATE, DELETE, or MERGE statement must have an OUTPUT clause.
Which leads back to #Adam Wenger's answer.
Not sure I completely understand what you are trying to do, but the following sql will execute (after replacing SOME_CONDITION):
CREATE TABLE IDS (id int not null, secondid int)
UPDATE t SET [secondid] = 100
OUTPUT inserted.[id], inserted.[secondid] into [IDS]
FROM [Table1] t
WHERE t.[Id] IN
(
SELECT TOP 100 PERCENT t.[id] from [Table1]
WHERE (SOME_CONDITION)
ORDER BY NEWID()
)
UPDATE [Table2]
SET some_column = i.secondid
FROM [Table2] JOIN IDS i ON i.id = [Table2].[id]
The Update syntax is as follows
UPDATE TableName SET ColumnName = Value WHERE {Condition}
but you have used FROM keyword also in that.
EDIT:
You change the code like follows and try again
UPDATE [Table2] SET some_column=IDS.secondid WHERE IDS.[id] = [Table2].[id] and
IDS.id=some_value