T-SQL - need to run update if only one record returned by query - sql

I need to update a record IFF there is only one record matching my search criteria. Here's what I have, but it's crude:
DECLARE #TestCount INT;
SELECT #TestCount = COUNT(*)
FROM TestRecords tr
WHERE
tr.UnitSerial = #UnitSerial
AND
tr.PassFailStatus = 1;
IF (#TestCount = 1)
UPDATE
TestRecords
SET
Invalid = 1
WHERE
TestRecordID =
(SELECT TestRecordID
FROM TestRecords tr
WHERE
tr.UnitSerial = #UnitSerial
AND
tr.PassFailStatus = 1);
Of course this is example code - there are more restrictions and tables joins, etc in the SELECT statement, and it's all wrapped by a transaction, but this is the gist of the stored proc logic.
I'm thinking there has to be a better way but I don't know what that is. Any suggestions?
Thanks, Dave

You could do it in one query as:
with toupdate as (
select tr.*,
count(*) over () as cnt
from TestRecords tr
where tr.UnitSerial = #UnitSerial AND tr.PassFailStatus = 1
)
update toupdate
set Invalid = 1
where cnt = 1
This assumes you are using SQL 2005 or greater.

I think your code will run without problems!
Use ##ROWCOUNT variable to determine how many records was affected by a SELECTE, UPDATE, INSERT statement: but in your case you have just set your #TestCount variable with the same result!

It's not far off what I would do, which is simply:
IF (SELECT COUNT(*) FROM x WHERE y = z) = 1
BEGIN
--statements
END
Of course, you could replace the condition with anything, e.g. a UDF with more complex dynamic conditions.

Related

How to Update a column Before Select?

I am setting a stored procedure for select and I want to update the value of one column in the database Before doing the Select.
This is what I tried but it's not working.
#roleID int and #query varchar(240)
SELECT
EP.Equipe_Projet_Id AS PROJET_ID,
U.USR_ID,
CleRepartition = CASE
WHEN #RoleID = 1 AND #query IS NOT NULL
THEN 100
AND (UPDATE EQUIPE_PROJET SET CleRepartition = 100
WHERE EP.Equipe_Projet_Id = #PROJET_ID AND EP.Role_Id = 3)
ELSE NULL
END
FROM
[EQUIPE_PROJET] EP
Expecting update of column on database and having it's value
i want to update the value of Column CleRepartition in the database while selecting.
This is not possible in a SELECT query. A SELECT retrieves data from the database. An UPDATE modifies data. These are two separate statements and cannot be combined.
You are doing this work in a stored procedure. Within a stored procedure, you can run an UPDATE and SELECT in any order, so you can accomplish both tasks. If you are concerned about data changing in the database between the two statements, you can wrap them in a transaction.
Stored procedure can do update then select after.
So I added the query of update in the beginning, then I do the select.
Like this:
UPDATE EP
SET CleRepartition = CASE
WHEN #RoleID = 1 AND #query IS NOT NULL
THEN 100
AND (UPDATE EQUIPE_PROJET
SET CleRepartition = 100
WHERE EP.Equipe_Projet_Id = #PROJET_ID
AND EP.Role_Id = 3)
ELSE NULL
END
FROM [EQUIPE_PROJET] EP
SELECT
EP.Equipe_Projet_Id AS PROJET_ID,
U.USR_ID,
CleRepartition
FROM
[EQUIPE_PROJET] EP
I hope that this will help someone.

How to store data from a SELECT statement and use that data to loop with an UPDATE statement

I am using PLSQL and I want to store the query results form SELECT statement in an array and then I want to loop using the elements from that array to UPDATE all the rows. The problem with the code below is that it returns a single-row. Sub-query returns more than one row because he is trying to set more than one variable in a row. Can you help me in this situation?
This is my code:
CREATE OR REPLACE PROCEDURE looping IS
BEGIN
FOR rec IN (SELECT IID FROM DATMCCN0)
LOOP
UPDATE DATMCCN0
SET E_NOME = (SELECT I_NOME FROM DAT_CCNCONFIG0 INNER JOIN DATMCCN0 ON DAT_CCNCONFIG0.I_NOME = DATMCCN0.CAPLIC where DATMCCN0.IID = rec.IID)
where IID = rec.IID;
END LOOP;
END;
EXECUTE looping;
You do not need a loop and can do it all in one MERGE statement (assuming your correlated query returns a single row for each IID):
CREATE OR REPLACE PROCEDURE looping
IS
BEGIN
MERGE INTO DATMCCN0 dst
USING (
SELECT b.IID,
I_NOME
FROM DAT_CCNCONFIG0 a
INNER JOIN DATMCCN0 b
ON a.I_NOME = b.CAPLIC
) src
ON ( src.IID = dst.IID)
WHEN MATCHED THEN
UPDATE SET E_NOME = src.I_NOME;
END;
If it does not then you will need to get only a single row, something like this:
CREATE OR REPLACE PROCEDURE looping
IS
BEGIN
MERGE INTO DATMCCN0 dst
USING (
SELECT b.IID,
MAX( I_NOME ) AS I_NOME
FROM DAT_CCNCONFIG0 a
INNER JOIN DATMCCN0 b
ON a.I_NOME = b.CAPLIC
GROUP BY b.IID
) src
ON ( src.IID = dst.IID)
WHEN MATCHED THEN
UPDATE SET E_NOME = src.I_NOME;
END;
One literal answer for your question "How to store data from a SELECT statement and use that data [to loop] with an UPDATE statement" would be a statement like this:
UPDATE
(SELECT src.E_NOME, dst.I_NOME
FROM DAT_CCNCONFIG0
JOIN DATMCCN0 src ON DAT_CCNCONFIG0.I_NOME = scr.CAPLIC
JOIN DATMCCN0 dst ON src.IID = dst.IID)
SET E_NOME = I_NOME;
However, it does not solve your problem that a single-row subquery returns more than one. Have a look at MT0's answer for that.

SQL Set variable to select result

I was wondering if it is possible to set a declared variable to a return value from a select result? Something like:
#WatchedSeconds
SET #WatchedSeconds = 200
DECLARE #SelectedVideo int
SET #SelectedVideo = (SELECT TOP 1 * FROM Video v WHERE v.VideoID = 12)
IF #SelectedVideo IS NOT NULL
BEGIN
IF #SelectedVideo.VideoLength = #WatchedSeconds
BEGIN
--DO SOMETHING
END
IF #SelectedVideo.SomeOtherColumn = #SomethingElse
BEGIN
END
END
It's for using some information from the SELECT result multiple places in a Stored Procedure.
I know that I can set a variable to e.g, a integer, and set it to the selected result, if it returns a integer, e.g:
DECLARE #VideoSeconds int
SET #VideoSeconds = (SELECT v.Length FROM Video v WHERE v.VideoID = #VideoID)
This way I have to make multiple variables, and multiple SELECT calls if I need to use more values from the Video result. And that's what I want to avoid.
You can do this simply by running:
SELECT #videoSeconds = v.Length FROM Video v WHERE v.VideoID = #VideoID
so as to not add the SET part.
Also, you must make sure that only 1 row is being returned by the query, otherwise it will generate an error.
You can try something like
(declare variables first...)
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
EDIT: All together this seems not to be the best approach... With SQL you should not think in values and single rows but rather in result sets (set based programming). Your thinking leads to many tiny selects, while loops, cursors and all this stuff one should avoid.
You can store the results in a temporary table or table variable:
SELECT TOP 1 *
INTO #SelectedVideo
FROM Video v
WHERE v.VideoID = 12;
Then you can assign values from the table later in your code. Something like:
IF ( (SELECT VideoLength FROM #SelectedVideo) = #WatchedSeconds)
However, for your particular example, if you have an index on video(VideoId), then there is little to be gained performance-wise from using a temporary table.
If what you're trying to get is similar to returning a dataset in a procedural language (so you can type something like Result.Field1 = 'Test') then I don't think this is possible. You'll just need to declare multiple variables and make the SELECT call as
SELECT TOP 1 #var1=col1, #var2=col2, #var3=col3, [...] FROM YourTable WHERE YourFilter
as #Shnugo suggests
The 'dataset' equivalent structure in SQL is cursors, but they require variables to be set up as well, so there's no benefit there.

How to delete all rows without deleting the last returned row in SQL Server?

I want to delete all the rows from a SELECT without deleting the last returned row by using a trigger when a delete query is executed.
This trigger doesn't work so any help is greatly appreciated.
CREATE TRIGGER TR_StergereOfertaSpeciala
ON OferteSpeciale
INSTEAD OF DELETE
AS
DECLARE #nr INTEGER;
IF (EXISTS(SELECT * FROM DELETED))
BEGIN
SET #nr = (SELECT COUNT(*) FROM DELETED);
DELETE FROM (
SELECT TOP(#nr - 1)* FROM OferteSpeciale
INNER JOIN DELETED ON OferteSpeciale.codP = Deleted.codP
AND OferteSpeciale.codM = Deleted.codM
AND OferteSpeciale.dela = Deleted.dela)
END
Here is an example of getting your concept to work properly:
CREATE TRIGGER TR_StergereOfertaSpeciala
ON OferteSpeciale
INSTEAD OF DELETE
AS BEGIN
DECLARE #nr INT
SET #nr = (SELECT COUNT(*) FROM DELETED)
IF (#nr > 1) BEGIN
DELETE o
FROM OferteSpeciale AS o
INNER JOIN (SELECT TOP (#nr - 1) * FROM DELETED /* ORDER BY ??? */) AS d
ON o.codP = d.codP
AND o.codM = d.codM
AND o.dela = d.dela
END
END
Note the syntax for a delete with a join. Also note that we're arbitrarily choosing the 1 row to keep. I would suggest, as #RBarryYoung has mentioned, specifically ordering the set by something to know which row we are keeping.
Another way of doing this which could avoid the somewhat dynamic TOP clause (clever, BTW) would be to specifically exclude the record you want to keep using NOT EXISTS/IN
Also, you probably want to avoid trigger recursion and nested triggers in this case.

SQL - Counting Returned Records

I'm building a stored procedure. This stored procedure needs to insert a record if a record with a specific value does not exist. If the value does exist, I need to update the record. The problem I'm having is determining if a record with the given value exists or not. I am using the following code:
DECLARE #record1ID as char(36)
SET #record1ID = (SELECT TOP 1 ID FROM Person WHERE [Role]='Manager')
DECLARE #record2ID as char(36)
SET #record2ID = (SELECT TOP 1 d.ID FROM Department d WHERE d.[ManagerID]=#record1ID)
-- If #record2ID is set update record, otherwise add record
-- how do I setup this if/else statement?
Thank you!
If this were a SQL Server as it looks like, you could do a count like this:
declare #rec_counter as int
set #rec_counter = 0
select #rec_counter = count(*) FROM Department d WHERE d.[ManagerID]=#record1
if (#rec_counter > 0)
begin
-- do whatever here
end
IF (EXISTS YOUR_SELECT)
BEGIN ...
or
IF (#record2ID IS NULL)
BEGIN ...
or use select count(*) instead of selecting a value