My question has to do with the order of updates in a single update statement. I have observed that when I set variables using a SELECT statement, that the variables are set in order. For example:
SELECT
#Test1 = 2,
#Test2 = #Test1 + 1
At this point #Test1 will be 2 and #Test2 will be 3 because the set operations are done in order. But what happens with UPDATE?
UPDATE TestTable SET
Test1 = 2,
Test2 = Test1 + 1
Will Test2 use the initial value of Test1 in its calculation or will it use the value we just set? Does it make any difference if it is an UPDATE statement inside of a MERGE?
MERGE INTO TestTable AS T
USING (
SELECT
Test1,
Test2
FROM SomeOtherTable
) AS S
ON S.Test1 = T.Test1
WHEN MATCHED THEN
UPDATE SET
T.Test1 = 2,
T.Test2 = T.Test1 + 1
;
The names on the right hand side of the assignment refer to the old values of the columns, regardless of the order they are in.
This (for example) allows you to swap two values without using a temporary variable.
UPDATE foo
SET a = b,
b = a
http://sqlfiddle.com/#!3/f6984/1
The SQL-92 specification (Section 13.10, General Rules, Item 6 on page 395) states:
The <value expression>s are effectively evaluated for each row of T before updating any row of T.
Related
I'd like to update fields while referring to a dynamic column.
The goal is to automate a process because each month the column to refer to changes.
For example it's like having different columns like month1, month2, month3 until month24. Each month, only 1 column needs to be updated but it's a running number that is calculated in another table.
So my question is how to make the query dynamic so that every month i only update the column number that i want and not the other one.
I tried the script below but the following issue comes up
Error converting data type varchar to float.
DECLARE #PromoMonthNumber VARCHAR(60)
DECLARE #PromoMonth VARCHAR(600)
SET #PromoMonthNumber = (SELECT CurrentDemandIndex FROM RidgeSys) --This refer to a number that change all the time
SET #PromoMonth = 'SELECT ABC.PromotionHistory' + #PromoMonthNumber
UPDATE ABC
SET #PromoMonth = table2.promotionhistory
FROM ABC
INNER JOIN (
SELECT Article.code as code, sum(ROUND(#PromoMonth,0)) as promotionhistory
FROM Article
INNER JOIN ABC ON DEF.articlecode = ABC.Articlecode
) as table2
ON ABC.articlecode = table2.code)
Here is your issue:
SELECT Article.code as code, sum(ROUND(#PromoMonth,0)) as promotionhistory
Since #PromoMonth is defined as VARCHAR, if the value is non-numeric, it will fail. Here is an example:
This works fine:
declare #x varchar(100) = '1';
select sum(round(#x,0));
Result:
1
This fails with same error above:
declare #x varchar(100) = 'x';
select sum(round(#x,0));
Result:
Msg 8114, Level 16, State 5, Line 3
Error converting data type varchar to float.
You need to check that the value is numeric before you do the calculation.
Let's say we have simple table T1 which has three columns:
CREATE TABLE T1(C1 INT PRIMARY KEY, C2 VARCHAR(20), C3 XML)
Now we create simple data:
INSERT INTO T1 VALUES(1, 'Test', '<Element></Element>')
Then I want to modify third column to achieve something like this:
<Element>Test</Element>
Which means, C2 is inserted into XML.
So I wanted to do that with variables:
DECLARE #test VARCHAR(20) = 'Example'
UPDATE
T1
SET
#test = C2,
C3.modify('
replace value of
(/Element/text())[1]
with sql:variable("#test") ')
Unfortunately the result is:
<Element>Example</Element>
What I'm doing wrong?
You are modifying the underlying variable in the same statement where it is used; this doesn't work in SQL Server. Either:
Split variable assignment and usage into two separate statements:
DECLARE #test VARCHAR(20);
select #test = t.C2
from T1 t;
UPDATE t SET C3.modify('
replace value of (/Element/text())[1] with sql:variable("#test")
')
from T1 t;
OR
Use the value of the column directly:
UPDATE t SET C3.modify('
replace value of (/Element/text())[1] with sql:column("t.C2")
')
from T1 t;
Unless you have some complex logic behind the variable value calculation, the second option is preferred due to performance reasons - you touch the table only once, not twice. Also, the second variant is highly recommended if you need to update more than 1 row, each with different values.
I searched the web but cannot find a solution for my problem (but perhaps I am using the wrong keywords ;) ).
I've got a Stored Procedure which does some automatic validation (every night) for a bunch of records. However, sometimes a user wants to do the same validation for a single record manually. I thought about calling the Stored Procedure with a parameter, when set the original SELECT statement (which loops through all the records) should get an AND operator with the specified record ID. I want to do it this way so that I don't have to copy the entire select statement and modify it just for the manual part.
The original statement is as follows:
DECLARE GenerateFacturen CURSOR LOCAL FOR
SELECT TOP 100 PERCENT becode, dtreknr, franchisebecode, franchisenemer, fakgroep, vonummer, vovolgnr, count(*) as nrVerOrd,
FaktuurEindeMaand, FaktuurEindeWeek
FROM (
SELECT becode, vonummer, vovolgnr, FaktuurEindeMaand, FaktuurEindeWeek, uitgestfaktuurdat, levdat, voomschrijving, vonetto,
faktureerperorder, dtreknr, franchisebecode, franchisenemer, fakgroep, levscandat
FROM vwOpenVerOrd WHERE becode=#BecondeIN AND levdat IS NOT NULL AND fakstatus = 0
AND isAllFaktuurStukPrijsChecked = 1 AND IsAllFaktuurVrChecked = 1
AND (uitgestfaktuurdat IS NULL OR uitgestfaktuurdat<=#FactuurDate)
) sub
WHERE faktureerperorder = 1
GROUP BY becode, dtreknr, franchisebecode, franchisenemer, fakgroep, vonummer, vovolgnr,
FaktuurEindeMaand, FaktuurEindeWeek
ORDER BY MIN(levscandat)
At the WHERE faktureerperorder = 1 I came up with something like this:
WHERE faktureerperorder = 1 AND CASE WHEN #myParameterManual = 1 THEN vonummer=#vonummer ELSE 1=1 END
But this doesn't work. The #myParameterManual indicates whether or not it should select only a specific record. The vonummer=#vonummer is the record's ID. I thought by setting 1=1 I would get all the records.
Any ideas how to achieve my goal (perhaps more efficient ideas or better ideas)?
I'm finding it difficult to read your query, but this is hopefully a simple example of what you're trying to achieve.
I've used a WHERE clause with an OR operator to give you 2 options on the filter. Using the same query you will get different outputs depending on the filter value:
CREATE TABLE #test ( id INT, val INT );
INSERT INTO #test
( id, val )
VALUES ( 1, 10 ),
( 2, 20 ),
( 3, 30 );
DECLARE #filter INT;
-- null filter returns all rows
SET #filter = NULL;
SELECT *
FROM #test
WHERE ( #filter IS NULL
AND id < 5
)
OR ( #filter IS NOT NULL
AND id = #filter
);
-- filter a specific record
SET #filter = 2;
SELECT *
FROM #test
WHERE ( #filter IS NULL
AND id < 5
)
OR ( #filter IS NOT NULL
AND id = #filter
);
DROP TABLE #test;
First query returns all:
id val
1 10
2 20
3 30
Second query returns a single row:
id val
2 20
Lets have a following query:
SELECT * FROM {tablename} WHERE ColumnId = dbo.GetId()
where dbo.GetId() is non-deterministic user defined function. The question is whether dbo.GetId() is called only once for entire query and its result is then applied or is it called for each row? I think it is called for every row, but I don't know of any way how to prove it.
Also would following query be more efficient?
DECLARE #Id int
SET #Id = dbo.GetId()
SELECT * FROM {tablename} WHERE ColumnId = #Id
I doubt this is guaranteed anywhere. Use a variable if you want to ensure it.
I amended #Prdp's example
CREATE VIEW vw_rand
AS
SELECT Rand() ran
GO
/*Return 0 or 1 with 50% probability*/
CREATE FUNCTION dbo.Udf_non_deterministic ()
RETURNS INT
AS
BEGIN
RETURN
(SELECT CAST(10000 * ran AS INT) % 2
FROM vw_rand)
END
go
SELECT *
FROM master..spt_values
WHERE dbo.Udf_non_deterministic() = 1
In this case it is only evaluated once. Either all rows are returned or zero.
The reason for this is that the plan has a filter with a startup predicate.
The startup expression predicate is [tempdb].[dbo].[Udf_non_deterministic]()=(1).
This is only evaluated once when the filter is opened to see whether to get rows from the subtree at all - not for each row passing through it.
But conversely the below returns a different number of rows each time indicating that it is evaluated per row. The comparison to the column prevents it being evaluated up front in the filter as with the previous example.
SELECT *
FROM master..spt_values
WHERE dbo.Udf_non_deterministic() = (number - number)
And this rewrite goes back to evaluating once (for me) but CROSS APPLY still gave multiple evaluations.
SELECT *
FROM master..spt_values
OUTER APPLY(SELECT dbo.Udf_non_deterministic() ) AS C(X)
WHERE X = (number - number)
Here is one way to prove it
View
View is created to add a Nondeterministic inbuilt Functions inside user defined function
CREATE VIEW vw_rand
AS
SELECT Rand() ran
Nondeterministic Functions
Now create a Nondeterministic user defined Functions using the above view
CREATE FUNCTION Udf_non_deterministic ()
RETURNS FLOAT
AS
BEGIN
RETURN
(SELECT ran
FROM vw_rand)
END
Sample table
CREATE TABLE #test
(
id INT,
name VARCHAR(50)
)
INSERT #test
VALUES (1,'a'),
(2,'b'),
(3,'c'),
(4,'d')
SELECT dbo.Udf_non_deterministic (), *
FROM #test
Result:
id name non_deterministic_val
1 a 0.203123494465542
2 b 0.888439497446073
3 c 0.633749721616085
4 d 0.104620204364744
As you can see for all the rows the function is called
Yes it does get called once per row.
See following thread for debugging functions
SQL Functions - Logging
And yes the below query is efficient as the function is called only once.
DECLARE #Id int
SET #Id = dbo.GetId()
SELECT * FROM {tablename} WHERE ColumnId = #Id
I have a col1 in my table with initial value of 0 with one row, I want to get updated value of col1.
My update query is :
Update table set col1 = col1 + 1
I can get last update by put output like :
Update table set col1 = col1 + 1 OUTPUT inserted.col1
But I am not sure that output value is related same query or last updated by other query at same time.
The value returned is the value of this update statement. It will not reflect updates made by other users.
EDIT:
You can also store the value in a variable without an OUTPUT clause (https://msdn.microsoft.com/en-us/library/ms177523.aspx):
Update table set #col1 = col1 = col1 + 1;