SQL Server: How to get top most parent id from the nth level in parent-child hierarchy

In the last week one of my team member who worked on ICD10 project asked me one question where he wanted to find the top most icd-code id from any child level. So in today’s post we’ll learn about how to get the top most parent id or let say root id from the leaf level or from the nth level in parent-child hierarchy. Hmm sounds interesting.

SQL Server 2005/2008 comes with a major tsql enhancement which really ease the life of developer. CTE(Common Table Expression) is one of them. CTE is a temporary result set which you can reference it in SELECT, UPDATE, INSERT or DELETE statement. There are two types of CTEs in SQL Server – Recursive and non-recursive. To achieve our expected result we need to use here recursive CTE. By not going into much detail, let me give you the short description about it. A recursive CTE is one that reference itself or call itself within that CTE and continues to execute until the query return the whole result. Let’s dive into it and see it in action.

For demo purpose, i have defined the table variable here with the required data in it.


DECLARE @Tab_ICDCodes TABLE (
CodeID [int] NOT NULL,
CodeName [varchar](250) NULL,
Code [varchar](250) NULL,
ParentID [int] NULL
)

INSERT INTO @Tab_ICDCodes(CodeID, CodeName, Code, ParentID)
VALUES  (1, 'Abdominal Pain', 'AP', 0),
	(2, 'Epigastric', 'EP', 1),
	(3, 'Ruptured', '', 2),
	(4, 'Acute abdomen', '', 2),
	(5, 'Aortic aneurysm', '', 3),
	(6, 'Nail', 'N', 0),
	(7, 'Nail Pain', 'N', 6)

Now to answer the original question here is the query which return top most parent / root – code id and code name. In below query we’ll pass child id as variable and query will return its top most parentid.


DECLARE @CodeId INT = 5

;WITH cteGetRootID
As
(

	SELECT 
		CodeID, CodeName, ParentID, 1 AS CodePosition
	FROM 
		@Tab_ICDCodes WHERE CodeID = @CodeId
	UNION All
	SELECT 
		ic.CodeID, ic.CodeName, ic.ParentID, CodePosition + 1
	FROM @Tab_ICDCodes ic
	INNER JOIN cteGetRootID cte ON ic.CodeID = cte.ParentID
)
SELECT TOP 1 CodeID, CodeName FROM cteGetRootID
ORDER BY CodePosition DESC

So, that’s it guys. Hope you like this post.

Tip of the day: SQL Server – In Stored Procedure if passing parameter is not null then update column value else keep old column value in Update query.

As a developer, we always try to write better code and try to reduce the possible bugs in the application. But at the same time its also important that how you write the code, how much readable it is. Well, we all are human beings and its almost impossible to write bug free code irrespective of your work experience. That’s where the process of code review comes into the picture. Importance of code review is not limited to code refactoring or bug fixing,  at-least for me it is also a solid mechanism of knowledge sharing / gathering.

So as a part of it, I was reviewing the code of stored procedure and found some unwanted if conditions. In that SP there were 3 optional parameters and those if conditions was something like this – check if parameter passed is not null then use it in update statement. And same condition for other two columns. Here is the sample code of that stored procedure.


CREATE PROCEDURE [dbo].[App_UpdateAccountInfo]
 @AcntId INT
,@CompanyID INT
,@FrnID INT = NULL
,@AccountNickName VARCHAR(50)
,@SystemName VARCHAR(30) = NULL

AS
BEGIN

SET NOCOUNT ON;

	UPDATE [dbo].[tblAccount]
	SET
		CompanyID = @CompanyID
		,AccountNickName = @AccountNickName
		,LastUpdateOn = GETDATE()
	 WHERE AccontID = @AcntId

	 -- Check if @SystemName is not null
	 IF @SystemName IS NOT NULL
	 BEGIN

		UPDATE [dbo].[tblAccount]
		 SET
			SystemName = @SystemName
		 WHERE AccontID = @AcntId

	 END

	 -- Check if @FrnID is not null
	 IF @FrnID IS NOT NULL
	 BEGIN

		UPDATE [dbo].[tblAccount]
		SET
			FrnID = @FrnID
		 WHERE AccontID = @AcntId

	 END

END

Now what if there were more than 5 optional parameters in above SP! As a developer do you think to write 5 or more if conditions and make SP more verbose? So what should be the proper way?

Well, Let me give you one hint. What if you update column with itself in update statement. Hmm no need to think much, here is the modified code of above SP.


CREATE PROCEDURE [dbo].[App_UpdateAccountInfo]
 @AcntId INT
,@CompanyID INT
,@FrnID INT = NULL
,@AccountNickName VARCHAR(50)
,@SystemName VARCHAR(30) = NULL

AS
BEGIN

	SET NOCOUNT ON;

	UPDATE [dbo].[tblAccount]
	SET
		CompanyID = @CompanyID
		,AccountNickName = @AccountNickName
		,SystemName = ISNULL(@SystemName, SystemName)
		,FrnID = ISNULL(@FrnID, FrnID)
		,LastUpdateOn = GETDATE()
	 WHERE AccontID = @AcntId

END

Well, SQL Server is smart enough to not write the same value on the disk which results in minimal log activity. However this is not true in some cases. There are several factors which is nicely described in Paul White’s article and this stackexchange link.

That’s it. Have you ever faced this type of scenario? write it down in the comment section. Hope you like this post.

SQL Server Error: Cannot schema bind function because table name is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.

Today for one of my application, i was trying to create a schema bound function for performance optimization. Before we go into the detail you should check this link to know that how the query optimizer can use it to make better performance decisions. Alright, now the function i wanted to create is used to check that particular role id have access to menu link or not. Simple right! Here is the code of my function.

CREATE FUNCTION [dbo].[udf_IsAuthorisedForMenuLink]
(
	@RoleID TINYINT,
	@MenuID INT
)
RETURNS BIT
WITH SCHEMABINDING
AS
BEGIN
	
	DECLARE @IsAuthorised BIT = 0

	IF EXISTS(SELECT 1 FROM tblRoleMenu WHERE RoleId = @RoleID AND MenuID = @MenuID)
	BEGIN
		SET @IsAuthorised = 1
	END
	
	RETURN @IsAuthorised
	
END

But when i try to create this function, i get the following error.

Msg 4512, Level 16, State 3, Procedure udf_IsAuthorisedForMenuLink, Line 23 
Cannot schema bind function ‘dbo.udf_IsAuthorisedForMenuLink’ because name ‘tblRoleMenu’ is invalid for schema binding. Names must be in two-part format and an object cannot reference itself.

So why i got this error? What is the resolution of this error? Well, the resolution itself is in the error message. As it said – “Names must be in two-part format” i.e. [Schema].[ObjectName] – in our case its dbo.tblRoleMenu. Actually i forgot to add schema in the select statement in my above function code. Once i add it and press “F5” and boom, function created successfully without any error.

Did you guys ever face this type of issue in your application? Write in the comment. Hope you like this post.

Alter table definition not reflected in SQL Server View

Few days ago, i got the chance to work with one of our legacy application. There was some changes in business requirement and i need to drop few columns from the table and so on and that table was used in one sql server view. After done my changes in table when i test my view, surprisingly it gives me an error! whooa!

Msg 4502, Level 16, State 1, Line 7
View or function ‘view_name’ has more column names specified than columns defined.

How is it possible as i have used “Select *” in my view. It should automatically reflect the changes in my view. Well, SQL server will not work in that way. After referring MSDN, i knew that metadata for my view is not automatically updated when i alter any dependent tables. So in today’s post we’ll see how we can resolve this type of error in SQL Server. Alright, let’s create this scenario.


CREATE TABLE [dbo].[EmployeeMaster](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[FirstName] [varchar](40) NOT NULL,
	[LastName] [varchar](40) NOT NULL,
	[JoiningDate] [date] NULL,
	[BloodGroup] [varchar](5) NULL,
	[PANNo] [varchar](20) NULL
 )
GO

INSERT INTO EmployeeMaster(FirstName,LastName,JoiningDate,BloodGroup)
	VALUES ('Azim', 'Premji', '2015-04-27', 'B+'),
			('Narayana', 'Moorthy', '2015-04-27', 'A+')
GO

Now let’s create the view.


CREATE VIEW view_Employee
AS
    SELECT * FROM  dbo.EmployeeMaster 

If you querying the view, everything works fine as expected. Now let us drop the “PANNo” column from the EmployeeMaster table.


ALTER TABLE EmployeeMaster
	DROP COLUMN PANNo

And let’s querying our view and see what happen.

ErrorInView

We get the error! Now to fix this error one of the way is alter our view or use the sp_refreshview stored procedure. So let’s see how we can fix the error using sp_refreshview SP.


EXEC SP_REFRESHVIEW view_Employee

Now again querying our view and as expected the error is gone now. Same way let say if you have added 2 new columns in your table then this time you won’t get any error when you querying your view but those 2 new columns not reflected in your view. So again to fix this issue you need to use “sp_refreshview” as shown above.

That’s it guys. Hope you like and enjoyed this post.

Execute a predefined tSql query stored in a column of table

Today’s post is inspired from the question asked here in the stackoverflow site. So the question is how to execute a predefined tsql query which is stored in a column of table and then display the result in one report. You can find the answer here i gave him. You might find it yourself in this type of situation in your application where you need to display the consolidated report over your other report in which you need to display the name of the report and its associated count or some other data.

Allright, let’s create this situation. For explanation i copied the data from that question. so here is the required table with some required records in it.


CREATE TABLE #tmpConsReport
(
	ReportName [varchar](100), 
	ReportQuery [varchar](2000)
);

INSERT INTO #tmpConsReport (ReportName, ReportQuery) 
	VALUES ('Sales Daily Report Count', 'Select COUNT(*) FROM Sales WHERE SaleDate = CAST(GETDATE() AS DATE)'),
			    ('Sales Weekly Report Count', 'Select COUNT(*) FROM Sales WHERE SaleDate >= CAST(GETDATE() -6 AS DATE) '),
				('HR Dept. Count', 'Select COUNT(*) FROM Users DepartmentID = 1'),
				('Todays New Joiny', 'Select TOP 1 firstname FROM Users ORDER BY EmpID DESC');

Now to execute the query which is stored in column of table we need to build dynamic query which looks something like this –


DECLARE @query VARCHAR(MAX) = ''

SELECT @query += 'SELECT ' + QUOTENAME(ReportName, '''') 
							+ ' AS ReportName, CAST((' + ReportQuery + ') AS VARCHAR) AS Result UNION ALL ' 
From #tmpConsReport

SET @query = LEFT(@query, LEN(@query) - 10)

PRINT(@query)
EXEC(@query)

If you don’t get any idea after seeing this query, no problem. Let’s print that dynamic query and it looks like this –


SELECT 'Sales Daily Report Count' AS ReportName, 
			CAST((Select COUNT(*) FROM Sales WHERE SaleDate = CAST(GETDATE() AS DATE)) AS VARCHAR) AS Result 

UNION ALL 

SELECT 'Sales Weekly Report Count' AS ReportName, 
			CAST((Select COUNT(*) FROM Sales WHERE SaleDate >= CAST(GETDATE() -6 AS DATE) ) AS VARCHAR) AS Result 

UNION ALL 

SELECT 'HR Dept. Count' AS ReportName, 
			CAST((Select COUNT(*) FROM Users DepartmentID = 1) AS VARCHAR) AS Result 
			
UNION ALL 

SELECT 'Todays New Joiny' AS ReportName, 
         CAST((Select TOP 1 firstname FROM Users ORDER BY EmpID DESC) AS VARCHAR) AS Result

Hmmm Now looks better. But its easy right!!! Yes, using UNION ALL i have built the dynamic query and then execute it.

That’s it guys. Hope you like this post and learned something new today. Have a nice day.

NOWAIT hint in SQL Server

Today, I was reading about Table Hint in MSDN for other reason and came across this “NOWAIT” hint. In today’s post we will learn about NOWAIT hint. Well, SQL server uses locks to ensure that the data in your table can be accessed, without risk of corruption or dirty reads. Let say when you are inserting or updating records within transaction then your table are locked and other transaction have to wait to read or change the records.

Sometime we are in situation that when our application request for data and if our request is locked by SQL server then rather than wait for some interval, its better to move out. So in that type of situations “NOWAIT” hint comes into the picture. It means when you apply this hint and requesting for select records from the table, the command fails immediately if current request is blocked and then reporting an error.

Let’s see this practically –

Open two query window in your SQL Server Management Studio. Assume that you have a table named Products and you tried to insert some records within one transaction as shown below in query window 1.

BEGIN TRAN

INSERT INTO Products
     VALUES (1, 'iPhone 5s', 55200)

--ROLLBACK
--COMMIT TRAN

To create the blocking situation, i have not committed transaction in above query

Query_Window_1

Now in second query window if you fire select statement command without any hint then you have to wait for infinite interval of time as our above transaction is not completed. But what happen if you fire select command with “NOWAIT” hint like below –

SELECT * FROM Products WITH (NOWAIT);

Above query results in an error as shown below.

Query_With_NoWaitHint

And you have to now manage the exception in your application and modify the code accordingly. Hope this post may help you in future.

That’s it for now folks. Hope you like this post.

Tip: Finding a value in any of the columns in a SQL Server table

Few days ago one of my colleague working in another team in my office asked me one question that he have had one table named “PatientDetail” with 58 columns and he wanted to find value from all varchar columns. As a developer the first solution came in my mind was – check value in each column like this FirstName = ‘hello’ OR LastName = ‘hello’ OR…so on. One thing to note here is there are more than 50 varchar columns out of 58 columns. So your query looks something like this –

SELECT * FROM PatientDetail
    WHERE FirstName = 'hello'
	  OR LastName = 'hello'
	  OR Column3 = 'hello'
	  OR Column4 = 'hello'
	  Or Column5 = 'hello'
	  OR ... Column52 = 'hello'

Don’t you think that above query becomes too much lengthy if i write each column name here. So What is the preferred solution ?

Hmm Do you know that you can use column name in “IN Clause” in sql server!! Really ??

Yes, the above query you can rewrite like this –

SELECT * FROM PatientDetail
     WHERE 'hello' IN (FirstName, LastName, Column3, Column4, Column5,...)

Well, we just reverse the IN Clause! and this is perfectly valid statement for sql server. Try it yourself. Now the only problem here is we still have to write more than 50 columns in “IN” clause. So to overcome this problem, let’s convert the above query into dynamic sql.

-- *** Prepare comma seperated column list ***
DECLARE @ColumnNames VARCHAR(3000)

SELECT @ColumnNames = ISNULL(@ColumnNames + ', ' + QUOTENAME(COLUMN_NAME), QUOTENAME(COLUMN_NAME)) 
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'PatientDetail'
	AND DATA_TYPE IN ('char', 'nvarchar', 'text', 'varchar')

--PRINT @ColumnNames

DECLARE @query VARCHAR(MAX)=
	'SELECT * FROM PatientDetail
	WHERE ''hello'' IN (' + @ColumnNames + ')'
	
EXEC SP_EXECUTESQL @query

That’s it. Hope you like this tip. If you know any other solution then post it in the comments. Thank you for reading this post.

The Role of data type precedence for COALESCE and ISNULL Function in SQL Server

Few days ago, I had a discussion regarding key difference between COALESCE and ISNULL function with one of my colleague in my office. Though he knows some of the differences but he doesn’t have any idea about data type precedence in both of these functions. How the data type of the result is determine here. So i thought to write the post regarding that.

In COALESCE data type of the result determines based on the data type precedence. As per the MSDN

Returns the data type of expression with the highest data type precedence.

 Let’s see it practically.

SELECT COALESCE (NULL, 1, GETDATE());

As this function evaluates the arguments in order and returns the first non-nullable value. In the above query first argument is null, so it evaluates second argument which is “1” and is of “Int” data type. But the third argument is “GetDate()” which is of “Datetime” data type and DATETIME data type has a higher precedence than INT. So the output of the above query is in DATETIME i.e. When we execute that query SQL Server will convert “1” into DATETIME.

1900-01-02 00:00:00.000

coalesce_exa1

Let’s see the another query.

SELECT COALESCE (NULL, 'Hello World', 1);

 coalesce_exa2

As you can see in above query that SQL Server will try to convert “Hello World” which is of VARCHAR data type into INT and results in error. Why ? because “INT” has highest data type precedence than “VARCHAR”. You can refer the Data Type Precedence chart from MSDN.

In ISNULL data type of the result determines based on the “first argument”, i.e data type precedence will not influenced the result. As per the MSDN

Returns the same type as check_expression. If a literal NULL is provided as check_expression, returns the datatype of the replacement_value.

 NOTE: Here check_expression is the first argument and replacement_value is the second argument passed in the ISNULL  function.

Now Let’s see it practically.

DECLARE @var_datetime DATETIME

SELECT ISNULL(@var_datetime, 1)

isnull_exa1

Here variable “@var_datetime” is of type datetime and by default it’s value is NULL. So as the first argument is of type DATETIME which means result type is of datetime. So when you execute query, sql server will convert “1” into datetime and return the result.

Let’s see the another query.

DECLARE @var_int INT

SELECT ISNULL(@var_int, 'Hello World')

isnull_exa2

As you can see that first argument is of type INT. So SQL Server will try to convert “Hello World” which is of VARCHAR into INT and results in error.

That’s it for now. Hope you like this post.

Display query editor and results in seperate tab in SSMS

As a developer, Its very helpful for us to set Sql Server Management Studio’s (SSMS) environment as friendly as possible. In the recent conversation in my team, one of my colleague asked me that how you set query editor and its results in separate tabs in SSMS ? So i thought to write the post regarding this feature given in SSMS for my readers.

By Default when you execute the query, results and messages or execution plan tabs are shown in lower half of the query editor window as shown in below screen.

Before_Seperate_Tab

Now to set query, results and messages in separate tabs, we have to follow the below steps.

  • Click on the “Options…” in Tools menu.
  • Then expand “Query Results” in left pane and then select “Results to Grid” option.
  • Now in the right pane, Click on “Display results in a separate tab” checkbox.
  • Click OK.

Option_Window

That’s it. Now when you open new query window, you will see latest configuration changes as show in below image.

Result_With_SeperateTab

This feature is really very helpful when we have more than one results set or analyzing query execution plan. Hope you like this tips.

SQL Server -Misconception about Count(ColumnName) vs Count(1) vs Count(*)

Few days ago, I was having a healthy debate with one of my colleague in my team regarding Count(columnName) vs Count(1) vs Count(*). And i observed that he was having some misunderstanding regarding Count function in sql server. So i thought it’ll be a good idea to write post regarding it.

So what’s the misunderstanding is – According to him, Count(1) will give the count of records of first column in the table and Count(2) will give the count of records of second column in the table and so on. Well really is it ? Let’s see it practically.

First let’s create the table with some sample data in it.


DECLARE @Products TABLE
(
ProductID INT,
ProductName VARCHAR(50),
Price MONEY
)

INSERT INTO @Products
     VALUES (1, 'iPhone 6s', 55200),
     (1, 'HTC ONE E9', 18000),
     (1, 'Samsung Galaxy J7', 15100),
     (1, NULL, 10200),
     (1, 'MOTO G3', NULL)

First let’s see the Count(*) in action.

COUNT(*) : – It counts the number of records in the table regardless of NULL values and duplicate values. Pretty simple right.

SELECT Count(*) AS [Count(*)] FROM @Products

COUNT(1) / COUNT(‘Test’) :- It counts the number of records in the table regardless of NULL values and duplicate values.

But here 1 does NOT refer to an ordinal position of a column. This will not count the records of the first column of the table as COUNT(ColumnName) does.

 

SELECT Count(*) AS [Count(*)]            -- 5 Count
	,COUNT(1) AS [Count(1)]          -- 5 Count
	,COUNT('Test') AS [Count(Test)]   -- 5 Count
FROM @Products

Count_diffMethod

Here COUNT(1) and COUNT(‘Test’) gives you the same count because 1 or 2 or “Test” inside Count() function is a non-null expression. When you specify any non-null expression, sql server will treat them as shown in below image.

WithoutCount

Hmmm the picture is clear now. But then what is the difference between COUNT(*) and COUNT(1) and COUNT(‘Test’) ?

Well the answer is – there is absolutely no difference between them! Alright, Let’s move ahead.

COUNT(ColumnName) :- It counts the number of records of specified column name regardless of duplicate values, But it doesn’t count the NULL values. i.e. It counts only the non-null values.

SELECT Count(ProductID) AS CountProductID
,COUNT(ProductName) AS CountProductName
,COUNT(Price) AS CountPrice
FROM @Products

CountColumn

That’s it guys. Hope you enjoyed this post. Please leave your comment. Thank you.