Tuesday, August 4, 2015

SQL Server-- Row Logic Manipulation


In SQL sometime we are asked to compare data from two or more rows and manipulate data to meet certain business requirements.

Here I am going to show one common business requirement we see in different industries such as sales and marketing, healthcare, etc

Let's look at this transcational data in our sample table

Here you see that for ID 1, there are three rows, for ID 2, there are 3 rows and for ID 3, there is 1 rows.


Now say that your business requirement ask you that get the first Visit_Date and last Purchase_Date as one row. Whenever there is more than 3 day gap, treat them as new data set.

So your final result should look like this




Let's see how we can achieve this

--- Sample Data

-- Create following table to illustrate date tranformation

--Drop table dbo.sample_Data

Create Table dbo.sample_Data
( ID int
, Visit_Date Date
, Purchase_Date Date)

---INSERT sample Data

INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (1, '2015-06-01','2015-06-02')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (1, '2015-06-02','2015-06-03')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (1, '2015-06-03','2015-06-04')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (2, '2015-06-01','2015-06-02')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (2, '2015-06-06','2015-06-07')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (2, '2015-06-07','2015-06-08')
INSERT INTO dbo.sample_Data (ID, Visit_Date, Purchase_Date) VALUES (3, '2015-06-01','2015-06-02')

-- Check the data from table
SELECT * FROM dbo.sample_Data

- Now let's look data and what we are trying to do.

--Condition 1. In this case where ID = 1, we want one row of data with original Visit_Date and Last Purchase_Date here it is '2015-06-01' and 2015-06-04' as one row

-- Condition 2 if there are multiple different visit and purchase date, you want to get first visit_date and Last purchase_Date. If there is more then 3 day gap, you
--- want to have another row showing next visit_Date and Purchase_Date

-- ********************************
-- ==> STEP1: Create temp tables.
-- ********************************
-- Create the temp processing table.
-- Rows will be deleted from this table during merge process.



SELECT ROW_NUMBER () OVER (ORDER BY ID, Visit_Date) AS RowID, *
INTO #temp_Sample_Data
FROM dbo.sample_Data

-- Create table used for merging dates.
-- Initially, rows are associated with itself.

SELECT ID
, RowID AS RowID1
, Visit_Date As Visit_Date1
, Purchase_Date as Purchase_Date1
,RowID AS RowID2
, Visit_Date As Visit_Date2
, Purchase_Date as Purchase_Date2
INTO #tem_Sample_Date_Merge
 FROM #temp_Sample_Data

 -- *********************************************************
-- ==> STEP 2: Merge the records.
-- *********************************************************
DECLARE @MaxDayDifference INT =3
DECLARE @Continue INT = 1

BEGIN TRAN

WHILE (@Continue >0)

BEGIN

DELETE A
FROM #tem_Sample_Date_Merge AS A
INNER JOIN #tem_Sample_Date_Merge AS B
ON A.RowID2 = B.RowID2
AND A.RowID1 > B.RowID1

UPDATE A
SET RowID2 = B.RowID2
, Visit_Date2 = B.Visit_Date2
, Purchase_Date2 =B.Purchase_Date2
FROM #tem_Sample_Date_Merge AS A
INNER JOIN #tem_Sample_Date_Merge AS B
ON A.ID = B.ID
AND DATEDIFF(DAY, A.Purchase_Date2, B.Visit_Date1) <=@MaxDayDifference
AND B.RowID1 = (SELECT MIN(C.RowID1) FROM #tem_Sample_Date_Merge AS C
Where C.ID = B.ID
AND C.RowID1> A.RowID1)

SET @Continue = @@rowcount

END
COMMIT TRAN



SELECT A.ID
, A.Visit_Date
, C.Purchase_Date
FROM  #temp_Sample_Data As A
INNER JOIN #tem_Sample_Date_Merge AS B
ON A.RowID = B.RowID1
INNER JOIN #temp_Sample_Data AS C
ON B.RowID2 = C.RowID
ORDER BY A.ID, A.Visit_Date



Wednesday, March 25, 2015

Age calculation and Age function in SQL Server


AGE CALCULATION

1 Calculating Age as of End of the year

Sometime as business requirement dictate that we should calculate age at the time of end year ( this can be end of financial year, accounting year, measurement year, etc)

Let's say we have a table of employee with a field as Date_of_Birth or DOB.

DECLARE @AgeAsOf DATE = '2015-12-31

Select DOB, AGE = CASE WHEN dateadd(year, datediff (year, E.DOB, @AgeAsOf), E.DOB) > @AgeAsOf
THEN datediff (year, E.DOB, @AgeAsOf) - 1
ELSE datediff (year, E.DOB, @AgeAsOf)
END

FROM dbo.Employee


Say further that you have to select data based on certain age range and you have to passed this in your WHERE condition. Here we are looking for those employee who live in US
and are in age range between 35 and 50 only

DECLARE @AgeAsOf DATE = '2015-12-31

Select * FROM dbo.EMPLOYEE E
WHERE E.Employee_location = 'US'
AND (CASE WHEN dateadd(year, datediff (year, E.DOB, @AgeAsOf), E.DOB) > @AgeAsOf
THEN datediff (year, E.DOB, @AgeAsOf) - 1
ELSE datediff (year, E.DOB, @AgeAsOf)
END ) BETWEEN 35 AND 50

2. Calculating Age as of today (getdate())

You can use above query to get age as of today by replacing @AgeAsOf variable with getdate() function

Select DOB, AGE = AGE =CASE WHEN dateadd(year, datediff (year, E.DOB, getdate()), E.DOB) >getdate()
THEN datediff (year, E.DOB, getdate()) - 1
ELSE datediff (year, E.DOB, getdate())
END

FROM dbo.Employee


Select * FROM dbo.EMPLOYEE E
WHERE E.Employee_location = 'US'
AND (CASE WHEN dateadd(year, datediff (year, E.DOB, getdate()), E.DOB) > getdate()
THEN datediff (year, E.DOB, getdate() - 1
ELSE datediff (year, E.DOB, getdate())
END ) BETWEEN 35 AND 50


3. Creating a function to get AGE.
create function [dbo].[AgeAtGivenDate](
    @DOB    DATE,
    @PassedDate DATE
)

returns INT
with SCHEMABINDING
as
begin

DECLARE @iMonthDayDob INT
DECLARE @iMonthDayPassedDate INT


SELECT @iMonthDayDob = CAST(datepart (MM,@DOB) * 100 + datepart  (DD,@DOB) AS INT)
SELECT @iMonthDayPassedDate = CAST(datepart (MM,@PassedDate) * 100 + datepart  (DD,@PassedDate) AS INT)

RETURN DateDiff(YY,@DOB, @PassedDate)
- CASE WHEN @iMonthDayDob <= @iMonthDayPassedDate
  THEN 0
  ELSE 1
  END

END


--How to execute your function
DECLARE @ret INT;
EXEC @ret = [dbo].[AgeAtGivenDate] @DOB ='1923-08-28', @PassedDate = '2015-12-31'
PRINT @ret




Select  [dbo].[AgeAtGivenDate] ('1923-08-28',  '2015-12-31')

Select dbo.AgeAtGivenDate, DOB
FROM EMPLOYEE


Monday, March 16, 2015

SQL Table with Primary Key and Indexes

When we create a sql table with a primary key, we need to understand what happens behind the scene and how indexes are affected by it.

Let's go through a simple example and touch each topic as we go through it.

Let's create a table called country with following structure.

Create table dbo.Country 
(Country_ID int Identity (1,1) NOT NULL Primary Key
, Name Varchar(40)  NOT NULL
, Alpha_2_Code_Name Varchar(2)
, Alpha_3_Code_Name Varchar(3)
, Numeric_Code int NOT NULL
, ISO_3166_2_Code Varchar(20)
);

And insert following test data in this table just created.

Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Afghanistan','AF','AFG',004,'ISO 3166-2:AF')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Ă…land Islands','AX','ALA',248,'ISO 3166-2:AX')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Albania','AL' ,'ALB',008,'ISO 3166-2:AL')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Algeria','DZ' ,'DZA',012,'ISO 3166-2:DZ')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('American Samoa','AS','ASM',016 ,'ISO 3166-2:AS')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Andorra','AD' ,'AND',020,'ISO 3166-2:AD')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Angola','AO' ,'AGO',024,'ISO 3166-2:AO')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Anguilla','AI' ,'AIA',660,'ISO 3166-2:AI')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Antarctica','AQ','ATA',010,'ISO 3166-2:AQ')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Antigua and Barbuda','AG' ,'ATG',028 ,'ISO 3166-2:AG')
Insert into dbo.Country (Name, Alpha_2_Code_Name, Alpha_3_Code_Name, Numeric_Code,ISO_3166_2_Code) VALUES  ('Argentina','AR','ARG',032,'ISO 3166-2:AR')

Now if we look at the table structure, this is how it looks like:














Now we look at index physical state by querying this function, we see following

SELECT page_count, index_level, record_count, index_depth
FROM sys.dm_db_index_physical_stats(db_id(N'Data_Base_Name'),object_id(N'dbo.country'),NULL, NULL, 'Detailed');









Let's see how this changes when we delete data from our table and how the index page reflects in that change.


1. DELETE FROM dbo.Country

Now re-run your above query

SELECT page_count, index_level, record_count, index_depth
FROM sys.dm_db_index_physical_stats(db_id(N'Data_Base_Name'),object_id(N'dbo.country'),NULL, NULL, 'Detailed');








So here basically we see that record count are deleted but page_count, and index_depth  remain same.

2. TRUNCATE table dbo.Country

After we truncate table, re-run your script.



SELECT page_count, index_level, record_count, index_depth
FROM sys.dm_db_index_physical_stats(db_id(N'Data_Base_Name'),object_id(N'dbo.country'),NULL, NULL, 'Detailed');









Here we see that everything has been truncated.

So when we truncate table, indexes is automatically dropped but structure definition remain same.

Wednesday, November 12, 2014

SQL Server Job Run Time from SQL Agent



How to get run time for job total run time from history.

USE MSDB;
GO

select
 j.name as 'JobName',
 run_date,
 run_time,
 CONVERT(CHAR(8),DATEADD(second,run_time,0),108) AS Total_Run_Time,
 h.message
From msdb.dbo.sysjobs j
INNER JOIN msdb.dbo.sysjobhistory h
 ON j.job_id = h.job_id
where j.enabled = 1  --Only Enabled Jobs
and j.name = 'JOB_Name_Make_Change'
order by run_date desc

Tuesday, October 28, 2014

SQL Server Interview Questions

Note: I will be updating this post on a regular basis


1. I was wondering when inserting a record into this table, does it lock the whole table?

Ans: In SQL Server, by default, a table in not locked away when we are inserting data. If someone is accessing the same table, it will give them dirty-data read. However, coming back to original question, Not by default, but if you use the TABLOCK hint or if you're doing certain kinds of bulk load operations, then yes.

Friday, October 24, 2014

sql if exists drop procedure

Many time, we when we are creating or alter an existing stored procedure, it is good practice to add this logic.

There are many way to this. Some of the method are described here.

IF EXISTS (SELECT * FROM sys.procedures WHERE object_id = OBJECT_ID(N'MyStandardStoredProcedureTemplate')   
  AND type in (N'P', N'PC'))  
DROP PROCEDURE dbo.MyStandardStoredProcedureTemplate

See the cost plan for this


Another way of doing this is

IF OBJECTPROPERTY(object_id('dbo.My ProcedureName'), N'IsProcedure') = 1
DROP PROCEDURE dbo.My ProcedureName
GO


Cost plan for this method is



The best practice is to create your stored procedure is to create your stored procedure and then use to alter to make changes as shown below.

USE Database_Name;
GO

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


IF OBJECT_ID('dbo.MyStandardStoredProcedureTemplate') IS NULL -- Check if SP Exists
 EXEC('CREATE PROCEDURE dbo.MyStandardStoredProcedureTemplate AS SET NOCOUNT ON;') -- Create dummy/empty SP to for Alter Statement
GO


ALTER PROCEDURE dbo.MyStandardStoredProcedureTemplate -- Alter the SP Always

AS
BEGIN

SET NOCOUNT ON;
SET XACT_ABORT ON; 

---Declare All your variables Here's

DECLARE  @Now datetime = getdate()

-- Error variables
, @ErrorProc varchar(128)
, @ErrorMessage varchar(1000)
, @ErrorLine int
, @ErrorSeverity int
, @ErrorState int



-- If you are creating a temp table, must add a script to drop before it go to create a table. See the example bellow

IF OBJECT_ID(N'tempdb..#MyTempTableForDataProcessing') IS NOT NULL
BEGIN
    DROP TABLE #MyTempTableForDataProcessing
END



-- Now Create your temp table here.

Create Table #MyTempTableForDataProcessing 
(ID int
, FirstName varchar(20)
, MiddleName varchar(10)
, LastName varchar(20)
)


-- If you are using a subquery in your select, you can populate it during intial stage to load data in buffer for faster select


--Uncomment below statement to your needs

--Insert Into #MyTempTableForDataProcessing
--Select ID,FirstName,MiddleName,LastName from Some_Parent_table


BEGIN TRY

-- Populated all your temp or your Select queries here in begin try section


BEGIN TRANSACTION 

-- Do an actual data write on physical tables like delete, insert, update etcs.

Print 'Hello'

COMMIT Transaction 

END TRY
BEGIN CATCH

    IF  (XACT_STATE()) <> 0
        ROLLBACK TRANSACTION;

select 
 @ErrorProc = ERROR_PROCEDURE()
, @ErrorMessage = ERROR_MESSAGE()
, @ErrorLine = ERROR_LINE()
, @ErrorSeverity = ERROR_SEVERITY()
, @ErrorState = ERROR_STATE()

    RAISERROR ( 
 @ErrorMessage
, @ErrorSeverity
, @ErrorState 
);


END CATCH

  END


Thursday, September 25, 2014

SQL: How to Alter or Modify table in Production Environment

How to Alter or Modify table in Production Environment

 Generally when we have to add a column, modify datatype or change column lenght,
 we simply write Alter statement. This will work perfectly in any development environment as long as we as developer can revert back to starting point.

 However in organization, DBA will ask you to check or if exist condition to any of your DDL scripts. So it's good practice to use
 them even in Development environments. Being said that, let's look at how we can use these practice and make a habit of it.

 Let's create a dummy table and we will walk through this.

 CREATE TABLE dbo.Dummy_Table (
ID int
)


 1. Adding a new column to an existing table.

 Generally this is a statement which we are all familiar with:

 Alter Table dbo.Dummy_Table Add FName Varchar(10)

 The above statement will run any environment perfectly if this column does not exist in your table. However a good practice would be to
 check if this column exist before we add a new column.

IF COL_LENGTH('dbo.Dummy_Table', 'FName') IS NULL
BEGIN
ALTER TABLE dbo.Dummy_Table ADD FName [varchar](10) NULL ;
END


 Now if we re-run a simple statement

ALTER TABLE dbo.Dummy_Table ADD FName [varchar](10) NULL ;

Msg 2705, Level 16, State 4, Line 1
Column names in each table must be unique. Column name 'FName' in table 'dbo.Dummy_Table' is specified more than once.

it will throw an error because that column is already existing in given table.

But if we run this statement, if will be always executed without doing any change to actual table

IF COL_LENGTH('dbo.Dummy_Table', 'FName') IS NULL
BEGIN
ALTER TABLE dbo.Dummy_Table ADD FName [varchar](10) NULL ;
END

 This way, we will be sure that
 script won't fail in production environment and we don't make our DBA mad for giving them a script which can possibly fail.

 2. Changing Column length of an existing column in a table

 This is a general statement which we use to alter column length.

 ALTER TABLE dbo.Dummy_Table ALTER COLUMN FName [varchar](15) NULL ;

 Notice that in orginal table we have column length of 10 and now we are increasing to 15. We also know that column already exist in given table.

 So we are checking to making sure that column length IS NOT NULL and if that statement is true we can change to any length we want.

IF COL_LENGTH('dbo.Dummy_Table', 'FName') IS NOT NULL
BEGIN
ALTER TABLE dbo.Dummy_Table ALTER COLUMN FName [varchar](15) NULL ;
END