Wednesday, August 7, 2013

Passing Entire table as Parameters of Stored Procedure

Passing Entire table as Parameters of Stored Procedure


Introduction

In this article I am trying to demonstrate, how to pass a entire table as a parameters of stored procedure. But this facility is from MS SQL Server 2008 only. SQL Server 2008 introduces the ability to pass a table data type into stored procedures and functions.

Let's start with an example
Here I am taking a simple example to understand the process only. I have two table object and I want to copy all the data from first table to second one  by using a stored procedure

Step-1 [ Create the Base Table Objects ]

-- Source Table Objects
IF OBJECT_ID(N'dbo.Tbl_EMPLOYEE', N'U') IS NOT NULL
   BEGIN
      DROP TABLE [dbo].[Tbl_EMPLOYEE];
   END
GO

CREATE TABLE [dbo].[Tbl_EMPLOYEE]
       (EMPID   INT         NOT NULL IDENTITY PRIMARYKEY,
        EMPNAME VARCHAR(50) NOT NULL);
GO

-- Inserting Records in my Source Table
INSERT INTO [dbo].[Tbl_EMPLOYEE]
       (EMPNAME)
VALUES ('Joydeep Das'),
       ('Chandan Bannerjee');
      
-- Now the Destination Table Object                       
IF OBJECT_ID(N'dbo.Tbl_EMPLOYEE_BACKUP', N'U') IS NOTNULL
   BEGIN
      DROP TABLE [dbo].[Tbl_EMPLOYEE_BACKUP];
   END
GO

CREATE TABLE [dbo].[Tbl_EMPLOYEE_BACKUP]
       (EMPID   INT         NOT NULL,
        EMPNAME VARCHAR(50) NOT NULL);
GO

My main target is to copy data from Tbl_EMPLOYEE to Tbl_EMPLOYEE_BACKUP

Step-2 [ Create Table Type ]
Now we are setting up the use of table valued parameters is creating a specific table type; this is necessary so that the structure of the table is defined in the database engine.
This allows us to define the type of table and reuse it as needed in our stored procedure code.

IF EXISTS(SELECT * FROM sys.table_types WHERE namelike 'type_Employee')
   BEGIN
      DROP TYPE type_Employee;
   END
GO

CREATE TYPE type_Employee AS TABLE
    (
       EMPID   INT         NOT NULL,
       EMPNAME VARCHAR(50) NOT NULL
    );  


Step-3 [ Create Stored Procedure ]

IF OBJECT_ID(N'dbo.sproc_EMPDTLS', N'P') IS NOT NULL
   BEGIN
      DROP PROCEDURE [dbo].[sproc_EMPDTLS];
   END
GO
CREATE PROCEDURE [dbo].[sproc_EMPDTLS]
       (
          @p_TableVariable type_employee READONLY
       )
AS
BEGIN
    INSERT INTO [dbo].[Tbl_EMPLOYEE_BACKUP]
           (EMPID, EMPNAME)
    SELECT EMPID, EMPNAME
    FROM   @p_TableVariable;
END            

Step-4 [ Execute Stored Procedure ]
First, we must declare a variable of type type_Employee. We don't need to define the structure of the table again because it was defined when we created the type.

BEGIN
   DECLARE @DataTable AS type_employee;
  
   INSERT INTO @DataTable
   SELECT * FROM [dbo].[Tbl_EMPLOYEE];
  
   EXEC [dbo].[sproc_EMPDTLS]
        @p_TableVariable =  @DataTable;
END

Step-5 [ Observe the Output ]

SELECT * FROM [dbo].[Tbl_EMPLOYEE];
GO
SELECT * FROM [dbo].[Tbl_EMPLOYEE_BACKUP];
GO

Is DML Trigger Fire When NO ROWS is Effected in a TABLE

Is DML Trigger Fire When NO ROWS is Effected in a TABLE

Introduction 


One of my friends has a question that is the Trigger fire if there is no row affect in the table.

My Answer is

YES it is

What MS says about it?


From Microsoft Developer Network

ms189799.note(en-US,SQL.90).gifNote:
These triggers fire when any valid event is fired, regardless of whether or not any table rows are affected.This is by design.


So it says that the Trigger fires with any valid event are fires. It not depends on whether the event affects any row or not.

To Demonstrate This

Here we take a simple Example

IF OBJECT_ID(N'dbo.Tbl_EXAMPLE', N'U') IS NOT NULL
   BEGIN
      DROP TABLE dbo.Tbl_EXAMPLE;
   END
GO  
CREATE TABLE dbo.Tbl_EXAMPLE
   (ID    INT         NOT NULL IDENTITY PRIMARY KEY,
    NAME  VARCHAR(50) NOT NULL);
GO

INSERT INTO dbo.Tbl_EXAMPLE
       (NAME)
VALUES ('Joydeep Das'),('Sangram Jit'),('Madhurima Das'),('Chandan Bhattacarys');                
GO

IF OBJECT_ID(N'dbo.trg_DELETEREC', N'TR') IS NOT NULL
   BEGIN
      DROP TRIGGER dbo.trg_DELETEREC;
   END
GO
CREATE TRIGGER dbo.trg_DELETEREC
ON dbo.Tbl_EXAMPLE
AFTER INSERT, DELETE
AS
BEGIN
   PRINT 'Trigger Effected';
END
     
DELETE  Tbl_EXAMPLE WHERE ID=10 

The above DELETE statement not affects any rows in the table. But the Trigger is fired.

How to Solve it

To solve it we have to change the definition of Trigger little more.

IF OBJECT_ID(N'dbo.trg_DELETEREC', N'TR') IS NOT NULL
   BEGIN
      DROP TRIGGER dbo.trg_DELETEREC;
   END
GO
CREATE TRIGGER dbo.trg_DELETEREC
ON dbo.Tbl_EXAMPLE
AFTER INSERT, DELETE
AS
BEGIN
   IF EXISTS(SELECT * FROM inserted)
      BEGIN
              PRINT 'Trigger Effected';
       END        
   IF EXISTS(SELECT * FROM deleted)
      BEGIN
              PRINT 'Trigger Effected';
       END                
END
     

SQL Server 2005

EventData() function

Introduction

From MS SQL Server 2005, we can get data regarding the event that initiated the DDL trigger by accessing the EventData()function.

How it’s Works

The main tasks of the function named EventData() is to return the information about the Server or Database Event and stored in a variable with data type XML. We just capture the data return by EventData() function and store it to a Table object for showing report.

If needed we can store the data to directly a XML data type holder columns of a table or process the data and store it to the different columns according to needs. As the return type of the function named EventData() is in  XML format , we can apply  XQuery against it.

To understand it properly let’s take an example

Example

IF OBJECT_ID(N'dbo.tbl_EVENTTRACKER', N'U') IS NOT NULL
   BEGIN
      DROP TABLE [dbo].[tbl_EVENTTRACKER];
   END
GO
CREATE TABLE [dbo].[tbl_EVENTTRACKER]
       (
         EVENTTYPE    nVARCHAR(50)   NULL,
         SPID         nVARCHAR(50)   NULL,
         LOGINNAME    nVARCHAR(50)   NULL,
         TSQLCODE     nVARCHAR(300)  NULL,
         SERVERNAME   nVARCHAR(50)   NULL,
         DBNAME       nVARCHAR(50)   NULL
       );
      
GO

Trigger Definition

IF OBJECT_ID(N'dbo.trg_MYDDLEVENT', N'TR') IS NOT NULL
   BEGIN
      DROP TABLE [dbo].[trg_MYDDLEVENT];
   END
GO  
CREATE TRIGGER [dbo].[trg_MYDDLEVENT]  
ON DATABASE  
FOR ALTER_TABLE 
AS 
DECLARE @Data XML; 
SET @Data = EventData();

INSERT INTO [dbo].[tbl_EVENTTRACKER]
(EVENTTYPE, SPID, LOGINNAME, TSQLCODE, SERVERNAME, DBNAME)
SELECT  
@Data.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(50)')    ASEVENTTYPE,
@Data.value('(/EVENT_INSTANCE/SPID)[1]', 'nvarchar(50)')         ASSPID,
@Data.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(50)')    ASLOGINNAME,
@Data.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'nvarchar(300)') ASTSQLCODE,
@Data.value('(/EVENT_INSTANCE/ServerName)[1]', 'nvarchar(50)')   ASSERVERNAME,
@Data.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'nvarchar(50)') ASDBNAME 
GO         
        
Now make a DDL statement

ALTER TABLE [dbo].[tbl_EVENTTRACKER]  
ADD newColumn SMALLDATETIME NULL

And Observe the Event Log by

SELECT * FROM [dbo].[tbl_EVENTTRACKER];

Limitation of UDF

Limitation of UDF

Introduction

All the developers are very well-known about the UDF in MS SQL server. The UDF is first introducing at Microsoft SQL Server 2000.
Here in this article we are trying to discuss about some limitation of UDF.

No Side-Effects
By definition, a UDF is supposed to have no side-effects on the database. 
In MS SQL Server user defined functions (UDFs) you cannot take any actions (update, delete, insert) that modify data on any object outside the scope of the UDF.  A table variable inside the UDF is, of course, allowed. 

Let’s take an Example:

IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_EMPID   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT = 1;
  
   UPDATE dbo.tbl_Employee
     SET EMPNAME='Joydeep Das'
   WHERE EMPID=@p_EMPID;
  
   RETURN @intRETURN; 
END  

When we compile this we get an error output.

Msg 443, Level 16, State 15, Procedure fn_MYUDF, Line 10
Invalid use of a side-effecting operator 'UPDATE' within a function.

Can NOT use Non-deterministic Built-in function
Within UDF we cannot use any non-deterministic built in function. Non deterministic functions may return different results each time they are called with a specific set of input values even if the database state that they access remains the same. We cannot influence the determinism of any built-in function. Each built-in function is deterministic or non deterministic based on how the function is implemented by SQL Server.

SQL Server 2005 has allowed some of the non-deterministic functions that were not allowed in 2000. In 2005 we can now use CURRENT_TIMESTAMP, @@MAX_CONNECTIONS, GET_TRANSMISSION_STATUS, @@PACK_RECEIVED, GETDATE, @@PACK_SENT,
GETUTCDATE, @@PACKET_ERRORS, @@CONNECTIONS, @@TIMETICKS, @@CPU_BUSY, @@TOTAL_ERRORS, @@DBTS, @@TOTAL_READ, @@IDLE,
@@TOTAL_WRITE and,  @@IO_BUSY

Limitation of Parameters
UDF can have up to 1023 input parameters; Stored Procedure can have up to 21000 input parameters. This is a relatively minor limitation because most routines require a much smaller number of parameters.

Limitation of CURSOR Declaration

IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_OPMODE   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT;
  
   DECLARE cur_test SCROLL CURSOR
        FOR SELECT o.id FROM sysobjects as o;
       
   RETURN @intRETURN; 
END 

Msg 1049, Level 15, State 1, Procedure fn_MYUDF, Line 0
Mixing old and new syntax to specify cursor options is not allowed.

In UDF we need ANSI SQL style to declare cursor.

In TSQL, user-defined functions cannot modify state of the database or persist information between executions. This is one of the reasons to disallow insert/update/delete on permanent tables or dynamic SQL.

This also means that UDFs will disallow cursors that are global in nature which happens to be the case with those declared using the ANSI SQL syntax. Note that there is no way to specify cursor as local using the ANSI SQL syntax. So we default all cursors declared within UDFs to be local implicitly. This creates a conflict in case of the DECLARE CURSOR statement using ANSI SQL syntax since that doesn't support the local option. Hence the error message about mixing old and new syntax for cursor options. The workaround is to use TSQL syntax for the DECLARE CURSOR statement to specify LOCAL or leave it out in which case we default to LOCAL anyway.



IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_OPMODE   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT;
  
   DECLARE cur_test CURSOR SCROLL
        FOR SELECT o.id FROM sysobjects as o;
       
   RETURN @intRETURN; 
END 

OR

IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_OPMODE   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT;
  
   DECLARE cur_test  CURSOR LOCAL SCROLL
        FOR SELECT o.id FROM sysobjects as o;
       
   RETURN @intRETURN; 
END   

Return Only One Result Set
UDFs can return only one row set to the user, whereas stored procedures can return multiple row sets.

UDF Can NOT Call Regular Stored Procedure
We can't call regular stored Procedure from functions - only other functions or some extended stored procedures.

-- Procedure
IF OBJECT_ID(N'dbo.proc_MYPROC', N'P')IS NOT NULL
   BEGIN
      DROP PROCEDURE [dbo].[proc_MYPROC];
   END
GO
CREATE PROCEDURE [dbo].[proc_MYPROC]
     (
        @p_EMPID   INT = 0
     )
 AS
 BEGIN
    SELECT *
    FROM   tbl_Employee
    WHERE  EMPID = @p_EMPID;
 END      
GO

--Function
IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_ID   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT;
  
   EXEC [dbo].[proc_MYPROC]
      @p_EMPID = @p_ID
       
   RETURN @intRETURN; 
END   

--Function Execution
SELECT  dbo.fn_MYUDF (1)

Msg 557, Level 16, State 2, Line 1
Only functions and some extended stored procedures
can be executed from within a function.

Can NOT Execute Dynamic SQL
UDFs also cannot execute dynamically constructed SQL statements. If we need to construct a statement dynamically based on the parameter values, we must resort to using stored procedures.

Can NOT Support SET Operation
SET options can affect not only the performance of the queries, but their output as well.
SET options cannot be altered within UDFs.

IF OBJECT_ID(N'dbo.fn_MYUDF', N'FN')IS NOT NULL
   BEGIN
      DROP FUNCTION [dbo].[fn_MYUDF];
   END
GO
CREATE FUNCTION [dbo].[fn_MYUDF]
       (
         @p_ID   INT
       ) 
RETURNS INT
AS
BEGIN
   DECLARE @intRETURN INT;
  
   SET NOCOUNT ON;
       
   RETURN @intRETURN; 
END 


Msg 443, Level 16, State 15, Procedure fn_MYUDF, Line 10
Invalid use of a side-effecting operator 'SET OPTION ON' within a function.

Error Checking Limitation
RAISERROR, PRINT statement is not allowed by UDF. Even @@ERROR global variable is not supported by UDF. If you encounter an error, UDF execution simply stops, and the calling routine fails. We are allowed to write a message to the Windows error log with xp_logevent if you have permission to use this extended procedure.




Type of Function



When I am thinking about function, I find several article related to UDF factions in SQL server. There are so many type noted in different article that hardly to remember them.
 In my point of view the, UDF function is broadly categorized at 2 types
1.    Scalar Function
2.    Table Function
I provide you only the simple definition of it, not any complicated boring concepts.
Scalar Function:
It returns only a single value.  This value can contains any SQL server data types. The function can accepts multiple parameters with different data types but only return a single value.
This simple example takes 2 decimal values and returns the summation of it. It returns type is also decimal value.
CREATE FUNCTION myFunction
(
            -- Add the parameters for the function here
            @p_Param1 decimal,  ß Input Parameters
            @p_Param2 decimal
)
RETURNS decimal ß Returen Type
AS
BEGIN
            -- Declare the return variable here
            DECLARE @p_ReturnVal decimal

            -- Add the T-SQL statements to compute the return value here
            SELECT @p_ReturnVal = @p_Param1 + @p_Param2

            -- Return the result of the function
            RETURN @p_ReturnVal ß Return Value

END
GO

SELECT dbo.myFunction(2,2) Result

GO
Table Function
It returns a table data type and it can be used like a view or table
CREATE FUNCTION myTableFunc
(          
            @p_CustName Varchar(50ß Input Parameters
)
RETURNS TABLE ß Returen Type
AS
RETURN
(
            -- Add the SELECT statement with parameter references here
            SELECT *
            FROM   ledger_descr WHERE descr LIKE @p_CustName+'%'
)
GO

SELECT *
FROM   dbo.myTableFunc('Raja')

GO