Archive

Archive for the ‘SQL Tips’ Category

Passing multiple/dynamic values to Stored Procedures & Functions | Part 3 – by using #table

September 9, 2012 3 comments

In my previous posts we saw how to pass multiple values to an SP by using CSV list and XML data, which are almost of same type. Here in this post we will see how we can achieve the same objective without passing values as parameters and by using temporary (temp, #) tables.

Here in the third part of this series the Stored Procedure will be created in such a way that it will use a Temporary Table, which does not exist in compile time. But at run time the temp-table should be created before running the SP. In this approach there is no need to pass any parameter with the SP, let’s see how:

-- Create Stored Procedure with no parameter, it will use the temp table created outside the SP:
CREATE PROCEDURE uspGetPersonDetailsTmpTbl
AS
BEGIN
	
	SELECT BusinessEntityID, Title, FirstName, MiddleName, LastName, ModifiedDate
	FROM [Person].[Person] PER
	WHERE EXISTS (SELECT Name FROM #tblPersons tmp WHERE tmp.Name  = PER.FirstName)
	ORDER BY FirstName, LastName

END
GO

-- Now, create a temp table, insert records with same set of values we used in previous 2 posts:
CREATE TABLE #tblPersons (Name NVARCHAR(100))

INSERT INTO #tblPersons
SELECT Names FROM (VALUES ('Charles'), ('Jade'), ('Jim'), ('Luke'), ('Ken') ) AS T(Names)

-- Now execute the SP, it will use the above records as input and give you required results:
EXEC uspGetPersonDetailsTmpTbl
-- Check the output, objective achieved 🙂

DROP TABLE #tblPersons
GO

-- Final Cleanup
DROP PROCEDURE uspGetPersonDetailsTmpTbl
GO

This approach is much better than the previous 2 approaches of using CSV or XML data.

The values will be entered in temp tables and then will be accessed inside the SP. There is no parsing involved as the records are directly read from temp table and used in SQL query just like normal queries. Also there is no limit of records if you compare with CSVs & XML data.

But, the catch here is, the temp table should exist before executing the SP, if for some reason it is not there the code will crash. But its rare and can be handled by some checks.

In my next and last [blog post] of this series we will see a new feature of SQL Server 2008 i.e. TVP, which can be used in such type of scenarios, check here.

Passing multiple/dynamic values to Stored Procedures & Functions | Part 2 – by passing XML

September 9, 2012 4 comments

In my previous post [Part 1] we saw how to pass multiple values to a parameter as a CSV string in an SP.
 

Here in the second part of this series we will use XML string that will contain the set of values and pass as an XML param variable to the SP. Then inside the SP we will parse this XML and use those values in our SQL Queries, just like we did in previous post with CSV string:

USE [AdventureWorks2012]
GO

-- Create an SP with XML type parameter:
CREATE PROCEDURE uspGetPersonDetailsXML (
	@persons XML
)
AS
BEGIN
	--DECLARE @persons XML
	--SET @persons = '<root><Name>Charles</Name><Name>Jade</Name><Name>Jim</Name><Name>Luke</Name><Name>Ken</Name></root>'

	SELECT T.C.value('.', 'NVARCHAR(100)') AS [Name]
	INTO #tblPersons
	FROM @persons.nodes('/root/Name') as T(C)
	
	SELECT BusinessEntityID, Title, FirstName, MiddleName, LastName, ModifiedDate
	FROM [Person].[Person] PER
	WHERE EXISTS (SELECT Name FROM #tblPersons tmp WHERE tmp.Name  = PER.FirstName)
	ORDER BY FirstName, LastName

	DROP TABLE #tblPersons
END
GO

-- Create XML string:
DECLARE @xml XML
SET @xml = '<root>
				<Name>Charles</Name>
				<Name>Jade</Name>
				<Name>Jim</Name>
				<Name>Luke</Name>
				<Name>Ken</Name>
			</root>'

-- Use the XML string as parameter which calling the SP:
EXEC uspGetPersonDetailsXML @xml
GO
-- Check the output, objective achieved 🙂

-- Final Cleanup
DROP PROCEDURE uspGetPersonDetailsXML
GO

 

This approach looks much cleaner and more stable to me than the previous one (CSV). XML is a de-facto standard to store and transmit data in a more structured way. Thus I prefer XML string over the CSV string on these type of cases.
 

The CSV approach is no different than this one, it internally converts the CSV string to an XML then parse it. Thus, like CSV if the XML string becomes lengthy it will also take time to parse the whole XML and then use the values in SQL queries in the SP.
 

In next [blog post] we will see how we can perform the same operation by using temporary (temp, #) tables.

Passing multiple/dynamic values to Stored Procedures & Functions | Part 1 – by passing CSV

September 9, 2012 4 comments

Here in the first Part of this series we will use a CSV string that will contain comma separated list of values and pass as a VARCHAR datatype param variable to the SP. Then inside the SP we will parse this VARCHAR CSV string and use those values in our SQL Queries:

-- As always I will use the AdventureWorks database 🙂
USE [AdventureWorks2012]
GO

-- Create an SP with NVARCHAR(MAX) parameter:
CREATE PROCEDURE uspGetPersonDetailsCSV (
	@persons NVARCHAR(MAX)
)
AS
BEGIN
	--DECLARE @persons NVARCHAR(MAX)
	--SET @persons = 'Charles,Jade,Jim,Luke,Ken'

	SELECT T.C.value('.', 'NVARCHAR(100)') AS [Name]
	INTO #tblPersons
	FROM (SELECT CAST ('<Name>' + REPLACE(@persons, ',', '</Name><Name>') + '</Name>' AS XML) AS [Names]) AS A
	CROSS APPLY Names.nodes('/Name') as T(C)
	
	SELECT BusinessEntityID, Title, FirstName, MiddleName, LastName, ModifiedDate
	FROM [Person].[Person] PER
	WHERE EXISTS (SELECT Name FROM #tblPersons tmp WHERE tmp.Name  = PER.FirstName)
	ORDER BY FirstName, LastName

	DROP TABLE #tblPersons
END
GO

-- No execute this SP by passing a list of values comma separated as a single string:
EXEC uspGetPersonDetailsCSV 'Charles,Jade,Jim,Luke,Ken'
GO
-- Check the output, objective achieved 🙂

-- Final Cleanup
DROP PROCEDURE uspGetPersonDetailsCSV
GO

 

This is the most simplest and common method to pass multiple values to an SP, but not ideal. If there are multiple values with count ranging to thousands, then storing them in VARCHAR will not be possible and parsing them would be a big trade off.
 

So, to perform this operation we have another method by using XML String. Check this in my next [blog post].

Search a String in Stored Procedure, View, Function or other database object definitions

August 1, 2012 1 comment

Looks like a simple topic and some might wonder why another post as this information is already available in other forums and blogs. But here I’ll go ahead with the topic and talk about an issue that not many people are aware of.

There are times when you want to search a particular string or keyword in various database objects like Stored Procedures, Views, Functions, etc. What you do in that case?
#1. Go to object explorer, open up each object and search the string by scripting it out.
– OR –
#2. Query the system catalog tables or metadata views.

Option #1 is time consuming, slow and very cumbersome.
Option #2 is quick and easy in retrieving the information.

So, we will see in what all ways we can get the required information from our database & which is the best way.

As I said at the top of this post if you search online you will see lot of solutions, but all those solution have some minor hidden issues which we will see here.

We will check below how we can search the “[HumanResources].[Employee]” table getting used in what all objects. (Here we are not checking the dependency on objects, but just checking any text which can be an object, SQL reserved keyword or user specified string to build a SQL query or a T-SQL script.)

–> 1. The first way comes in people mind is to search with INFORMATION_SCHEMA.ROUTINES View:

USE [AdventureWorks2012]
GO

select ROUTINE_DEFINITION, * 
from INFORMATION_SCHEMA.ROUTINES 
where ROUTINE_DEFINITION like N'%[[]HumanResources].[[]Employee]%'
6 Records returned with following Stored Procedure names:

ROUTINE_NAME
ufnGetContactInformation
uspGetEmployeeManagers
uspGetManagerEmployees
uspUpdateEmployeeHireInfo
uspUpdateEmployeeLogin
uspUpdateEmployeePersonalInfo

–> 2. The second way is to search in sys.syscomments View:

select
	CASE 
		WHEN OBJECTPROPERTY(id, 'IsProcedure') = 1      THEN 'Stored Procedure'
		WHEN OBJECTPROPERTY(id, 'IsView') = 1			THEN 'View'
		WHEN OBJECTPROPERTY(id, 'IsScalarFunction') = 1 THEN 'Scalar Function'
		WHEN OBJECTPROPERTY(id, 'IsTableFunction') = 1  THEN 'Table Valued Function'
		WHEN OBJECTPROPERTY(id, 'IsInlineFunction') = 1 THEN 'Inline Function'
		WHEN OBJECTPROPERTY(id, 'IsTrigger') = 1        THEN 'Trigger'
	END AS ObjType, 
	* 
from sys.syscomments 
where text like N'%[[]HumanResources].[[]Employee]%'

This query returns 12 reocrds, with 1 Table Valued Function, 5 SPs, 1 Trigger & 5 Views.

–> 3. The third way is to search in sys.sql_modules View:

select
	CASE 
		WHEN OBJECTPROPERTY(object_id, 'IsProcedure') = 1      THEN 'Stored Procedure'
		WHEN OBJECTPROPERTY(object_id, 'IsView') = 1			THEN 'View'
		WHEN OBJECTPROPERTY(object_id, 'IsScalarFunction') = 1 THEN 'Scalar Function'
		WHEN OBJECTPROPERTY(object_id, 'IsTableFunction') = 1  THEN 'Table Valued Function'
		WHEN OBJECTPROPERTY(object_id, 'IsInlineFunction') = 1 THEN 'Inline Function'
		WHEN OBJECTPROPERTY(object_id, 'IsTrigger') = 1        THEN 'Trigger'
	END AS ObjType, 
	* 
from sys.sql_modules 
where definition like N'%[[]HumanResources].[[]Employee]%'

Same as above, returns 12 reocrds.

No issue till now, great!!!

Now, let’s check this scenario: I want to get all Database Objects containing the “[BikeFrame]” column name.

–> 1. When I choose to search with INFORMATION_SCHEMA.ROUTINES view it does not give any result:

select ROUTINE_DEFINITION, * 
from INFORMATION_SCHEMA.ROUTINES 
where ROUTINE_DEFINITION like N'%[[]BikeFrame]%'

Because this View stores only first 4000 characters in ROUTINE_DEFINITION column of NVARCHAR datatype. So the query gets truncated before the column name occurs.

–> 2. When I go with sys.syscomments view it also gives me nothing:

select * 
from sys.syscomments 
where text like N'%[[]BikeFrame]%'

Here this View stores the query-text in “text” column of NVARCHAR type with size of 4000 characters. The query also gets truncated here but it stores the query in chunks thus the rest part goes to the next rows.

So it should return the row with matching chunk from the matching row, but it does not, why? Because in this typical case the column name is getting splitted in different rows. Check by running this query:

select id, colid, ctext, text
from sys.syscomments 
where id = 2059154381
Output:-
id		colid	ctext			text
2059154381	1	0x0D000A00430052004...	CREATE VIEW [Production].[vProductModelCatalog...
2059154381	2	0x65004600720061006...	eFrame] ,[CatalogDescription].value(N'declare nam...

Here, you can see the second half text “eFrame]” of the column name is placed at beginning of the 2nd row.
And if you expand the “text” column you will see the first half text “[Bik” of the column name at the end of 1st row.

–> 3. When I go with sys.sql_modules view it gives me expected result:

select * 
from sys.sql_modules 
where definition like N'%[[]BikeFrame]%'

Because this View stores the query text in “definition” column of NVARHCAR(MAX) type with length of 2GB.

So, its better to use sys.sql_modules view to do all metadata based searches instead of INFORMATION_SCHEMA.ROUTINES & sys.syscomments views.

Categories: SQL Tips Tags:

“Identity Gap” Issue with the new SEQUENCE feature in SQL Server 2012 … and its workaround

July 27, 2012 3 comments

In my previous post I discussed about an issue with IDENTITY property. Here today while working on a similar new feature “SEQUENCE”, I found a similar kind of behavior with it.

Here also when you restart SQL Server or restart the service the last sequence number jumps to a higher random number.

Here is a simple code to reproduce this issue:

-- CREATE a simple Sequence
CREATE SEQUENCE CountBy1 
	START WITH 1 
	INCREMENT BY 1 
	MINVALUE 0 
	NO MAXVALUE ;
GO


-- CREATE a test table:
CREATE TABLE TEST_SEQ (ID INT, NAME VARCHAR(200));

-- INSERT some records:
INSERT INTO TEST_SEQ
SELECT NEXT VALUE FOR CountBy1, 'Manoj Pandey'

INSERT INTO TEST_SEQ
SELECT NEXT VALUE FOR CountBy1, 'Gaurav Pandey'
GO

-- Check the INSERTed records before server restart:
SELECT * FROM TEST_SEQ
GO


-- RESTART SQL Server & INSERT a new record:
INSERT INTO TEST_SEQ
SELECT NEXT VALUE FOR CountBy1, 'Garvit Pandey'
GO

-- Check the INSERTed records after server restart:
SELECT * FROM TEST_SEQ
GO


--// Final cleanup
DROP TABLE TEST_SEQ
DROP SEQUENCE CountBy1

Finally I got the following output:

As you can see by running the above test before I restarted SQL Server the SEQUENCE value assigned to the last record was 2, but when I restarted the new SEQUENCE value generated is 51.

Reason: Actually while creating SEQUENCE object SQL Server engine caches the new SEQUENCE values to Increase performance. By default the cache size is 50, so it caches values upto 50 values, and when SQL Server restarts it starts after 50, that’s a bug.

Workaround: To avoid this situation you can put an “NO CACHE” option while declaring the SEQUENCE object, like:

-- CREATE a simple Sequence
CREATE SEQUENCE CountBy1 
	START WITH 1 
	INCREMENT BY 1 
	MINVALUE 0 
	NO MAXVALUE 
	NO CACHE ; -- here
GO

This will not cache the future values and you wont get this issue of jumping values and gaps.

To know more about SEQUENCES check my previous blog post, [link].