Why is the ##ERROR always 0 and ##ROWCOUNT always 1? - sql

I create a table like:
then I execute this statement:
update test
set tname = 'Joker'
where tid % 2 = 0
it shows:
which means 'there are two rows affected' in Chinese.
But if execute print ##rowcount immediately, the result is:
What else, if execute insert into test values('Paul','foo'), the result is:
which means:
Message 8101, Level 16, Status 1, Line 21.
Only when column list is used and IDENTITY_INSERT is ON, you can set values in the identity column of 'test' table explicitly'.
But if then execute print ##ERROR, it shows:
which I think should be 8101.
Could someone tell me why? Thanks

First issue is not replicable, I have prepared demo script
create table test(tid int identity(1,1), tname varchar(100))
insert into test values ('James'),('Jake'),('Tom'),('Mary')
update test
set tname = 'Joker'
where tid % 2 = 0
select ##ROWCOUNT --returns 2
Second issue is because you are running the select #error statement separately. That's why you are getting 0. If you run the last two statements together you will get 545 as result
set identity_insert test on
insert into test values ('James'),('Jake'),('Tom'),('Mary')
select ##ERROR --returns 545

For the second question (it would have been a good idea to post these as 2 separate questions), your SQL statement is trying to insert 'Paul' into the tid column. You need to separate the values like this:
INSERT INTO test VALUES ('Paul'), ('foo')
Better yet, be explicit with the column names:
INSERT INTO test (tname) VALUES ('Paul'), ('foo')

Related

Microsoft SQL Server - default value provided by stored procedure

Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure the parameters of which are values passed to insert into the row?
For example, I have table User:
| username | name | surname | id |
Insert looks like this:
INSERT INTO USER (username, name, surname)
VALUES ('myusername', 'myname', 'mysurname');
The id column is populated with an (integer) value retrieved by calling stored procedure mystoredproc with parameters myusername, myname, mysurname.
A further question is, would this stored procedure be called on each row, or can it be called in a grouped fashion. For example, I'd like my stored procedure to take the name and append a random integer to it so that that if I insert 100 users with the name 'David', they will get the same id and the stored procedure will be called only once. A bit of a bad example on the second point.
Good day,
Is it possible to have a non-null column where the value is generated at insert by calling a stored procedure
Option 1: please check if this work for you
Specify Default Value for the Column and use "NOT NULL"
create trigger on the table AFTER INSERT
Inside the trigger, you can use the virtual table "inserted" in order to get the inserted values.
Using these values (using the inserted table) you can update the column using the logic you need for all the rows at once
** there is no need to use external SP probably, but you can execute SP from trigger if needed
** All executed by a trigger is in the same transaction as the original query.
would this stored procedure be called on each row
NO! The trigger will be executed once for all rows you insert in the same statement. The inserted table includes all the rows which were inserted. In your update section (step 4) you can update all the rows which were inserted in once and no need to execute something for each row
** If you do use external SP which is executed from the trigger then you can pass it all the inserted table as one using Table-Valued Parameter
------------------- update ---------------
Here is a full example of using this logic:
drop table if exists T;
CREATE TABLE T (id int identity(2,2), c int NOT NULL default 1)
GO
CREATE TRIGGER tr ON T AFTER INSERT
AS BEGIN
SET NOCOUNT ON;
UPDATE T SET T.c = T2.C + 1
FROM inserted T2
INNER JOIN T T1 ON T1.id = T2.id
END
INSERT T(c) values (1) -- I insert the value 1 but the trigger will change it to 1+1=2
select * from T
GO
-- test multiple rows:
INSERT T(c) values (10),(20),(30),(40)
select * from T
GO
DECLARE #rc INT = 0,
#UserID INT = ABS(CHECKSUM(NEWID())) % 1000000 + 1;
WHILE #rc = 0
BEGIN
IF NOT EXISTS (SELECT 1 FROM dbo.Users WHERE UserId= #UserId)
BEGIN
INSERT dbo.Users(UserId) WHERE Username = #UserName SELECT #UserId;
SET #rc = 1;
END
ELSE
BEGIN
SELECT #UserId = ABS(CHECKSUM(NEWID())) % 1000000 + 1,
#rc = 0;
END
END

instead of trigger result output

I have created my first trigger. Please see the code section for the trigger below.
The triggers and the results are as expected, except for one thing.
So when I run the code below it will not insert the values into my table so the number of records remains unchanged.
insert into MatlabSearchPath(directory, userName)
values('madeup', 'default')
In the messages window though I get two lines. I don't understand why I see two lines and in particular 1 row affected - the number of records in my table hasn't changed?
(0 row(s) affected)
(1 row(s) affected)
Trigger
create trigger trDefaultPathInsert on DVLP_QES.dbo.MatlabSearchPath
instead of insert
as
begin
declare #defCount int
declare #retVal int
select #defCount = count(userName) from inserted where userName = 'Default'
if (#defCount > 0)
begin
select #retVal = count(HostName) from DVLP_QES.dbo.UserHostName where HostName = HOST_NAME()
if (#retVal > 0)
begin
insert into MatlabSearchPath select * from inserted
end
else
begin
insert into MatlabSearchPath select * from inserted where inserted.userName <> 'Default'
end
end
end
Update
I should mention that there a 3 triggers on this table, one is the trigger above the other one is a delete & the last one is an update
Your trigger does the following:
Counts records you are trying to insert, where userName equals 'Default'
In your case, count is 1.
Pay attention to your collation - if it's case sensitive, you are going to skip that whole branch of code.
If you enter the if branch, next thing trigger checks is if there are rows in UserHostName table where HostName equals host name of your client; pay attention that you don't think it should be host name of your server or something like that
If you enter the TRUE-branch, it should insert everything to the table; however, if not, it shouldn't insert anything. Of course, except if the collation is case sensitive, then revert the logic.
I I were you, I would add PRINT statements into trigger, just to make sure how does it execute.
create trigger trDefaultPathInsert on DVLP_QES.dbo.MatlabSearchPath
instead of insert
as
begin
declare #defCount int
declare #retVal int
select #defCount = count(userName) from inserted where userName = 'Default'
PRINT '#defCount'
PRINT #defCount
if (#defCount > 0)
begin
select #retVal = count(HostName) from DVLP_QES.dbo.UserHostName where HostName = HOST_NAME()
PRINT '#retVal'
PRINT #retVal
if (#retVal > 0)
begin
PRINT 'TRUE-BRANCH'
insert into MatlabSearchPath select * from inserted
end
else
begin
PRINT 'FALSE-BRANCH'
insert into MatlabSearchPath select * from inserted where inserted.userName <> 'Default'
end
end
EDIT
It seems that the message about rows affected can't be controlled inside the trigger. Even the standard SET NOCOUNT ON on the trigger beginning won't stop it from showing. This gave me notion that the message is a result of the trigger being successfully finished by calling it with X rows, where X will eventually be in the X row(s) affected message.
This SO question furtherly confirms the problem.
The situation here if the first message indicating cero is because the instead of trigger is uses to ignore the insert you sent and do instead whats in the trigger
You can debug your code with management studio

How to check if value is inserted successfully or not?

I have a procedure where I insert values into my table.
declare #fName varchar(50),#lName varchar(50),#check tinyint
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
EDITED:
Now I want check if it inserted successfully set #check = 0 else #check = 1
You can use ##ROWCOUNT server variable immediately after the insert query to check number of affected rows by the insert operation.
declare #fName varchar(50) = 'Abcd',
#lName varchar(50) = 'Efgh'
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
PRINT ##ROWCOUNT --> 0- means no rows affected/nothing inserted
--> 1- means your row has been inserted successfully
For your requirement, you could use a Case statement(as per comment):
--If you need #check as a bit type please change Int to bit
DECLARE #check Int = CASE WHEN ##ROWCOUNT = 0 THEN 1 ELSE 0 END
You need to use ##ROWCOUNT
It returns the number of rows affected by the last statement. If the number of rows is more than 2 billion, use ROWCOUNT_BIG.
##ROWCOUNT is both scope and connection safe.
In fact, it reads only the last statement row count for that
connection and scope.
It’s safe to use ##ROWCOUNT in SQL Server even when there is a trigger
on the base table. The trigger will not skew your results; you’ll get
what you expect. ##ROWCOUNT works correctly even when NOCOUNT is set.
so you query should be:
declare #fName varchar(50), #lName varchar(50), #check tinyint = 0
...
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
if ##ROWCOUNT>0
set #check = 1
You can use ##rowcount after insert table, like this:
DECLARE #check int
INSERT INTO Employees (Name,Email,Phone,[Address])
VALUES('Test','test#mail.com','','')
if(##ROWCOUNT>0)
SET #check=1
SELECT #check;
In SQL-Sever you can use OUTPUT clause to check if values are inserted successfully.
By following query
declare #fName varchar(50),#lName varchar(50)
INSERT INTO myTbl(fName,lName) OUTPUT inserted.* values(#fName,#lName) ;
IF the values are inserted it will show output of inserted values. You can also store these values into new table.
I can say you can simply check the previous numbers of rows before inserting the new.
for example previous you have 50 rows. just store the numbers of rows in a variable and check after the insert query if numbers of row increased or no.
yes little extra chunk of code but easy to understand

Get ROWCOUNT from INSTEAD OF TRIGGER

A legacy app does an INSERT on a table with an instead of trigger and subsequently uses the rowcount for further processing.
We now need to opt out of certain INSERTs with the use of an INSTEAD OF INSERT trigger.
The problem is that ##ROWCOUNT still returns the number of attempted inserts.
For example, a fictitious trigger that will never complete an insert might be
ALTER TRIGGER [dbo].[trig_ACCOUNT_CREDITS_RunningTotalINSERT]
ON [dbo].[ACCOUNT_CREDITS]
INSTEAD OF INSERT
AS
BEGIN
--tried with NOCOUNT ON and OFF
SET NOCOUNT OFF;
--This is an example of the branching logic that might determine
--whether or not to do the INSERT
IF 1=2 --no insert will ever occur (example only)
BEGIN
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT COL1, COL2 from INSERTED
END
END
and some INSERT statements might be
--No rows will be inserted because value of COL1 < 5
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2) VALUES ( 3, 3)
--We would assume row count to be 0, but returns 1
select ##ROWCOUNT
--No rows will be inserted because value of COL1 < 5
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT 1, 1
union all
SELECT 2, 2
--We would assume row count to be 0, but returns 2
select ##ROWCOUNT
I can work around the issue, but it bothers me that I can't trust ##ROWCOUNT. I can find no reference to this issue on SO or those other knowledge banks. Is this simply a case of TRIGGERS ARE EVIL?
Can I affect ##ROWCOUNT?
Some statements may change ##ROWCOUNT inside the trigger.
Statement
SELECT * FROM INSERTED WHERE COL1 < 5
executes and set ##ROWCOUNT to 1
Put statement
SET NOCOUNT ON;
then
IF NOT EXISTS (SELECT * FROM INSERTED WHERE COL1 < 5)
BEGIN
SET NOCOUNT OFF;
INSERT INTO dbo.ACCOUNT_CREDITS (COL1, COL2)
SELECT COL1, COL2 from INSERTED
END
The Problem
I need information in the context of the main process that is only available in the context of the trigger.
The Solution
Whether getting ##ROWCOUNT or anything else from a trigger, or even passing information to a trigger, there are two methods that allow for sharing information with triggers:
SET CONTEXT_INFO / CONTEXT_INFO()
Local Temporary Tables (i.e. tables with names starting with a single #: #tmp)
I posted an example of using CONTEXT_INFO in an answer on a related question over at DBA.StackExchange: Passing info on who deleted record onto a Delete trigger. There was a discussion in the comments on that answer related to possible complications surrounding CONTEXT_INFO, so I posted another answer on that question using a temporary table instead.
Since that example dealt with sending information to a trigger, below is an example of getting information from a trigger as that is what this question is about:
First: Create a simple table
CREATE TABLE dbo.InsteadOfTriggerTest (Col1 INT);
Second: Create the trigger
CREATE TRIGGER dbo.tr_InsteadOfTriggerTest
ON dbo.InsteadOfTriggerTest
INSTEAD OF INSERT
AS
BEGIN
PRINT 'Trigger (starting): ' + CONVERT(NVARCHAR(50), ##ROWCOUNT);
SET NOCOUNT ON; -- do AFTER the PRINT else ##ROWCOUNT will be 0
DECLARE #Rows INT;
INSERT INTO dbo.InsteadOfTriggerTest (Col1)
SELECT TOP (5) ins.Col1
FROM inserted ins;
SET #Rows = ##ROWCOUNT;
PRINT 'Trigger (after INSERT): ' + CONVERT(NVARCHAR(50), #Rows);
-- make sure temp table exists; no error if table is missing
IF (OBJECT_ID('tempdb..#TriggerRows') IS NOT NULL)
BEGIN
INSERT INTO #TriggerRows (RowsAffected)
VALUES (#Rows);
END;
END;
Third: Do the test
SET NOCOUNT ON;
IF (OBJECT_ID('tempdb..#TriggerRows') IS NOT NULL)
BEGIN
DROP TABLE #TriggerRows;
END;
CREATE TABLE #TriggerRows (RowsAffected INT);
INSERT INTO dbo.InsteadOfTriggerTest (Col1)
SELECT so.[object_id]
FROM [master].[sys].[objects] so;
PRINT 'Final ##ROWCOUNT (what we do NOT want): ' + CONVERT(NVARCHAR(50), ##ROWCOUNT);
SELECT * FROM #TriggerRows;
Output (in Messages tab):
Trigger (starting): 91
Trigger (after INSERT): 5
Final ##ROWCOUNT (what we do NOT want): 91
Results:
RowsAffected
5

Assertion in MySQL

I have a SQL script to run against a large database. I'd like to put a couple of simple queries at the start, just as a sanity check.
Is there any way to write an assertion in MySQL? Or any kind of "select ..., and if it doesn't match this value, then abort the entire script"?
Some crazy code. Main point is: SET could raise error for mysql variables.
For example.
SET #value = 0;
SET SESSION sql_mode = if(#value, ##SESSION.sql_mode, 'something wrong uphere');
Would output ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of 'something wrong uphere' and execution would be stopped.
This approach is not semantic but it works.
You could put the entire script in an if statement, depending on what kind of value you need to check, here's an example:
DECLARE #value int
SET #value = (SELECT COUNT(*) FROM dbo)
IF #value >0
BEGIN
--Do Stuff Here
END
You can also do this via a stored procedure / function, as in the example below:
CREATE FUNCTION `RunProcess`() RETURNS INT(11)
runProcess:BEGIN
DECLARE check_value INT;
DECLARE error_code INT;
SELECT COUNT(*) FROM dbo INTO check_value;
IF check_value = 0 THEN set error_code = 666;
LEAVE runProcess;
RETURN error_code;
END IF;
...
...
END;
One way to go about this could be to make an insert fail on purpose if you assertion fails. This is not pretty, but it is simple and it works.
Let's say Table1 contains columnA and columnB. You want to insert into this table the values ('any', 'thing'), but ONLY if these values fulfil some conditions.
Then do something like this:
SELECT 'any', 'thing' INTO #setA, #setB FROM DUAL WHERE ( your conditions...);
INSERT INTO Table1 (columnA, columnB) VALUES (#setA, #setB);
the 'any' and 'thing' values would obviously be inserted at runtime in the query.
If you Table1 is set up to accept only non-null values in column A or B, this will fail when your conditions fail.
A very simple nonsensical example (this obviously can be achieved otherwise, this is only an example :-) ), if you want the values to differ:
SELECT 'any', 'thing' INTO #setA, #setB FROM DUAL WHERE ( 'a'!='b');
INSERT INTO Table1 (columnA, columnB) VALUES (#setA, #setB);
The test condition doesn't have to be on the values you are trying to insert:
SELECT 'any', 'thing' INTO #setA, #setB FROM DUAL WHERE ( 'another' IS NOT NULL);
INSERT INTO Table1 (columnA, columnB) VALUES (#setA, #setB);
If your goal is not to do perform an insert, the same logic can be applied on different transaction types, the idea being to alter the variables so they make the query fail.
Or you can create a dummy table just for this purpose, with a non-null column, and always insert your assertion result in the dummy table's first row. Not pretty, but it works :-)