"No one is harder on a talented person than the person themselves" - Linda Wilkinson ; "Trust your guts and don't follow the herd" ; "Validate direction not destination" ;

September 29, 2009

Example - Use of Cross Apply Operator

We will look at simple example using CROSS Apply operator. Using Cross Apply Operator to JOIN table returned by function. Example below lists an example step by step

use tempdb

DROP TABLE Employee
DROP TABLE ADDRESS

--STEP 1
CREATE TABLE Employee
(
     NAME VARCHAR(20),
     Id INT Primary Key
)

--STEP 2
CREATE TABLE ADDRESS
(
   Id INT Foreign Key References Employee(Id),
   Location VARCHAR(100),
   Isactive bit
)

--STEP 3
INSERT INTO Employee (NAME,Id)
VALUES ('Ram',1)
INSERT INTO Employee (NAME,Id)
VALUES ('Sri',2)

--STEP 4
INSERT INTO ADDRESS (ID,Location,Isactive)
VALUES (1,'Chennai',1)
INSERT INTO ADDRESS (ID,Location,Isactive)
VALUES (1,'Bangalore',0)
INSERT INTO ADDRESS (ID,Location,Isactive)
VALUES (2,'Bangalore',1)
INSERT INTO ADDRESS (ID,Location,Isactive)
VALUES (2,'Delhi',0)

--STEP 5
SELECT * FROM ADDRESS

--STEP 6
CREATE Function dbo.getaddress (@id int)
RETURNS
@ADDRESS TABLE
(
   Id int,
   location varchar(100)
)
AS
BEGIN
   IF ISNULL(@id,0) = 0
     BEGIN
       RETURN
     END
   INSERT INTO @ADDRESS(Id, location)
   SELECT ID, Location
   FROM ADDRESS
   WHERE Isactive = 1
   AND Id = @id
RETURN
END

--STEP 7
--Function and Cross Apply
SET STATISTICS IO ON
SELECT * FROM
dbo.Employee T1 CROSS APPLY
dbo.getaddress(T1.Id)

--Table '#1CF15040'. Scan count 2, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--Table 'Employee'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SELECT * FROM
dbo.Employee T1 JOIN ADDRESS AD
ON T1.Id = AD.Id
WHERE AD.Isactive = 1
--Table 'Employee'. Scan count 0, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
--Table 'ADDRESS'. Scan count 1, logical reads 1, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SCAN Count is Zero for employee table. Reason Here. Both inputs to nested loop are "SEEK"s, meaning you should usually see zero scan count.


To Touch on Basics Again
  • Scalar Functions - Returns Single Variable
  • Inline Table Value Function - Returns Table
  • MultiStatement TVF - Return Table as defined in Schema
Good link on Difference between Functions and Stored Procedures Pinal Article - link

Happy Reading!!

September 14, 2009

Learning's on perfomance troubleshooting

Enables DBCC Traceon (1222) to Capture Deadlocks.
Run below trace to capture deadlock process. Link here

I also learnt I need to do a good learning on locking concepts, I found below links useful
Range locks
Geek City: What do you intend with that lock?
SQL Server DBA Concurrency and Locking Interview Questions
Presentation Links: SQL Server Performance Tuning (Quest)


Happy Learning!!!

September 08, 2009

TSQL XML Parsing

Parsing XML using TSQL. Below listed are some approaches.

--Approach I
DECLARE @x XML
SET @x = '<Response>
<Status><IsError>1</IsError></Status>
<Errors>
<Error><Number>1</Number><Message>AAAA</Message></Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
</Errors>
</Response>'

SELECT ParamValues.ID.query('Number').value('.','integer') as 'Number', ParamValues.ID.query('Message').value('.','VARCHAR(20)') as 'Message'
FROM @x.nodes('/Response/Errors/Error') as ParamValues(ID)

--Approach II
DECLARE @x XML
SET @x = '<Response>
<Status><IsError>1</IsError></Status>
<Errors>
<Error><Number>1</Number><Message>AAAA</Message></Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
</Errors>
</Response>'

SELECT c.value('./Number[1]', 'Integer')
, c.value('./Message[1]', 'VARCHAR(100)')
FROM @x.nodes('Response/Errors/Error') T(c)

--Approach III
DECLARE @x XML
SET @x = '<Response>
<Status><IsError>1</IsError></Status>
<Errors>
<Error><Number>1</Number><Message>AAAA</Message></Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
</Errors>
</Response>'

DECLARE @max INT, @i INT
SELECT @max = @x.query('<e>{ count(/Response/Errors/Error) }</e>').value('e[1]','int')
print @max
SET @i = 1
DECLARE @ErrorId VARCHAR(10)
DECLARE @ErrorMessage VARCHAR(100)
WHILE @i <= @max
BEGIN
SELECT @ErrorId = x.value('Number[1]', 'VARCHAR(10)') FROM @x.nodes('/Response/Errors/Error[position()=sql:variable("@i")]') e(x)
SELECT @ErrorMessage = x.value('Message[1]', 'VARCHAR(100)') FROM @x.nodes('/Response/Errors/Error[position()=sql:variable("@i")]') e(x)
SELECT @ErrorId, @ErrorMessage
SET @i = @i + 1
END

--With SubNodes
DECLARE @x XML
SET @x = '<Response>
<Status><IsError>1</IsError></Status>
<Errors>
<Error><Number>1</Number>
<Message>AAAA</Message>
<SubError>
<SNumber>5</SNumber>
<SMessage>SAAA</SMessage>
</SubError>
</Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
</Errors>
</Response>'

SELECT c.value('./Number[1]', 'Integer')
, c.value('./Message[1]', 'VARCHAR(100)')
, c.value('./SubError[1]/SNumber[1]', 'Integer')
, c.value('./SubError[1]/SMessage[1]', 'VARCHAR(100)')
FROM @x.nodes('Response/Errors/Error') T(c)

--Master Status Error 0, Sub Nodes Error Collection

DECLARE @x XML
SET @x = '
<ReturnStatus>
<Status>0</Status>
</ReturnStatus>
<Response>
<Errors>
<Error><Number>1</Number>
<Message>AAAA</Message>
<SubError>
<SNumber>5</SNumber>
<SMessage>SAAA</SMessage>
</SubError>
</Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
<Error><Number>2</Number><Message>BBB</Message></Error>
</Errors>
</Response>'

SELECT
c.value('..[1]/..[1]/..[1]/ReturnStatus[1]/Status[1]', 'Integer')
, c.value('./Number[1]', 'Integer')
, c.value('./Message[1]', 'VARCHAR(100)')
, c.value('./SubError[1]/SNumber[1]', 'Integer')
, c.value('./SubError[1]/SMessage[1]', 'VARCHAR(100)')
FROM @x.nodes('Response/Errors/Error') T(c)

I thought of posting it from my past learning. We can use above approach to load data into a table from XML...

SQL Server 2005 XQuery Performance Tips
Bulk Inserts with XML
OpenXML and XQuery Optimisation Tips
Performance tips of using XML data in SQL Server


Happy Reading!!

September 01, 2009

Simple TSQL Exercise

Question #1
Given a Table, Lookup based on Priority and return the value of action based on it

--Priotitylist
1. Match for PriorityA, PriorityB
2. Match for PriorityA, *
3. Match for *, PriorityB
4. Match for *, *

--STEP 1
CREATE TABLE TestPriority
(
PriorityA CHAR(5) NOT NULL,
PriorityB CHAR(5) NOT NULL,
Action BIT NOT NULL
)

--STEP2
INSERT INTO TestPriority (PriorityA, PriorityB, Action)
VALUES ('AA','BB',1)
INSERT INTO TestPriority (PriorityA, PriorityB, Action)
VALUES ('AA','*',0)
INSERT INTO TestPriority (PriorityA, PriorityB, Action)
VALUES ('*','BB',1)
INSERT INTO TestPriority (PriorityA, PriorityB, Action)
VALUES ('*','*',0)

--STEP 3
DECLARE @PriorityA CHAR(5)
DECLARE @PriorityB CHAR(5)
SET @PriorityA = '*'
SET @PriorityB = 'BB'

SELECT Top 1 Action
FROM
(
SELECT ACTION, Priority =
CASE WHEN PriorityA = @PriorityA AND PriorityB = @PriorityB THEN 1
WHEN PriorityA = @PriorityA AND PriorityB = '*' THEN 2
WHEN PriorityA = '*' AND PriorityB = @PriorityB THEN 3
WHEN PriorityA = '*' AND PriorityB = '*' THEN 4
END
FROM TestPriority
) AS TESTResult
WHERE Priority IS NOT NULL
Order by Priority ASC

Question #2
You Have a Customer And Interest Table. Write a Query to Calculate Interest based on below conditions

Condition
1. When a match is found use the interest value
2. When no match found use default value '*'

--STEP 1
CREATE TABLE INTEREST
(
Region VARCHAR(5),
Rate INT
)

--STEP 2
INSERT INTO INTEREST(Region,Rate)
VALUES('CA',5),('US',10),('FR',20)

INSERT INTO INTEREST(Region,Rate)
VALUES('*',25)

--STEP 3
CREATE TABLE Customer
(
Name VARCHAR(20),
Amount INT,
Region VARCHAR(5)
)

--STEP 4
INSERT INTO Customer(Name,Amount,Region)
VALUES('Raj',10000,'CA')

INSERT INTO Customer(Name,Amount,Region)
VALUES('Raja',10200,'SA')

INSERT INTO Customer(Name,Amount,Region)
VALUES('Raa',10200,'FR')

INSERT INTO Customer(Name,Amount,Region)
VALUES('Ram',10200,'IN')

SELECT * FROM Customer

--STEP 5
SELECT C.Name, C.Amount*I.Rate/100, C.Amount, C.Region FROM
INTEREST I JOIN Customer C
ON ((I.Region = C.Region) OR (I.Region = CASE WHEN C.Region NOT IN (SELECT Region FROM INTEREST) THEN '*' END))