Labels

Friday, December 30, 2011

CROSS APPLY VS OUTER APPLY

The terminology used with CROSS APPLY and OUTER APPLY is somewhat misleading. A better name for CROSS APPLY would be INNER APPLY. The APPLY ( CROSS APPLY, OUTER APPLY) operator allows value(s) from the main (outer) query to be used as input to a table-valued function. The rows from the main query and table-valued function are combined. As an example, the orgchart subtree table-valued function may return 290 rows in subtree format for the CEO of AdventureWorks but only 10 rows for the marketing manager. The following T-SQL example scripts will demonstrate how CROSS APPLY and OUTER APPLY can be used in practical database applications.

In the following example we demonstrate CROSS APPLY & OUTER APPLY with group by derived table.

USE AdventureWorks;

DECLARE  @Year  INT,
         @Month INT
SET @Year = 2003;
SET @Month = 

-- SQL cross apply  
-- SQL Server cross apply
-- SQL group by
-- SQL correlated subquery

SELECT [Customer] = s.Name,

       -- Special money data type currency formatting option
       [TotalSalesforMonth] = '$' +
        Convert(VARCHAR,Convert(MONEY,SalesAmount.OrderTotal),1)
FROM     Sales.Customer AS c
         -- The customer name is in this table
                       INNER JOIN Sales.Store AS s
           ON s.CustomerID = c.CustomerID
         -- Cross apply
         CROSS APPLY (SELECT   soh.CustomerId,
                               Sum(sod.LineTotal) AS OrderTotal
                      FROM     Sales.SalesOrderHeader AS soh
                               INNER JOIN Sales.SalesOrderDetail AS sod
                                 ON sod.SalesOrderId = soh.SalesOrderId
                      -- Correlation to the outer query
                     WHERE soh.CustomerId = c.CustomerId
                                             -- Filter data
                               AND Year(OrderDate) = @Year
                               AND Month(OrderDate) = @Month
                      GROUP BY soh.CustomerId) AS SalesAmount
ORDER BY [Customer]
             
/* Partial results  
(174 row(s) affected)

Customer                      TotalSalesforMonth
Accessories Network           $283.95
Acclaimed Bicycle Company     $3,405.17
Action Bicycle Specialists    $92,278.05
Advanced Bike Components      $68,906.65
Aerobic Exercise Company      $48.59
*/
             
-- SQL outer apply
-- SQL Server outer apply 

SELECT [Customer] = s.Name,
                     -- Special money data type currency formatting option
       [TotalSalesforMonth] = '$' +
                      Convert(VARCHAR,Convert(MONEY,SalesAmount.OrderTotal),1)
FROM     Sales.Customer AS c
                       -- The customer name is in this table
                       INNER JOIN Sales.Store AS s
           ON s.CustomerID = c.CustomerID
         OUTER APPLY (SELECT   soh.CustomerId,
                               Sum(sod.LineTotal) AS OrderTotal
                      FROM     Sales.SalesOrderHeader AS soh
                               INNER JOIN Sales.SalesOrderDetail AS sod
                                 ON sod.SalesOrderId = soh.SalesOrderId

                      -- Correlation to the outer query
                     WHERE soh.CustomerId = c.CustomerId
                               -- Filter data
                               AND Year(OrderDate) = @Year
                               AND Month(OrderDate) = @Month
                      GROUP BY soh.CustomerId) AS SalesAmount
ORDER BY [Customer]
GO
             
/* Partial results
             
(701 row(s) affected)

Customer                      TotalSalesforMonth
Acceptable Sales & Service    NULL
Accessories Network           $283.95
Acclaimed Bicycle Company     $3,405.17
Ace Bicycle Supply            NULL
Action Bicycle Specialists    $92,278.05
Active Cycling                NULL

*/
              In the following example we demonstrate CROSS APPLY & OUTER APPLY with table-valued function. We start with creating a table-valued function. In the second step, we will create data and store it in a table variable.

USE AdventureWorks;

GO

-- Minimum function

-- Maximum function

-- SQL minmax function

-- T-SQL table-valued function

CREATE FUNCTION fnMinimumMaximum

               (@Input1 MONEY,

                @Input2 MONEY)

RETURNS @MinMax  TABLE (Minimum money, Maximum money)

AS

BEGIN

  IF @Input1 is NULL and @Input2 is NULL RETURN

  INSERT @MinMax

    SELECT CASE

              WHEN @Input1 < @Input2 THEN @Input1

              WHEN @Input2 < @Input1 THEN @Input2

              ELSE COALESCE(@Input1,@Input2)

            END AS Minimum,

            CASE

              WHEN @Input1 > @Input2 THEN @Input1

              WHEN @Input2 > @Input1 THEN @Input2

              ELSE COALESCE(@Input1,@Input2)

            END AS Maximum;

  RETURN

END -- function



GO

-- SELECT * FROM dbo.fnMinimumMaximum(NULL, NULL) 

-- SQL table variable create and population

-- Cross or outer apply will select the maximum dimension

DECLARE @Product TABLE (ProductName varchar(30), Width int, Height int) 

INSERT @Product VALUES ( 'PortraitFrame', 8, 11)

INSERT @Product VALUES ( 'LandscapeFrame', 12, 7)

INSERT @Product VALUES ( 'Notepad Computer', NULL, NULL)

INSERT @Product VALUES ( 'CircleFrame', 10, 10)

INSERT @Product VALUES ( 'Ringbinder', NULL, NULL)



-- OUTER APPLY includes nomatches (null data)

-- SQL outer apply

-- Outer apply T-SQL

-- Outer apply mssql

-- SQL outer apply with table-valued function

SELECT ProductName, MaxDimension=Maximum

FROM @Product p

OUTER APPLY dbo.fnMinimumMaximum (Width, Height)

/*

ProductName             MaxDimension

PortraitFrame           11.00

LandscapeFrame          12.00

Notepad Computer        NULL

CircleFrame             10.00

Ringbinder              NULL

*/



-- CROSS APPLY excludes nomatches (null data)

-- SQL cross apply

-- Cross apply T-SQL

-- Cross apply mssql

-- SQL cross apply with table-valued function

SELECT ProductName, MaxDimension=Maximum

FROM @Product p

CROSS APPLY dbo.fnMinimumMaximum (Width, Height)

GO

/*

ProductName             MaxDimension

PortraitFrame           11.00

LandscapeFrame          12.00

CircleFrame             10.00

*/

------------

The following example applies a derived table from a correlated subquery instead of table-valued function (UDF).

------------

-- OUTER APPLY versus CROSS APPLY using derived table

------------

-- Create and populate tables for testing

USE tempdb;

GO

SET NOCOUNT ON;

CREATE TABLE Account (

  AccountID   INT    IDENTITY    PRIMARY KEY,

  AccountName VARCHAR(50),

  CreateDate  SMALLDATETIME    DEFAULT (CURRENT_TIMESTAMP),

  IsActive    BIT    DEFAULT (1))



GO



CREATE TABLE [Transaction] (

  TransactionId   INT    IDENTITY    PRIMARY KEY,

  AccountID       INT,

  Amount          MONEY,

  TransactionDate SMALLDATETIME    DEFAULT (getdate()))



GO

INSERT Account(AccountName) VALUES ('Roger Smith')

INSERT Account(AccountName) VALUES ('Linda White')

INSERT Account(AccountName) VALUES ('Corner Hardware')

INSERT Account(AccountName) VALUES ('Laptop Land')

INSERT Account(AccountName) VALUES ('Cellphone City')



INSERT [Transaction] (AccountID, Amount) VALUES(1, 1400.0)

INSERT [Transaction] (AccountID, Amount) VALUES(1, 1200.0)

INSERT [Transaction] (AccountID, Amount) VALUES(1, 1300.0)

INSERT [Transaction] (AccountID, Amount) VALUES(1, 1100.0)

INSERT [Transaction] (AccountID, Amount) VALUES(2, 400.0)

INSERT [Transaction] (AccountID, Amount) VALUES(2, 200.0)

INSERT [Transaction] (AccountID, Amount) VALUES(2, 300.0)

INSERT [Transaction] (AccountID, Amount) VALUES(2, 900.0)

INSERT [Transaction] (AccountID, Amount) VALUES(3, 33400.0)

INSERT [Transaction] (AccountID, Amount) VALUES(3, 11200.0)

INSERT [Transaction] (AccountID, Amount) VALUES(3, 22300.0)

INSERT [Transaction] (AccountID, Amount) VALUES(3, 12100.0)

GO



-- CROSS APPLY returns only matching data

-- SQL cross apply

-- SQL derived table

-- SQL correlated subquery

SELECT   a.AccountName,

         TopAmount=tt.Amount

FROM     ACCOUNT a

CROSS APPLY (SELECT   TOP ( 1 ) Amount

             FROM     [Transaction] t

             WHERE    t.AccountID = a.AccountID

             ORDER BY Amount DESC) tt

ORDER BY AccountName

/*

AccountName       TopAmount

Corner Hardware   33400.00

Linda White       900.00

Roger Smith       1400.00

*/



-- OUTER APPLY returns  matching and non-matching data

-- SQL outer apply

SELECT   a.AccountName,

         TopAmount=tt.Amount

FROM     ACCOUNT a

OUTER APPLY (SELECT   TOP ( 1 ) Amount

             FROM     [Transaction] t

             WHERE    t.AccountID = a.AccountID

             ORDER BY Amount DESC) tt

ORDER BY AccountName

/*

AccountName       TopAmount

Cellphone City    NULL

Corner Hardware   33400.00

Laptop Land       NULL

Linda White       900.00

Roger Smith       1400.00

*/



-- Cleanup

DROP TABLE tempdb.dbo.Account

DROP TABLE tempdb.dbo.[Transaction]

------------

The following example applies AdventureWorks data. Following is the T-SQL code sample script to create a function to be used with "APPLY":

USE AdventureWorks;

GO

-- SQL user-defined function

-- SQL table-valued function

CREATE FUNCTION Sales.fnCustomerOrderTotal

               (@CustomerID INT)

RETURNS @Result TABLE(OrderTotal MONEY)

AS

  BEGIN

    INSERT @Result

    SELECT   SUM(sod.LineTotal) AS OrderTotal

    FROM     Sales.SalesOrderHeader AS soh

             JOIN Sales.SalesOrderDetail AS sod

               ON sod.SalesOrderID = soh.SalesOrderID

    WHERE    soh.CustomerID = @CustomerID

    GROUP BY soh.CustomerID

    

    RETURN

  END



GO

OUTER APPLY will return rows also for customers without order as nulls. In the example below null is translated into 0.0. This is logical businesswise.

-- SQL outer apply

-- SQL Server outer apply

-- Outer apply T-SQL

SELECT   STORE = s.Name,

         OrderTotal = convert(VARCHAR,isnull(cot.OrderTotal,0.0),1)

FROM     Sales.Store AS s

         JOIN Sales.Customer AS c

           ON s.CustomerID = c.CustomerID

         OUTER APPLY Sales.fnCustomerOrderTotal(c.CustomerID) AS cot

ORDER BY STORE


This is the partial result set:

Store OrderTotal
A Bicycle Association 0
A Bike Store 85,177.08
A Cycle Shop 0
A Great Bicycle Company 9,055.29
A Typical Bike Shop 83,457.11
Acceptable Sales & Service 1,258.38
Accessories Network 2,165.79
Acclaimed Bicycle Company 7,300.83

Following is the way to use "CROSS APPLY". The result set will not contain 0.0 (null) orders.

-- SQL cross apply

-- SQL Server cross apply

-- Cross apply mssql

SELECT   STORE = s.Name,

         OrderTotal = convert(VARCHAR,isnull(cot.OrderTotal,0.0),1)

FROM     Sales.Store AS s

         JOIN Sales.Customer AS c

           ON s.CustomerID = c.CustomerID

         CROSS APPLY Sales.fnCustomerOrderTotal(c.CustomerID) AS cot

ORDER BY STORE

This is the partial result set:

Store OrderTotal
A Bike Store 85,177.08
A Great Bicycle Company 9,055.29
A Typical Bike Shop 83,457.11
Acceptable Sales & Service 1,258.38
Accessories Network 2,165.79
Acclaimed Bicycle Company 7,300.83
Ace Bicycle Supply 3,749.13
Action Bicycle Specialists 321,752.83
Active Cycling 1,805.45
Active Life Toys 200,013.37
Active Systems 639.98
Active Transport Inc. 88,245.87
Activity Center 42,650.40



------------

In the following example the supervisory subtree function returns the entire orgchart for a supervisory personnel at any level. For CEO, Ken Sanchez, it returns 290 employees including himself. For non-supervisory staff the table-valued function returns an empty table.

CROSS APPLY returns only correlated data. Therefore, for marketing staff Mary Gibson it returns nothing.

OUTER APPLY will return non-correlated data as well, placing NULLs into the missing cells.

USE tempdb;

GO

-- Create table for demonstration

-- SQL select into create table

-- SQL inner join

SELECT e.EmployeeID,

       e.Title,

       StaffName = LastName + ', ' + FirstName,

       ManagerID,

       Department = d.Name

INTO   Employee

FROM   AdventureWorks.HumanResources.Employee e

       INNER JOIN AdventureWorks.HumanResources.EmployeeDepartmentHistory edh

         ON e.EmployeeID = edh.EmployeeID

       INNER JOIN AdventureWorks.HumanResources.Department d

         ON edh.DepartmentID = d.DepartmentID

       INNER JOIN AdventureWorks.Person.Contact c

         ON e.ContactID = c.ContactID

WHERE  edh.EndDate IS NULL

GO



SELECT COUNT(*) FROM Employee

GO

-- 290 - Entire staff of AdventureWorks Cycles



-- Function to return OrgChart for supervisors only

-- For non-supervisory staff it will have empty return

-- SQL table-valued function

-- SQL user-defined function UDF

-- Tree processing function

CREATE FUNCTION fnSupervisorySubTree

              (@EmployeeID AS INT)

RETURNS @TREE TABLE(EmployeeID   INT,

                    EmployeeName VARCHAR(35),

                    Department   VARCHAR(30),

                    ManagerID    INT,

                    ManagerName  VARCHAR(35),

                    SubTreeLevel INT)

AS

  BEGIN

    -- SQL common table expression - CTE

    -- SQL recursive CTE

    WITH cteSubTree(EmployeeID,EmployeeName,Department,ManagerID,ManagerName,SubTreeLevel)

         AS (-- Anchor (root) node

             SELECT e1.EmployeeID,

                    e1.StaffName,

                    e1.Department,

                    e1.ManagerID,

                    e2.StaffName,

                    0

             FROM   Employee e1

                    LEFT JOIN Employee e2

                      ON (e1.ManagerID = e2.EmployeeID)

             WHERE  e1.EmployeeID = @EmployeeID

             UNION ALL

             -- Recursive nodes to leaf level

             SELECT e1.EmployeeID,

                    e1.StaffName,

                    e1.Department,

                    e1.ManagerID,

                    e2.StaffName,

                    cte.SubTreeLevel + 1

             FROM   Employee e1

                    INNER JOIN Employee e2

                      ON e1.ManagerID = e2.EmployeeID

                    JOIN cteSubTree AS cte

                      ON e1.ManagerID = cte.EmployeeID)

    -- Return results

    INSERT INTO @TREE

    SELECT *

    FROM   cteSubTree

    -- IF NOTE SUPERVISOR, RETURN AN EMPTY SET (TABLE)

    IF (SELECT COUNT(* )  FROM   @TREE) = 1

      DELETE @TREE

    

    RETURN

  END



GO



-- Test UDF - user-defined function

-- Generate orgchart for Marketing, David Bradley manager

SELECT EmpID = EmployeeID,

       EmpName = EmployeeName,

       Department,

       MgrID = ManagerID,

       MgrName = ManagerName,

       SubLvl = SubTreeLevel

FROM   dbo.fnSupervisorySubTree(6)

GO

/* Results



EmpID EmpName           Department  MgrID MgrName           SubLvl

6     Bradley, David    Marketing   109   Sánchez, Ken      0

2     Brown, Kevin      Marketing   6     Bradley, David    1

46    Harnpa…, Sariya   Marketing   6     Bradley, David    1

106   Gibson, Mary      Marketing   6     Bradley, David    1

119   Williams, Jill    Marketing   6     Bradley, David    1

203   Eminhizer, Terry  Marketing   6     Bradley, David    1

269   Benshoof, Wanida  Marketing   6     Bradley, David    1

271   Wood, John        Marketing   6     Bradley, David    1

272   Dempsey, Mary     Marketing   6     Bradley, David    1

*/



-- CROSS APPLY returns no row for staff Mary Gibson

-- SQL cross apply

SELECT Employee = e.StaffName,

       e.Title,

       oc.Department,

       Staff = oc.EmployeeName,

       Supervisor = oc.ManagerName

FROM   Employee e

       CROSS APPLY dbo.fnSupervisorySubTree(e.EmployeeID) AS oc

WHERE  e.EmployeeID = 106

/*

Employee          Title                   Department  Staff Supervisor

*/



-- OUTER APPLY returns a row for staff Mary Gibson

-- SQL outer apply

SELECT   Employee = e.StaffName,

         e.Title,

         oc.Department,

         Staff = oc.EmployeeName,

         Supervisor = oc.ManagerName

FROM     Employee e

         OUTER APPLY dbo.fnSupervisorySubTree(e.EmployeeID) AS oc

WHERE    e.EmployeeID = 106

GO

/*

Employee          Title                   Department  Staff Supervisor

Gibson, Mary      Marketing Specialist    NULL        NULL  NULL

*/
             

-- Cleanup

DROP TABLE tempdb.dbo.Employee

DROP FUNCTION dbo.fnSupervisorySubTree

GO

Thursday, December 29, 2011

Concurrency Control with rowversion

Pessimistic concurrency means locking the data at the row, page, or table level and don't allow anyone to modify it until the target user is done modifying and saving it back to the database. Trouble with this method: it may take a few minutes for the target user to update a record during which other users may be prevented from doing their work (locked out from the table). If the target user called away for a meeting for example in the middle of data entry, you need to unlock the table by a timeout mechanism in order to prevent disruption to data access by other users.
Optimistic concurrency means reading a record in a table and displaying it for the target user, but not locking it. Other users can read and modify the record at anytime while the target user is performing the manual update on the computer screen. When the target user releases the record for database update you need to check if someone changed it in between the initial read and the release (like 1-5 minutes). Usually this is not a problem due to the work distribution among staff, nevertheless you have to program for it to avoid conflicting updates and damage to database integrity.
Assume you are a developer and developing a program in Visual Basic to update the name and address table of customers. There will be 100 staff member who can perform this application function. How can you be sure that while target staff A typing in the change, staff X is not changing the same row?
Here is what you do:     


1. Read the name and address table including the timestamp. You display the info to the user for update and save the timestamp.
2. Certain amount of time later, like 2 minutes, the user presses the submit button after changes were typed in.
3. You open a transaction with Begin Transaction
4. You read the timestamp of the name and address row
5. You compare the current timestamp to the saved timestamp.
6. If the timestamps are same, you update the row and commit the transaction
7. If timestamps are different, you roll back the transaction and notify the user about the fact that the data was changed by someone else. You can let the user decide what to do or follow the appropriate company business rule for data entry conflict resolution.

This is pretty common practice in multi user environment. The alternate would be to examine a datetime column, or the entire row which is more processing intensive.
The following example shows timestamp (rowversion in SQL Server 2008) in action:
-- SQL Server 2008 T-SQL Code
USE tempdb;  
-- SQL create table for Concurrency Checking demo

CREATE TABLE Celebrity (
  CelebrityID INT    IDENTITY    PRIMARY KEY,
  FirstName   VARCHAR(25),
  LastName    VARCHAR(30),
  VERSIONSTAMP  ROWVERSION)
GO
           
-- SQL insert - populate table
INSERT Celebrity (FirstName, LastName)
VALUES
('Jessica', 'Simpson'),
('Nick', 'Carter'),
('Stevie', 'Brock'),
('Christina', 'Aguilera'),
('Frank','Sinatra'),
('Doris','Day'),
('Elvis', 'Presley')
GO            

SELECT * FROM Celebrity
GO 
/* Results
CelebrityID FirstName   LastName    VERSIONSTAMP
1           Jessica     Simpson     0x0000000000000876
2           Nick        Carter      0x0000000000000877
3           Stevie      Brock       0x0000000000000878
4           Christina   Aguilera    0x0000000000000879
5           Frank       Sinatra     0x000000000000087A
6           Doris       Day         0x000000000000087B
7           Elvis       Presley     0x000000000000087C
*/
           
-- SQL update demo: SOMEONE UPDATED RECORD since it was read

CREATE TABLE #Semaphore (ID int identity(1,1) primary key,
                          StartVersion bigint,
                          PK int)
DECLARE @MyKey int

INSERT INTO #Semaphore (StartVersion, PK)
SELECT  VERSIONSTAMP, 1
FROM Celebrity WHERE CelebrityID=1

SELECT @MyKey = SCOPE_IDENTITY() 

-- SIMULATION: somebody else updating the same record

UPDATE Celebrity
SET    FirstName = 'Celine',
       LastName = 'Dion'
WHERE  CelebrityID = 

-- We are attempting to update.

BEGIN TRANSACTION

IF (SELECT StartVersion
    FROM   #Semaphore
    WHERE  ID = @MyKey) = (SELECT VERSIONSTAMP
                           FROM   Celebrity
                           WHERE  CelebrityID = 1)

  BEGIN
    UPDATE Celebrity
    SET    FirstName = 'Lindsay',
           LastName = 'Lohan'
    WHERE  CelebrityID =   

    COMMIT TRANSACTION
  END
ELSE 
     BEGIN
    ROLLBACK TRANSACTION
    PRINT 'ROLLBACK - UPDATE CONFLICT'
    RAISERROR ('Celebrity update conflict.',10,0)
  END

DELETE #Semaphore WHERE ID = @MyKey
SELECT * FROM   Celebrity

GO
/* CelebrityID    FirstName   LastName    VERSIONSTAMP
1           Celine      Dion        0x000000000000087D
2           Nick        Carter      0x0000000000000877
3           Stevie      Brock       0x0000000000000878
4           Christina   Aguilera    0x0000000000000879
5           Frank       Sinatra     0x000000000000087A
6           Doris       Day         0x000000000000087B
7           Elvis       Presley     0x000000000000087C
*/

-- SQL UPDATE with NO CONFLICT
DECLARE @MyKey int

INSERT INTO #Semaphore (StartVersion, PK)
SELECT  VERSIONSTAMP, 1
FROM Celebrity WHERE CelebrityID=1

SELECT @MyKey = SCOPE_IDENTITY() 

-- We are trying to update.
BEGIN TRANSACTION

IF (SELECT StartVersion
    FROM   #Semaphore
    WHERE  ID = @MyKey) = (SELECT VERSIONSTAMP
                           FROM   Celebrity
                           WHERE  CelebrityID = 1)
  BEGIN

    UPDATE Celebrity
    SET    FirstName = 'Lindsay',
           LastName = 'Lohan'
    WHERE  CelebrityID =  

    COMMIT TRANSACTION

 END
ELSE
  BEGIN
    ROLLBACK TRANSACTION
    PRINT 'ROLLBACK - UPDATE CONFLICT'
    RAISERROR ('Celebrity update conflict.',10,0)
  END

DELETE #Semaphore WHERE ID = @MyKey
SELECT * FROM   Celebrity
GO
/*
CelebrityID FirstName   LastName    VERSIONSTAMP
1           Lindsay     Lohan       0x000000000000087E
2           Nick        Carter      0x0000000000000877
3           Stevie      Brock       0x0000000000000878
4           Christina   Aguilera    0x0000000000000879
5           Frank       Sinatra     0x000000000000087A
6           Doris       Day         0x000000000000087B
7           Elvis       Presley     0x000000000000087C
*/

-- Cleanup
DROP TABLE #Semaphore
DROP TABLE Celebrity


CROSS APPLY

CROSS APPLY is quite simple: a restricted "INNER JOIN" between a table (outer query) and a table-valued function (common usage), or derived table from correlated subquery. The table-valued function is evaluated only for the paramater values supplied by the outer query.

Adventure Works 2008 Database:

CREATE FUNCTION fnRange ( @Start int, @End int)
RETURNS TABLE AS
RETURN (SELECT * FROM (SELECT SEQ=ROW_NUMBER() OVER (ORDER BY (SELECT 1))
    FROM   MASTER.dbo.spt_values a
           -- CROSS JOIN MASTER.dbo.spt_values b -- uncomment for more range
           ) x
           WHERE SEQ BETWEEN @Start AND @End)
GO

;WITH CTE (Color, MinID, MaxID) AS
 (SELECT Color, MIN(ProductID), MAX(ProductID)
  FROM Production.Product
  WHERE Color is not null  GROUP BY Color),
  cteSEQ AS (SELECT Color, SEQ FROM CTE
             CROSS APPLY  dbo.fnRange (MinID, MaxID) as R)
 SELECT  *, P.Color, I.SEQ
 FROM cteSEQ I LEFT JOIN Production.Product P
   ON I.SEQ = P.ProductID AND I.Color = P.Color
      AND P.Color is not null
   WHERE P.ProductID is  null
 ORDER BY I.Color, I.SEQ

------------
-- T-SQL column aliasing with CROSS APPLY
------------
SELECT TranID, ProdID, Qty, Cost, TotalCost=Qty * Cost
FROM AdventureWorks2008.Production.TransactionHistory
 CROSS APPLY
 (SELECT TranID = TransactionID
        ,ProdID = ProductID
        ,Qty = Quantity
        ,Cost = ActualCost ) x
 WHERE Qty > 10 and Cost > 0.0
 ORDER BY TranID, ProdID
 /* TranID  ProdID      Qty   Cost        TotalCost
100154      864         14    41.275      577.85
100157      869         24    45.4935     1091.844
100198      869         16    45.4935     727.896
....
*/

-- CROSS APPLY using GROUP BY derived table from correlated subquery
------------
USE AdventureWorks;
DECLARE  @Year  INT,         @Month INT
SET @Year = 2003;
SET @Month = 2
-- SQL cross apply - SQL group by - SQL correlated subquery
SELECT [Customer] = s.Name,
       -- Special money data type currency formatting option
       [Total$ Sales] = '$' +
        Convert(VARCHAR,Convert(MONEY,SalesAmount.OrderTotal),1)
FROM     Sales.Customer AS c
         -- The customer name is in this table
         INNER JOIN Sales.Store AS s
           ON s.CustomerID = c.CustomerID
         -- The inner query is a correlated GROUP BY subquery
         CROSS APPLY (SELECT   soh.CustomerId,
                               Sum(sod.LineTotal) AS OrderTotal
                      FROM     Sales.SalesOrderHeader AS soh
                               INNER JOIN Sales.SalesOrderDetail AS sod
                                 ON sod.SalesOrderId = soh.SalesOrderId
                      -- This is the correlation to the outer query
                     WHERE soh.CustomerId = c.CustomerId
                               -- Filter data
                               AND Year(OrderDate) = @Year
                               AND Month(OrderDate) = @Month
                      GROUP BY soh.CustomerId) AS SalesAmount
ORDER BY [Customer]
GO
/* Partial results
(132 row(s) affected)
Customer                      Total$ Sales
Ace Bicycle Supply            $647.99
Affordable Sports Equipment   $50,953.32
Alpine Ski House              $939.59
Basic Sports Equipment        $159.56
Bicycle Lines Distributors    $22,243.33
*/

/**** OUTER APPLY results: (701 row(s) affected) ********/
------------
-- CROSS APPLY using derived table from correlated subquery
------------
-- Create and populate tables for testing
USE tempdb;
GO
SET NOCOUNT ON;
CREATE TABLE Account (
  AccountID   INT    IDENTITY    PRIMARY KEY,
  AccountName VARCHAR(50),
  CreateDate  SMALLDATETIME    DEFAULT (CURRENT_TIMESTAMP),
  IsActive    BIT    DEFAULT (1))
GO
CREATE TABLE Deposit (
  DepositId   INT    IDENTITY    PRIMARY KEY,
  AccountID       INT,
  Amount          MONEY,
  DepositDate SMALLDATETIME    DEFAULT (getdate()))

GO
INSERT Account(AccountName) VALUES ('Charles Mills')
INSERT Account(AccountName) VALUES ('Miranda Vegas')
INSERT Account(AccountName) VALUES ('Corner Hardware')
INSERT Account(AccountName) VALUES ('Laptop Land')
INSERT Account(AccountName) VALUES ('Cellphone City')
SELECT * FROM Account
/*
AccountID   AccountName       CreateDate              IsActive
1           Charles Mills     2015-01-25 08:38:00     1
2           Miranda Vegas     2015-01-25 08:38:00     1
3           Corner Hardware   2015-01-25 08:38:00     1
4           Laptop Land       2015-01-25 08:38:00     1
5           Cellphone City    2015-01-25 08:38:00     1
*/

INSERT Deposit (AccountID, Amount) VALUES(1, 1400.0)
INSERT Deposit (AccountID, Amount) VALUES(1, 1200.0)
INSERT Deposit (AccountID, Amount) VALUES(1, 1300.0)
INSERT Deposit (AccountID, Amount) VALUES(1, 1100.0)
INSERT Deposit (AccountID, Amount) VALUES(2, 400.0)
INSERT Deposit (AccountID, Amount) VALUES(2, 200.0)
INSERT Deposit (AccountID, Amount) VALUES(2, 300.0)
INSERT Deposit (AccountID, Amount) VALUES(2, 900.0)
INSERT Deposit (AccountID, Amount) VALUES(3, 33400.0)
INSERT Deposit (AccountID, Amount) VALUES(3, 11200.0)
INSERT Deposit (AccountID, Amount) VALUES(3, 22300.0)
INSERT Deposit (AccountID, Amount) VALUES(3, 12100.0)
GO
 -- CROSS APPLY returns only matching data - SQL cross apply
-- Cross apply T-SQL - Cross apply mssql - derived table - SQL correlated subquery
SELECT   a.AccountName,
         TopDeposit=tt.Amount
FROM     ACCOUNT a
CROSS APPLY (SELECT   TOP ( 1 ) Amount
             FROM     Deposit t
             WHERE    t.AccountID = a.AccountID
             ORDER BY Amount DESC) tt
ORDER BY AccountName
/*
AccountName       TopDeposit
Corner Hardware   33400.00
Miranda Vegas     900.00
Charles Mills     1400.00
*/

-- Cleanup
DROP TABLE tempdb.dbo.Account
DROP TABLE tempdb.dbo.Deposit
------------
In the following example, the table-valued inline function returns the top 5 (highest TotalDue) orders for a store or individual customer, provided there are 5 orders. The SELECT query itself is restricted to stores. CROSS APPLY joins the store information with the top 5 orders information produced by the table-valued function.
USE AdventureWorks;
GO
-- SQL inline function
-- User-defined inline function
CREATE FUNCTION Sales.fnTopNOrders (
      @CustomerID AS INT,
      @n AS INT )
RETURNS TABLE
AS
RETURN
SELECT
      TOP(@n) SalesOrderID,
      ShipDate = convert(char(10), ShipDate,112),
-- SQL currency formatting
      TotalDue=convert(varchar,TotalDue,1)
FROM AdventureWorks.Sales.SalesOrderHeader
WHERE CustomerID = @CustomerID
ORDER BY TotalDue DESC
GO
 -- SQL cross apply - SQL Server cross apply
SELECT
      StoreName=s.Name,
      [Top].ShipDate,
      [Top].SalesOrderID,
      TotalDue='$'+[Top].TotalDue
FROM AdventureWorks.Sales.Store AS s
JOIN AdventureWorks.Sales.Customer AS c
ON s.CustomerID = c.CustomerID
 CROSS APPLY
 AdventureWorks.Sales.fnTopNOrders(c.CustomerID, 5) AS [Top]
WHERE CustomerType='S'
ORDER BY StoreName, convert(money,TotalDue) DESC
GO

Partial result set:
StoreName  ShipDate  SalesOrderID  TotalDue
A Bike Store  20020208  45283  $37,643.14
A Bike Store  20020508  46042  $34,722.99
A Bike Store  20011108  44501  $26,128.87
A Bike Store  20010808  43860  $14,603.74
A Great Bicycle Company  20010908  44125  $3,450.98
A Great Bicycle Company  20020308  45569  $2,828.58
A Great Bicycle Company  20011208  44793  $2,828.58
A Great Bicycle Company  20030308  49537  $622.95
A Great Bicycle Company  20031208  59009  $50.77
A Typical Bike Shop  20020608  46343  $39,156.33
A Typical Bike Shop  20011208  44755  $37,725.60
The CROSS APPLY operator is frequently used in joining DMVs with DMFs (Dynamic Management Views with Dynamic Management Functions). In the following examples the sys.dm_exec_sql_text DMF returns the source text for the cached plans and queries like CREATE PROCEDURE, SELECT..., CREATE VIEW, etc..
-- SQL cross apply - Cross apply mssql
-- SQL dynamic management view - dmv - SQL dynamic management function - dmf
SELECT   LastExecutionTime = max(last_execution_time),
         Query = Text
FROM     sys.dm_exec_query_stats AS eqs
CROSS APPLY sys.dm_exec_sql_text(eqs.sql_handle) AS SQL
WHERE    Text LIKE ('%AdventureWorks%')
         AND Text NOT LIKE ('%fullText%')
GROUP BY Text
ORDER BY Query
GO
/* Partial results

LastExecutionTime             Query
2009-01-24 09:42:23.217       SELECT EmployeeID, StaffName = LastName+',.... 
2009-01-24 10:31:49.170       SELECT e.EmployeeID, e.Title, StaffName = ....
*/
-- SQL Server cross apply
SELECT
      PlanSource=sql.text,
      plans.*
FROM  sys.dm_exec_cached_plans plans
CROSS APPLY  sys.dm_exec_sql_text (plans.plan_handle) AS sql

The following CROSS APPLY query returns sql source and information about all executing requests within SQL Server:
-- SQL cross apply
SELECT
      RequestSource = sql.text,
      er.*
FROM
sys.dm_exec_requests er
CROSS APPLY
sys.dm_exec_sql_text(sql_handle) AS sql
GO
 The following CROSS APPLY example script creates the Employee table in tempdb with select into from HumanResources tables in AdventureWorks. To be used with CROSS APPLY, the T-SQL script creates a table-valued orginazational chart subtree function by applying recursive CTE. The new tree-processing UDF is used in CROSS APPLY queries:

USE tempdb;
GO
-- SQL select into create table - SQL inner join
SELECT      e.EmployeeID, e.Title, StaffName = LastName+', '+FirstName,
            ManagerID, Department=d.Name
INTO Employee
FROM AdventureWorks.HumanResources.Employee e
INNER JOIN AdventureWorks.HumanResources.EmployeeDepartmentHistory edh
      ON e.EmployeeID = edh.EmployeeID
INNER JOIN AdventureWorks.HumanResources.Department d
      ON edh.DepartmentID = d.DepartmentID
INNER JOIN AdventureWorks.Person.Contact c
      ON e.ContactID = c.ContactID
WHERE edh.EndDate is NULL
GO
-- SELECT * FROM Employee
-- SQL table-valued function - user-defined function UDF - Tree processing function
CREATE FUNCTION fnOrgChartSubTree(@EmployeeID AS INT)
    RETURNS @TREE TABLE (
             EmployeeID             INT
            ,EmployeeName           VARCHAR(35)
            ,Department             VARCHAR(30)
            ,ManagerID              INT
            ,ManagerName            VARCHAR(35)
            ,OrgChartLevel          INT )
AS
BEGIN
-- SQL common table expression - CTE - SQL recursive CTE
  WITH cteOrgChartSubTree(EmployeeID, EmployeeName, Department,
                  ManagerID, ManagerName, OrgChartLevel)
  AS
  (
    -- Anchor (root) node
    SELECT e1.EmployeeID, e1.StaffName, e1.Department,
           e1.ManagerID, e2.StaffName, 0
    FROM Employee e1
    LEFT JOIN Employee e2
    ON (e1.ManagerID = e2.EmployeeID)
    WHERE e1.EmployeeID = @EmployeeID
    UNION ALL
    -- Recursive nodes to leaf level
    SELECT  e1.EmployeeID, e1.StaffName, e1.Department,
                  e1.ManagerID, e2.StaffName, cte.OrgChartLevel+1
    FROM Employee e1
    INNER JOIN Employee e2
            ON e1.ManagerID = e2.EmployeeID
    JOIN cteOrgChartSubTree AS cte
        ON e1.ManagerID = cte.EmployeeID
  )
-- Return results
  INSERT INTO @TREE
  SELECT * FROM cteOrgChartSubTree;
  RETURN
END
GO

-- Find CEO
SELECT CEO=StaffName, CEOid =EmployeeID
FROM Employee
WHERE ManagerID is NULL
GO
/* Results
CEO                     CEOid
Sánchez, Ken            109
*/

-- Test UDF - user-defined function
-- Generate orgchart starting with CEO
SELECT EmpID = EmployeeID, EmpName=EmployeeName, Department,
MgrID=ManagerID, MgrName=ManagerName, OCLvl=OrgChartLevel
FROM dbo.fnOrgChartSubTree(109)
GO
/* 290 rows - Partial results
EmpID EmpName           Department  MgrID MgrName           OCLvl
109   Sánchez, Ken      Executive   NULL  NULL              0
6     Bradley, David    Marketing   109   Sánchez, Ken      1
12    Duffy, Terri      Engineering 109   Sánchez, Ken      1
42    Trenary, Jean     Informa…    109   Sánchez, Ken      1
140   Norman, Laura     Executive   109   Sánchez, Ken      1
148   Hamilton, James   Production  109   Sánchez, Ken      1
273   Welcker, Brian    Sales       109   Sánchez, Ken      1
268   Jiang, Stephen    Sales       273   Welcker, Brian    2
284   Alberts, Amy      Sales       273   Welcker, Brian    2
*/

-- Test UDF for supervisor Miller, Dylan
SELECT EmpID = EmployeeID, EmpName=EmployeeName, Department,
MgrID=ManagerID, MgrName=ManagerName, OCLvl=OrgChartLevel
FROM dbo.fnOrgChartSubTree(158)
GO
/* Results
   R & D = Research and Development
  EmpID EmpName           Department  MgrID MgrName           OCLvl
158   Miller, Dylan     R & D       3     Tamburello…       0
79    Margheim, Diane   R & D       158   Miller, Dylan     1
114   Matthew, Gigi     R & D       158   Miller, Dylan     1
217   Raheem, Michael   R & D       158   Miller, Dylan     1
*/
 -- Test UDF for staff Margheim, Diane (leaf level on orgchart tree)
SELECT EmpID = EmployeeID, EmpName=EmployeeName, Department,
MgrID=ManagerID, MgrName=ManagerName, OCLvl=OrgChartLevel
FROM dbo.fnOrgChartSubTree(79)
GO
/* Results

EmpID EmpName           Department        MgrID       MgrName           OCLvl
79    Margheim, Diane   Research and...   158         Miller, Dylan     0
*/
 -- Get top-level executives - Level 1 when root is  CEO
-- SQL select into create temporary table - SQL cross apply
SELECT ExecName=oc.EmployeeName, oc.EmployeeID, oc.Department
INTO #EXECS
FROM Employee e
CROSS APPLY dbo.fnOrgChartSubTree(e.EmployeeID) AS oc
WHERE e.EmployeeID = 109
and oc.OrgChartLevel=1;
SELECT * FROM #EXECS
GO
/* Results

ExecName          EmployeeID  Department
Bradley, David    6           Marketing
Duffy, Terri      12          Engineering
Trenary, Jean     42          Information Services
Norman, Laura     140         Executive
Hamilton, James   148         Production
Welcker, Brian    273         Sales
*/

-- Get orgchart by executives
-- Note: AdventureWorks database does not have Department Manager info
-- SQL cross apply - SQL IN operator
SELECT      Executive=e.StaffName, e.Title, oc.Department,
            Staff=oc.EmployeeName, Supervisor = oc.ManagerName
FROM Employee e
CROSS APPLY dbo.fnOrgChartSubTree(e.EmployeeID) AS oc
WHERE e.EmployeeID IN (Select EmployeeID FROM #EXECS)
ORDER by Executive, Department, Supervisor, Staff
GO
/* 289 rows CEO Ken Sanchez not included - Partial results
   VPS = Vice President of Sales
   WB = Welcker, Brian
   Dept = Department
 
Executive   Title Dept  Staff                   Supervisor
WB          VPS   Sales Tsoflias, Lynn          Abbas, Syed
WB          VPS   Sales Pak, Jae                Alberts, Amy
WB          VPS   Sales Valdez, Rachel          Alberts, Amy
WB          VPS   Sales Varkey C.., Ranjit      Alberts, Amy
WB          VPS   Sales Ansman-W.., Pamela      Jiang, Stephen
WB          VPS   Sales Blythe, Michael         Jiang, Stephen
WB          VPS   Sales Campbell, David         Jiang, Stephen
WB          VPS   Sales Carson, Jillian         Jiang, Stephen
WB          VPS   Sales Ito, Shu                Jiang, Stephen
WB          VPS   Sales Mensa-A.., Tete         Jiang, Stephen
WB          VPS   Sales Mitchell, Linda         Jiang, Stephen
WB          VPS   Sales Reiter, Tsvi            Jiang, Stephen
WB          VPS   Sales Saraiva, José           Jiang, Stephen
WB          VPS   Sales Vargas, Garrett         Jiang, Stephen
WB          VPS   Sales Welcker, Brian          Sánchez, Ken
WB          VPS   Sales Abbas, Syed             Welcker, Brian
WB          VPS   Sales Alberts, Amy            Welcker, Brian
WB          VPS   Sales Jiang, Stephen          Welcker, Brian
*/

-- Cleanup
DROP TABLE tempdb.dbo.Employee
DROP TABLE #EXECS
GO

In the following sql CROSS APPLY example, first we create a function (UDF) to get the total sales for a bike store. Second, we use CROSS APPLY to get the sales figures for the early dealers (CustomerID < 100) of AdventureWorks Cycles. Naturally, we can achieve the same results in a single complex query which may even be faster. The CROSS APPLY advantage appears when the user-defined function is used in several queries: developer productivity gain. 

-- SQL create function
USE AdventureWorks;
GO
CREATE FUNCTION dbo.fnGetTotalSalesByCustomer(@CustID  int)
  RETURNS TABLE
AS
RETURN
  SELECT    Store = s.Name,
                  TotalSales = '$'+convert(varchar,TotalSales,1)
  FROM
  (
      SELECT CustomerID = @CustID, TotalSales=sum(SubTotal)
      FROM Sales.SalesOrderHeader
      WHERE CustomerID =@CustID
  ) soh
  INNER JOIN Sales.Store s
  ON soh.CustomerID = s.CustomerID
 GO

-- SQL cross apply use - SQL Server cross apply
SELECT  Store, TotalSales
FROM Sales.Customer AS c
CROSS APPLY dbo.fnGetTotalSalesByCustomer (c.CustomerID) tsc
WHERE c.CustomerID < 100
ORDER BY Store
GO

/* Partial results

Store                                     TotalSales
A Bike Store                              $102,351.80
Advanced Bike Components                  $433,942.38
Aerobic Exercise Company                  $3,301.21
Associated Bikes                          $9,384.45
Bicycle Exporters                         $37,684.82
Bicycle Warehouse Inc.                    $7,959.01
Bike World                                $112,601.32
*/
In the following example we calculate financial statistics using Aggregate Functions for each dealer which sells AdventureWorks mountain bikes and associated products.
Here is the listing:
USE tempdb

GO

-- drop FUNCTION dbo.fnOrderFingerprint
CREATE FUNCTION dbo.fnOrderFingerprint
               (@CustomerID AS INT)
RETURNS TABLE
AS
  RETURN
    SELECT Label = 'Maximum $',
           TotalDue = max(TotalDue)
    FROM   AdventureWorks.Sales.SalesOrderHeader
    WHERE  CustomerID = @CustomerID
    UNION
    SELECT Label = 'Average $',
           TotalDue = avg(TotalDue)
    FROM   AdventureWorks.Sales.SalesOrderHeader
    WHERE  CustomerID = @CustomerID
    UNION
    SELECT Label = 'Minimum $',
           TotalDue = min(TotalDue)
    FROM   AdventureWorks.Sales.SalesOrderHeader
    WHERE  CustomerID = @CustomerID
    UNION
    SELECT Label = 'Order Count',
           TotalDue = count(TotalDue)
    FROM   AdventureWorks.Sales.SalesOrderHeader
    WHERE  CustomerID = @CustomerID
    UNION
    SELECT Label = 'Standard Deviation $',
           TotalDue = stdev(TotalDue)
    FROM   AdventureWorks.Sales.SalesOrderHeader
    WHERE  CustomerID = @CustomerID

GO
 SELECT   Customer = S.Name,
         F.Label,
         [Total Due] = left(convert(VARCHAR,convert(MONEY,F.TotalDue),1),
                            len(convert(VARCHAR,convert(MONEY,F.TotalDue),1)) - 3)
FROM     AdventureWorks.Sales.Store AS S
         JOIN AdventureWorks.Sales.Customer AS C
           ON S.CustomerID = C.CustomerID
         CROSS APPLY tempdb.dbo.fnOrderFingerprint(C.CustomerID) AS F
ORDER BY Customer ASC,
         Label DESC
GO

Queries that are recently Run

SELECT deqs.last_execution_time AS LastRun, dest.TEXT AS QueryText, *
FROM sys.dm_exec_query_stats AS deqs
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest
WHERE LEFT(dest.TEXT,8)='SELECT *'
ORDER BY LastRun DESC

principles of query optimization

The principles of query optimization are quite simple:

1. Each FOREIGN KEY and WHERE clause predicate column should be indexed (PRIMARY KEY is indexed automatically).

2. Example for other indexing candidates: GROUP BY column in frequent/business critical query.

3. Use FILLFACTOR for dynamic tables; example FILLFACTOR 80 if table will grow 10% during the week (requires experimentation); FILLFACTOR 80 leaves 20% empty space for growth. Similar consideration for very frequent variable length column UPDATE.

4. Assign clustered index (PK default is clustered index, but not a requirement.) to a column which is used in business critical range searches.

5. REBUILD indexes every weekend. Update statistics for all tables in all databases every night.

6. Program WHERE clauses with SARGABLE predicates; don't use dateadd(dd,1,OrderDate) for example because that formulation will prevent the database engine from performing an INDEX SEEK on the (indexed) OrderDate column.

7 . For sizable temporary storage use temporary tables (#tblA) instead of table variables (@tblA).

8. Examine the query/sproc to streamline it; eliminate potential overhead.

9. Examine the execution plan for ways to improve the query.

With the exception of 8 and 9, all the optimization steps can be carried out without being a super-expert in T-SQL. That would roughly take care of 99% of performance problems. Before you go down looking at "esoteric" (for experts only) server / database performance related data, just make sure you did the basics. One bad query can bring down a mighty SS to its knees! Generating tons of performance data along the way. An expert of course can quickly sort out things and find the offending query. However, for everybody including the expert, it is simpler and safer to start with optimization basics.