SQL Server 2005 IF...ELSE incorrect execution - sql-server

I am using SQL Server 2005 - 9.00.5266.00 (X64)
I am not sure what did I miss in below SQL. Can somebody find out what is the problem?
Here it goes -
create table my_temp
(
col1 varchar(10)
)
--===================== the first block is executed
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'my_temp') and Name = 'col2')
begin
select 'alter table my_temp add col2 bit'
--alter table my_temp add col2 bit
end
else
begin
select 'update my_temp set col2 = null'
--update my_temp set col2 = null
end
--====================== the second block is executed
IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'my_temp') and Name = 'col2')
begin
--select 'alter table my_temp add col2 bit'
alter table my_temp add col2 bit
end
else
begin
--select 'update my_temp set col2 = null'
update my_temp set col2 = null
end
I am confused that the same IF statement executes different code blocks.

the problem related with batch
if you executed your code like that, where you seperate batch with use of GO statement
create table my_temp
(
col1 varchar(10)
)
GO
-- rest of script
then you will run your query each time ELSE part will executed because table is created
if you executed whole script first time with out GO statement then IF part will executed because at that time table will not create
Once you separate the QUERY table creation and other code then always ELSE part will executed

Related

Table valued function in Oracle

I have used the functions in SQL Server for a long time. The functions can be used in the FROM clause and the WHERE clause can be specified.
In SQL Server the WHERE clause is used within the function itself. Unlike Oracle, the WHERE clause is applied after the function has produced the results.
The difference is important if the function without WHERE returns many results.
Furthermore, in SQL Server the indices inside the function are used if the WHERE is applied on the column with the index.
Example:
CREATE TABLE table_test (
col1 varchar(50),
col2 varchar(50)
)
--INSERT TEST DATA
Declare #Id int
Set #Id = 1
While #Id <= 1000000
Begin
Insert Into table_test values ('col1' + CAST(#Id as nvarchar(10)), 'col2' + CAST(#Id as nvarchar(10)))
Set #Id = #Id + 1
End
CREATE FUNCTION func_test() RETURNS TABLE
AS
RETURN
(
SELECT * FROM table_test
)
GO
CREATE NONCLUSTERED INDEX ixd_test ON table_test (col1) INCLUDE (col2)
SELECT * FROM func_test() WHERE col1 like 'col132%'
Is there a similar type of function in Oracle?
Create table
CREATE TABLE table_test (
col1 varchar(50),
col2 varchar(50)
)
Once you fill the data in to test_table (using something like follows)
Begin
for i in 1..1000000 loop
Insert Into table_test values ('col1' || TO_CHAR(i), 'col2' || TO_CHAR(i));
end loop;
commit;
End;
/
Something like follows may work. (Tested using oracle live sql)
-- Create row definition
create OR REPLACE TYPE SOME_ROW_TYPE AS OBJECT
(
COL1 varchar2(50),
COL2 varchar2(50)
)
/
-- Create table definition
create type SOME_TABLE_TYPE as table of SOME_ROW_TYPE
/
CREATE OR REPLACE FUNCTION DoStuff RETURN SOME_TABLE_TYPE AS
-- Declarations
RET_TABLE SOME_TABLE_TYPE := SOME_TABLE_TYPE();
CURSOR DATA_FETCH IS
SELECT
COL1,
COL2
FROM TABLE_TEST;
BEGIN
FOR ITEM IN DATA_FETCH LOOP
RET_TABLE.extend;
RET_TABLE(RET_TABLE.LAST) := SOME_ROW_TYPE(
ITEM.COL1,
ITEM.COL2);
END LOOP;
RETURN RET_TABLE;
END;
/
Creating index
CREATE INDEX index_name ON table_test(col1)
fetch via
select * from DoStuff() WHERE col1 like 'col132%'

T-SQL - Insert trigger; IF EXISTS not evaluating as intended

Not sure what I'm missing. When I debug and step through the INSERT query I've included below, I see that '%a%' is the value of #Answer, and 103 is the value for #ItemId.
IF EXISTS is always evaluating to false when I insert the values shown beneath:
CREATE TRIGGER TR_cc_Additional_Information_Answers_INS
ON cc_Additional_Information_Answers
AFTER INSERT
AS
BEGIN
CREATE TABLE temp_answers
(
TempAnswer VARCHAR(50),
TempAdditional_Information_ItemID INT
)
INSERT INTO temp_answers (TempAnswer, TempAdditional_Information_ItemID)
SELECT Description, Additional_Information_ItemID
FROM inserted
DECLARE #Answer varchar(50)
SELECT #Answer = '''%' + t.TempAnswer + '%''' FROM temp_answers t
DECLARE #ItemId int
SELECT #ItemId = t.TempAdditional_Information_ItemID FROM temp_answers t
IF EXISTS(SELECT 1
FROM cc_Additional_Information_Answers a
WHERE a.Description LIKE #Answer
AND a.Additional_Information_ItemID = #ItemId)
BEGIN
RAISERROR('Answer is too similar to pre-existing answers for this item', 16, 1)
ROLLBACK TRANSACTION
RETURN
END
DROP TABLE temp_answers
END
GO
And this is my insert query:
INSERT INTO cc_Additional_Information_Answers (Additional_Information_ItemID, Description)
VALUES (103, 'a')
And the pre-existing record:
Thanks in advance, SQL community!
EDIT: this also does not behave as expected. . .
INSERT INTO cc_Additional_Information_Answers (Additional_Information_ItemID, Description)
VALUES (103, 'a')
Given this data
Your IF EXISTS will always evaluate to true because the inserted value is already inserted (although it can be rolled back) when the trigger runs (it's an "AFTER" trigger).
So you will want to inspect only those records that existed in the table before the insertion. I always use an outer join for this. Also: I would never create a table in a trigger. The following should work as expected:
CREATE TRIGGER TR_cc_Additional_Information_Answers_INS ON cc_Additional_Information_Answers
AFTER INSERT
AS
BEGIN
IF EXISTS(
SELECT 1 FROM cc_Additional_Information_Answers a
LEFT OUTER JOIN inserted i ON a.Additional_Information_AnswerID = i.Additional_Information_AnswerID
INNER JOIN inserted temp ON a.Additional_Information_ItemID = temp.Additional_Information_ItemID
WHERE a.Description LIKE '%' + temp.Description + '%'
AND i.Additional_Information_AnswerID IS NULL
)
BEGIN
RAISERROR('Answer is too similar to pre-existing answers for this item', 16, 1)
ROLLBACK TRANSACTION
RETURN
END
END
GO

SQL Stored Procedure with Input parameters with While loop

I have a code below that should insert records into the table but unfortunately this code foes not work in case multiple records are inserted or updated or deleted. How should I rewrite the code for procedure to loop through all the inserted / deleted records? And I do need to use that stored procedure with Input parameters (not just simple insert into ... select ... from ...)
IF EXISTS (SELECT * FROM MyDB.sys.triggers WHERE object_id = OBJECT_ID(N'[dbo].[MyTable_DEL_UPD_INS]'))
DROP TRIGGER [dbo].[MyTable_DEL_UPD_INS]
GO
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #PKId INT,
#Code VARCHAR(5),
#AuditType VARCHAR(10)
SET #Code = 'TEST'
IF EXISTS (SELECT * FROM deleted d)
AND NOT EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'DELETE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'UPDATE'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
IF NOT EXISTS (SELECT * FROM deleted d)
AND EXISTS (SELECT * FROM inserted i)
BEGIN
SELECT TOP 1
#PKId = d.[MyTable_PK],
#AuditType = 'INSERT'
FROM
deleted d WITH (NOLOCK)
IF #PKId IS NOT NULL
AND #Code IS NOT NULL
EXEC MyDB.[dbo].[SP_Audit] #PKId, #Code, #AuditType
END
END
GO
ALTER TABLE [MyDB].[dbo].[MyTable] ENABLE TRIGGER [MyTable_DEL_UPD_INS]
You should avoid using loops in triggers.
Triggers should be as quick to run as possible, since SQL Server will not return control to whatever statement that fired the trigger until the trigger is completed.
So instead of a loop, you should modify your SP_Audit procedure to work with multiple records instead of a single one.
usually, this is easily be done using a table valued parameter.
If you could post the SP_Audit as well, we could give you a complete solution.
Since you didn't post it, you can use these guidelines as a start:
First, you create a user defined table type:
CREATE TYPE dbo.Ids AS TABLE
(
Id int NOT NULL PRIMARY KEY
)
GO
Then, you create the procedure to use it:
CREATE PROCEDURE [dbo].[STP_Audit_MultipleRecords]
(
#IDs dbo.Ids readonly,
#Code CHAR(4),
#AuditType CHAR(6)
)
AS
-- Implementation here
GO
Last, your write your trigger like this:
CREATE TRIGGER [dbo].[MyTable_DEL_UPD_INS]
ON [MyDB].[dbo].[MyTable]
AFTER DELETE, UPDATE, INSERT
NOT FOR REPLICATION
AS
BEGIN
DECLARE #HasDeleted bit = 0,
#HasInserted bit = 0,
#AuditType CHAR(6),
#Code CHAR(4)
SET #Code = 'TEST'
DECLARE #IDs as dbo.Ids
IF EXISTS (SELECT * FROM deleted d)
SET #HasDeleted = 1
IF EXISTS (SELECT * FROM inserted i)
SET #HasInserted = 1
IF #HasDeleted = 1
BEGIN
IF #HasInserted = 1
BEGIN
SET #AuditType = 'UPDATE'
END
ELSE
BEGIN
SET #AuditType = 'DELETE'
END
END
ELSE
IF #HasInserted = 1
BEGIN
SET #AuditType = 'INSERT'
END
INSERT INTO #IDs (Id)
SELECT [MyTable_PK]
FROM inserted
UNION
SELECT [MyTable_PK]
FROM deleted
EXEC [dbo].[STP_Audit_MultipleRecords] #IDs, #Code, #AuditType
END
GO
Notes:
The #HasDeleted and #HasInserted variables are to allow you to only execute the EXISTS query once for every procedure.
Getting the primary key values from the deleted and inserted table is done using a single union query. Since union eliminates duplicate values, you can write this query just once. If you want to, you can write a different query for each audit type, but then you will have to repeat the same query 3 times (with different tables)
I've changed the data types of your #code and #AuditType variables to char, since they have a fixed length.

SQL Server : a strange thing happens with an Update inside a stored procedure

I have a stored procedure in SQL Server 2005 that the code inside is something like this :
select Unique_ID as ID
into tmp_table
from table1
Then, after some other statements, I do this update :
Update table1
set Flag = ‘Y’
Where Unique_ID in (select Unique_ID from tmp_table)
As you notice, I've purposely mistaken ID by Unique_ID (the column name in tmp_table) when I wanted to update.
Now the bizarre thing that happens, is that SQL Server doesn’t show an error when I execute the stored procedure.
It ignores the line where the error is :
Where Unique_ID in (select Unique_ID from tmp_table)
And runs just this :
Update table1 set
Flag = ‘Y’
Any ideas?
Thanks a lot.
UPDATE:
Here is the stored procedure code :
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [dbo].[MyProc]
AS
BEGIN
SET NOCOUNT ON
DECLARE #date VARCHAR(20)
SET #date = REPLACE(CONVERT(VARCHAR(8), GETUTCDATE(), 3), '/', '')
+ REPLACE(CONVERT(VARCHAR(8), GETUTCDATE(), 108), ':', '')
DECLARE #PATH AS VARCHAR(500)
SET #PATH = 'C:\MyPath\'
BEGIN TRY
EXEC master.dbo.xp_create_subdir #PATH
END TRY
BEGIN CATCH
PRINT 'FOLDER NOT CREATED'
END CATCH
IF EXISTS (SELECT name FROM sys.tables WHERE name = 'TMP_TABLE')
DROP TABLE TMP_TABLE
SELECT
UNIQUE_ID [Unique ID] ,
'col1' COL1,
'col2' COL2,
'col3' COL3,
'col4' COL4
INTO
TMP_TABLE
FROM
TABLE1
WHERE
Flag IN 'N'
--Try to generate file, If there was any problem, return from the SP => no update
BEGIN TRY
DECLARE #QUERY VARCHAR(MAX)
SET #QUERY = 'EXEC master..xp_cmdshell ''sqlcmd -E -s"," -W -h-1 -Q "SET NOCOUNT ON;SELECT * FROM dbo.TMP_TABLE" | findstr /V /C:"-" /B > '
+ #PaTH + '\FileName_' + #date + '.csv'',no_output;'
EXEC(#QUERY)
END TRY
BEGIN CATCH
PRINT 'Exception during file generation'
RETURN
END CATCH
PRINT 'File Generated'
UPDATE TABLE1
SET Flag = 'Y',
MODIFICATION_DATE = GETDATE()
WHERE
UNIQUE_ID IN (SELECT UNIQUE_ID FROM TMP_TABLE)
BEGIN TRY
DROP TABLE dbo.TMP_TABLE
PRINT 'TMP table dropped'
END TRY
BEGIN CATCH
PRINT 'TMP table not dropped'
END CATCH
END
GO
The problem is on this line:
WHERE UNIQUE_ID IN (SELECT **UNIQUE_ID** FROM TMP_TABLE)
I should replace UNIQUE_ID with [UNIQUE ID]
But SQL Server doesn't throw an error, it simply updates all records.
And here is the structure of table1 :
CREATE TABLE [dbo].[TABLE1]
(
UNIQUE_ID int NOT NULL IDENTITY(1, 1), --Primary key
Flag [varchar] (1) ,
MODIFICATION_DATE datetime,
)
I assume you had a typo in your query. Your tables look like that?
create table1
( Unique_ID int
, Flag char(1)
, ...
);
create tmp_table
( [Unique ID] int primary key
, ...
);
in that case your query is:
Update table1 t1 set
Flag = ‘Y’
Where t1.Unique_ID in (select t1.Unique_ID from tmp_table)
which is true except your id is null. you just compare the Unique_ID from table1 with itself.
Try to explicitly refer to the column to which you want to compare, by using the table name as well:
UPDATE TABLE1
SET Flag = 'Y',
MODIFICATION_DATE = GETDATE()
WHERE
UNIQUE_ID IN (SELECT TMP_TABLE.UNIQUE_ID FROM TMP_TABLE)
Confusion arises because there's a column named UNIQUE_ID on both tables, so by fully-qualifying it with the table name in the subselect, you can be 100% sure that you are referring to the right column.

SQL-Server Trigger on update for Audit

I can't find an easy/generic way to register to an audit table the columns changed on some tables.
I tried to do it using a Trigger on after update in this way:
First of all the Audit Table definition:
CREATE TABLE [Audit](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Date] [datetime] NOT NULL default GETDATE(),
[IdTypeAudit] [int] NOT NULL, --2 for Modify
[UserName] [varchar](50) NULL,
[TableName] [varchar](50) NOT NULL,
[ColumnName] [varchar](50) NULL,
[OldData] [varchar](50) NULL,
[NewData] [varchar](50) NULL )
Next a trigger on AFTER UPDATE in any table:
DECLARE
#sql varchar(8000),
#col int,
#colcount int
select #colcount = count(*) from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'MyTable'
set #col = 1
while(#col < #colcount )
begin
set #sql=
'INSERT INTO Audit
SELECT 2, UserNameLastModif, ''MyTable'', COL_NAME(Object_id(''MyTable''), '+ convert(varchar,#col) +'), Deleted.'
+ COL_NAME(Object_id('MyTable'), #col) + ', Inserted.' + COL_NAME(Object_id('MyTable'), #col) + '
FROM Inserted LEFT JOIN Deleted ON Inserted.[MyTableId] = Deleted.[MyTableId]
WHERE COALESCE(Deleted.' + COL_NAME(Object_id('MyTable'), #col) + ', '''') <> COALESCE(Inserted.' + COL_NAME(Object_id('MyTable'), #col) + ', '''')'
--UserNameLastModif is an optional column on MyTable
exec(#sql)
set #col = #col + 1
end
The problems
Inserted and Deleted lost the context when I use the exec function
Seems that colnumber it isn't always a correlative number, seems if you create a table with 20 columns and you delete one and create another, the last one have a number > #colcount
I was looking for a solution for all over the net but I couln't figure out
Any Idea?
Thanks!
This highlights a greater problem with structural choice. Try to write a set-based solution. Remove the loop and dynamic SQL and write a single statement that inserts the Audit rows. It is possible but to make it easier consider a different table layout, like keeping all columns on 1 row instead of splitting them.
In SQL 2000 use syscolumns. In SQL 2005+ use sys.columns. i.e.
SELECT column_id FROM sys.columns WHERE object_id = OBJECT_ID(DB_NAME()+'.dbo.Table');
#Santiago : If you still want to write it in dynamic SQL, you should prepare all of the statements first then execute them.
8000 characters may not be enough for all the statements. A good solution is to use a table to store them.
IF NOT OBJECT_ID('tempdb..#stmt') IS NULL
DROP TABLE #stmt;
CREATE TABLE #stmt (ID int NOT NULL IDENTITY(1,1), SQL varchar(8000) NOT NULL);
Then replace the line exec(#sql) with INSERT INTO #stmt (SQL) VALUES (#sql);
Then exec each row.
WHILE EXISTS (SELECT TOP 1 * FROM #stmt)
BEGIN
BEGIN TRANSACTION;
EXEC (SELECT TOP 1 SQL FROM #stmt ORDER BY ID);
DELETE FROM #stmt WHERE ID = (SELECT MIN(ID) FROM #stmt);
COMMIT TRANSACTION;
END
Remember to use sys.columns for the column loop (I shall assume you use SQL 2005/2008).
SET #col = 0;
WHILE EXISTS (SELECT TOP 1 * FROM sys.columns WHERE object_id = OBJECT_ID('MyTable') AND column_id > #col)
BEGIN
SELECT TOP 1 #col = column_id FROM sys.columns
WHERE object_id = OBJECT_ID('MyTable') AND column_id > #col ORDER BY column_id ASC;
SET #sql ....
INSERT INTO #stmt ....
END
Remove line 4 #colcount int and the proceeding comma. Remove Information schema select.
DO not ever use any kind of looping a trigger. Do not use dynamic SQl or call a stored proc or send an email.All of these things are exretemly inappropriate in a trigger.
If tyou want to use dynamic sql use it to create the script to create the trigger. And create an audit table for every table you want audited (we actually have two for every table) or you will have performance problems due to locking on the "one table to rule them all".

Resources