Labels

Showing posts with label SQL Server. Show all posts
Showing posts with label SQL Server. Show all posts

Tuesday, July 2, 2013

Tables having same column names

SELECT C.Name AS ColumnName
,  STUFF(
   (SELECT ',' + O1.Name
   FROM SYS.OBJECTS O1
   JOIN SYS.Columns C1 ON O1.object_id = C1.object_id
   WHERE C1.Name = C.Name
   FOR XML PATH('')),1,1,'') AS TableNames
FROM
SYS.OBJECTS O
JOIN SYS.Columns C ON O.object_id = C.object_id
JOIN SYS.Types T ON C.user_type_id = T.user_type_id
WHERE O.TYPE = 'U' AND C.Name like '%dead%'
GROUP BY C.Name
ORDER BY C.Name

Wednesday, April 3, 2013

HASH BYTES Enhanced

-- HASH BYTES Enhanced

--Before SQL Server 2012, HashBytes function used to support following algorithms while encrypting a string.

--MD2,MD4,MD5,SHA and SHA1

--Now In SQL Server 2012, 2 more algorithms "SHA2256" and "SHA2512" has been added.

SELECT HASHBYTES('SHA2_256', 'Anitha Saradhi') AS 'SHA2256'
SELECT HASHBYTES('SHA2_512', 'Anitha Saradhi') AS 'SHA2512'

FORCESEEK Enhancement


-- FORCESEEK hint is extended to specify columns used to seek in the specified index

/*
Index Scan retrieves all the rows from the table. Index Seek retrieves selective rows from the table.

Index Scan:
Since a scan touches every row in the table whether or not it qualifies, the cost is proportional to the total number of rows in the table.
Thus, a scan is an efficient strategy if the table is small or if most of the rows qualify for the predicate.

Index Seek:
Since a seek only touches rows that qualify and pages that contain these qualifying rows,
the cost is proportional to the number of qualifying rows and pages rather than to the total number of rows in the table.

In general query optimizer tries to use an Index Seek which means that optimizer has found a useful index to retrieve recordset.
But if it is not able to do so either because there is no index or no useful indexes on the table then SQL Server has to scan all the records that satisfy query condition.

When SQL Server chooses seek over scan while retrieving record details, it will compare key value with search input, based on comparison result,
Query engine will move to appropriate page.
Suppose, index has multiple columns, if we don't want all key columns to consider, in SQL Server 2012, we can mention the index columns to consider when the index has multiple key columns.
*/

SELECT CompanyID,CompanyName,Amount
FROM COMPANIES
WITH (FORCESEEK(Idx_Company(CompanyID)))
WHERE CompanyID = 1

Forcescan

-- FORCESCAN Hint added to force query optimizer to choose scan over seek

 /* Often while tuning stored procedures, we will see tables have the "seek" operator.
 Few times one of reason for slow performance is wrong estimates on number of rows.
 Because of that, there are more chances that query optimizer choose "SEEK" over "SCAN".
 To force optimizer to choose "SCAN" over "SEEK", FORCESCAN hint has been added. */

Select OH.OrderID,OD.ItemName,OH.OrderCost
from OrderHeader OH
INNER JOIN OrderDetails OD WITH (FORCESCAN)
on OH.OrderID = OD.OrderID

File Tables



--select value , value_in_use  from sys.configurations  where name like 'filestream access level'


--EXEC sp_configure filestream_access_level, 2
--RECONFIGURE

USE [master]
GO

CREATE DATABASE SQLDocumentStoreDB
ON PRIMARY
(NAME = SQLDocumentStoreDB
,FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL11.SQLSERVER2012\MSSQL\DATA\SQLDocumentStoreDB.mdf'),
FILEGROUP FileStreamFG CONTAINS FILESTREAM
(NAME = SQLDocumentStoreFileTable
,FILENAME = 'C:\FileStreamShare\DocumentStore' ) --Folder location
LOG ON
(NAME = SQLDocumentStoreDB_Log
,FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL11.SQLSERVER2012\MSSQL\Log\SQLDocumentStoreDB_Log.ldf')
WITH FILESTREAM
--Gives full non-transactional access to the share/ directory
(NON_TRANSACTED_ACCESS = FULL
,DIRECTORY_NAME = N'DocumentStore');
GO


USE [SQLDocumentStoreDB]
GO

CREATE TABLE MyDocuments AS FILETABLE
WITH (FileTable_Directory = 'MyDocumentStore');

--INSERT INTO MyDocuments(name,is_directory)
-- values('Docs',1)
-- INSERT INTO MyDocuments(name,is_directory)
-- values('Multimedia',1)

/****** Script for SelectTopNRows command from SSMS  ******/
SELECT * FROM [SQLDocumentStoreDB].[dbo].[MyDocuments]

USE [master]
GO

SELECT DB_NAME(Database_id) AS [Database]
,[database_id] AS [DatabaseID]
,[non_transacted_access] AS [NonTransactedAccess]
,[non_transacted_access_desc] AS [NonTransactedAccessDesc]
,[directory_name] AS [DirectoryName]
FROM [sys].[database_filestream_options]
WHERE DB_NAME(Database_id) = 'SQLDocumentStoreDB'

File Growth Error & its resolution


/*Error Message:

Msg 5169, Level 16, State 1, Line 1
FILEGROWTH cannot be greater than MAXSIZE for file ‘NewDB’.
Creating Scenario: */

CREATE DATABASE [NewDB]
ON PRIMARY
(NAME = N'NewDB',
FILENAME = N'D:\NewDB.mdf' ,
SIZE = 4096KB,
FILEGROWTH = 1024KB,
MAXSIZE = 4096KB)
LOG ON
(NAME = N'NewDB_log',
FILENAME = N'D:\NewDB_log.ldf',
SIZE = 1024KB,
FILEGROWTH = 10%)
GO

--Now let us see what exact command was creating error for him.

USE [master]
GO
ALTER DATABASE [NewDB]
MODIFY FILE ( NAME = N'NewDB', FILEGROWTH = 1024MB )
GO

/* Workaround / Fix / Solution:
The reason for the error is very simple. He was trying to modify the filegrowth to much higher value than the maximum file size
specified for the database. There are two way we can fix it. */

--Method 1: Reduces the filegrowth to lower value than maxsize of file

USE [master]
GO
ALTER DATABASE [NewDB]
MODIFY FILE ( NAME = N'NewDB', FILEGROWTH = 1024KB )
GO

--Method 2: Increase maxsize of file so it is greater than new filegrowth

USE [master]
GO
ALTER DATABASE [NewDB]
MODIFY FILE ( NAME = N'NewDB', FILEGROWTH = 1024MB, MAXSIZE = 4096MB)
GO

New in SSMS 2012

-- 1. CODE SNIPPET:

press "Ctrl+k,Ctrl+x"

To add a new code snippet:

    Create the snippet file with .snippet extension(Its advisable to take existing snippet file and modify it)
    Go to Tools -> Code Snippets Manager
    Select the folder, under which you wants to add code snippet and click on Add button and select the snippet file.

-- 2. TASK LIST : GO TO VIEW -> TASK LIST
    We often use some external tools to manage our task list, where we will add our tasks and will follow the tasks according to the task list.
    Now with SQL Server 2012, external tool is no longer required.

-- 3. Zoom/Magnify feature helps in presentations

-- 4. Multi Monitor Support:

    Until SQL Server 2012, in SSMS, we were able to open only one query window. 2 or more query windows can't share the same screen.
    Only one query window can be visible on the screen.

    Now SQL Server 2012 SSMS provides the option to open multiple query windows at a time.
    Easily we can drag the query window and can place that query window where ever we want.

-- 5. Debugging Enhancements:
        Before SQL Server 2012, SSMS already provided a feature to debug the queries.
        In SQL Server 2012, this has been enhanced more to provide more rich experience.

        Conditional Hit breakpoints ? Break point will hit only when the specified condition met.

        Hit Count -> Break point will hit when that breakpoint is hit specified number of times.
        Export/Import the breakpoints to an XML file.
        and more..

        You can get these additional options by Right click on a break point and explore these additional capabilites

-- 6. New Sequence Node to manage Sequences:

        SQL Server 2012 SSMS has a new node “Sequences” in object explorer under Database-> programmability.
        This will allows you to create new sequences or update existing sequences by using GUI.

            Go to Object Expolorer -> Expland database -> Programmability -> Sequences.
            Right click the folder -> New Sequence

        This will open the dialog box, where you can create new sequence and mention its properties, such as minimum value, maximum value and increment value and set its other properties like "Cycle","Cache" etc.

-- 7. “Surround With” feature to enclose your query with IF,BEGIN..END,WHILE blocks

Often in stored procedures/queries, we will use IF, BEGIN..END, WHILE statements. Now, In SQL Server 2012, SSMS provides an option “Surround with”,
which simplifies in enclosing query statements with these blocks.

    Right Click and choose “Surround with”, which shows IF,BEGIN,WHILE options. Or Alternatively press Ctrl+k,Ctrl+s

-- 8. Keyboard Shortcuts enhancement

Few users are used to shortcuts in SSMS, where as users coming from Visual Studio background, might be used to certain shortcuts,
which might be different from SSMS. Now for the users, who are used to Visual Studio, SQL Server 2012 allows to change the keyboard shortcuts similarly like Visual Studio.
This feature also gives ability to export keyboard shortcuts to a file or import these shortcuts from a file.

To change keyboard shortcuts like Visual Studio,

    Go to Tools -> Options -> Keyboard
    Change dropdown selection from "Default" to "Visual Studio 2010 compatible" and Click OK.

To export/import settings to/from a file, you can find it under Tools -> import/export settings

-- 9 . Cycle Clipboard Ring feature to access clipboard and previously copied items

Often we will copy various parts of a query window content to another query window. While copying we often switch between multiple windows.
Now in SQL Server 2012, SSMS supports a new feature "Cycle Clipboard Ring", which allows us to access previously copied items from clipboard.
With the help of this feature, we can copy all the queries at once in a query window and now in another query window, we can paste all those consecutively..

Eg:

    From a query window, copy the query content by pressing Ctrl+C
    Copy another query content by pressing Ctrl+C
    To access recently copied content, press Ctrl+Shift+V
    Now to access the first copied content, press Ctrl+Shift+ V two times.

-- 10. One of the challenges involved in moving/copying databases to a new location (prior to SQL Server 2012) was copying the dependencies along with the database.
External dependencies such as login names, linked servers etc are not automatically copied to the new location when you restore a regular database backup.

SQL Server 2012 (Denali) introduces Contained Databases, a new feature that allows you to embed all the dependencies right into your database.
When the database is moved/copied to a new location, all the dependencies are also moved/copied along with the database.

GO TO Database Properties -> Options -> Containment Type.


-- 11. Following permissions has been added in SQL Server 2012

    Alter any availability Group - Users having this permission can update any availability group.
    Alter any server role - Users having this permission can update server roles.
    Create availability Group - Users having this permission can create availability group.
    Create any server role - Users having this permission can create server role.

Before SQL Server 2012, we have 8 Server Roles. There was no provision to create our own Custom Server Role. Now, in SQL Server 2012, we can create our own Custom Server Role.
While creating Custom Server Role, we can configure the required Endpoints,Logins,Servers,TSQL Named pipes etc.

To add your own custom server role,

    Connect to SSMS
    In object expolorer, Go to Security-> Server Roles.
    Right click and choose "Add new server role"

Date Ranges using SQL 2012 Functions

 -- Fiscal Year : July to June
DECLARE @Today        DATE = CAST(GETDATE() AS DATE)
DECLARE @BeginDate    DATE = CAST(CASE WHEN @DateSelection = 6 -- Last 12 Months
                                     THEN dbo.fn_LocalTimeToUtcTime(STR(MONTH(DATEADD(MONTH, -12,@Today)),2) + '/1/' + STR(YEAR(DATEADD(MONTH, -12, @Today)),4))
                                     WHEN @DateSelection = 5 -- This Fiscal Year
                                     THEN dbo.fn_LocalTimeToUtcTime(IIF(MONTH(@Today) > 6, '7/1/' + STR(YEAR(DATEADD(MONTH, -3, @Today)),4),'7/1/' + STR(YEAR(DATEADD(MONTH, -3, @Today)) - 1,4)))
                                    WHEN @DateSelection = 4 -- This Fiscal Quarter
                                        THEN dbo.fn_LocalTimeToUtcTime(CHOOSE(MONTH(@Today),'1', '1', '1','4','4','4','7','7','7', '10', '10', '10') + '/1/' + STR(YEAR(DATEADD(MONTH, -1, @Today)),4))
                                    WHEN @DateSelection = 3 -- Last 3 Months
                                        THEN dbo.fn_LocalTimeToUtcTime(STR(MONTH(DATEADD(MONTH, -3,@Today)),2) + '/1/' + STR(YEAR(DATEADD(MONTH, -3, @Today)),4))
                                    WHEN @DateSelection = 2 -- Next Month
                                        THEN dbo.fn_LocalTimeToUtcTime(DATEADD(MONTH, DATEDIFF(MONTH, 0, @Today) + 1, 0))
                                    WHEN @DateSelection = 1 -- Last Month
                                        THEN dbo.fn_LocalTimeToUtcTime(DATEADD(MONTH, DATEDIFF(MONTH, 0, @Today) - 1, 0))
                                ELSE DATEADD(MONTH, DATEDIFF(MONTH, 0, @Today), 0) -- This Month
                                END AS DATE)
DECLARE @EndDate DATE  = CAST(CASE WHEN @DateSelection = 6
                                      THEN dbo.fn_LocalTimeToUtcTime(EOMONTH(DATEADD(MONTH, DATEDIFF(MONTH, 0, @Today) - 1, 0)))
        WHEN @DateSelection = 5
        THEN dbo.fn_LocalTimeToUtcTime(IIF(MONTH(@Today) > 6, '6/30/' + STR(YEAR(DATEADD(MONTH, -3, @Today)) + 1 ,4),'6/30/' + STR(YEAR(DATEADD(MONTH, -3, @Today)),4)))
        WHEN @DateSelection = 4
            THEN dbo.fn_LocalTimeToUtcTime(CHOOSE(MONTH(@Today),'3/31/', '3/31/', '3/31/','6/30/','6/30/','6/30/','9/30/','9/30/','9/30/', '12/31/', '12/31/', '12/31/') + STR(YEAR(DATEADD(MONTH, -1, @Today)),4))
        WHEN @DateSelection = 3
            THEN dbo.fn_LocalTimeToUtcTime(EOMONTH(STR(MONTH(DATEADD(MONTH, -1,@Today)),2) + '/1/' + STR(YEAR(DATEADD(MONTH, -1, @Today)),4)))
        WHEN @DateSelection = 2
            THEN dbo.fn_LocalTimeToUtcTime(EOMONTH(@BeginDate))
        WHEN @DateSelection = 1
            THEN dbo.fn_LocalTimeToUtcTime(EOMONTH(@BeginDate))
    ELSE EOMONTH(@Today)
                                END AS DATE)

SELECT @BeginDate, @EndDate

Get Last 12 Months and their Start Dates

CREATE PROCEDURE P_Last12Months
AS
BEGIN
    DECLARE @Months TABLE
    (
    MID INT IDENTITY(1,1),
    MonthNumber INT,
    MonthDesc VARCHAR(15),
    StartDate DATE
    )

    DECLARE @Loop INT = 0, @Today DATETIME = GETDATE()

    WHILE (@Loop > -12)
    BEGIN

    INSERT @Months
    SELECT      IIF(MONTH(@Today) + @Loop <= 0 , 12 + MONTH(@Today) + @Loop, MONTH(@Today) + @Loop) AS MonthNumber
            , DATENAME(MONTH,DATEADD(MONTH, @Loop, @Today)) AS MonthDesc
            , DATEFROMPARTS(IIF(MONTH(@Today) + @Loop <= 0, YEAR(@Today) - 1,YEAR(@Today)),IIF(MONTH(@Today) + @Loop <= 0 , 12 + MONTH(@Today) + @Loop, MONTH(@Today) + @Loop) , 1)

    SET @Loop = @Loop - 1

    END

    SELECT * FROM @Months
END

Thursday, June 7, 2012

Replace the last delimiter with some word in a delimited string

SELECT REVERSE(STUFF(REVERSE('ABC, CDE, EFG'), CHARINDEX(',', REVERSE('ABC, CDE, EFG')), 1, ' dna '))

Next 3 , 6, 9, 12 months

SQL SERVER:

DECLARE
@3MB DATE = CAST(DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()) AS DATE),

@3ME DATE = CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,2,GETDATE()))+1,0))AS DATE),

@6MB DATE = CAST(DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,2,GETDATE()))+1,0)AS DATE),

@6ME DATE = CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,5,GETDATE()))+1,0))AS DATE),

@9MB DATE = CAST(DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,5,GETDATE()))+1,0)AS DATE),

@9ME DATE = CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,8,GETDATE()))+1,0))AS DATE),

@12MB DATE = CAST(DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,8,GETDATE()))+1,0)AS DATE),

@12ME DATE = CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,DATEADD(mm,11,GETDATE()))+1,0))AS DATE)

SELECT @3MB AS TMB, @3ME AS TME, @6MB AS SMB, @6ME AS SME, @9MB AS NMB, @9ME AS NME, @12MB AS TWMB, @12ME AS TWME


SSRS:

=Today.AddDays(1-Today.Day) -- 1st day of current month
=Today.AddDays(1-Today.Day).AddMonths(3).AddSeconds(-1) -- end of 3rd month from today
=Today.AddDays(1-Today.Day).AddMonths(3) -- begin of 4th month from today
=Today.AddDays(1-Today.Day).AddMonths(6).AddSeconds(-1) -- end of 6th month from today
=Today.AddDays(1-Today.Day).AddMonths(6) -- begin of 7th month from Today
=Today.AddDays(1-Today.Day).AddMonths(9).AddSeconds(-1) -- end of 9th month from today
=Today.AddDays(1-Today.Day).AddMonths(9) -- begin of 10th month from today
=Today.AddDays(1-Today.Day).AddMonths(12).AddSeconds(-1) -- end of 12th month from today

Tuesday, April 3, 2012

Group By VS Compute BY


GROUP BY produces a single result set. There is one row for each group containing only the grouping columns and aggregate functions that show the subaggregate for that group.
The select list can contain only the grouping columns and aggregate functions.
COMPUTE produces multiple result sets. One kind of result set contains the detail rows for each group containing the expressions from the select list.
The other type of result set contains the subaggregate for a group, or the total aggregate for the SELECT statement.
The select list can contain expressions other than the grouping columns or aggregate functions.
The aggregate functions are specified in the COMPUTE clause, not in the select list.

Monday, January 2, 2012

Pagination in SQL

-- SQL Server stored procedure - SQL pagination - SQL row_number function
CREATE PROCEDURE uspContactInfoByPage
                @PageSize   INT,
                @PageNumber INT
AS
  BEGIN
    WITH cteContact
         AS (SELECT ContactID,
                    FirstName,
                    MiddleName,
                    LastName,
                    EmailAddress,
                    Phone,
                    ROW_NUMBER()
                      OVER(ORDER BY LastName, FirstName, ContactID) AS SEQUENCE
             FROM   Person.Contact)
    SELECT Name = replace(FirstName + ' ' + isnull(MiddleName,'') + ' ' + LastName,
                          ' ',' '),
           [Email Address] = EmailAddress,
           Telephone = Phone
    FROM   cteContact
    WHERE  SEQUENCE BETWEEN @PageSize * @PageNumber + 1
    AND @PageSize * (@PageNumber + 1)
  END

GO

EXEC dbo.uspContactInfoByPage
  50 ,
  100
GO

Ranking Functions

-- Microsoft T-SQL ranking functions comparison
------------
-- MSSQL row_number (row number sequence) - NTILE
-- MSSQL rank and dense_rank functions - over order by clause
-- Data is rounded down to the thousands for ranking
USE AdventureWorks;
SELECT  
         c.AccountNumber                  AS CustAccount,
         FLOOR(h.SubTotal /1000)          AS [SubTotal (Thousands $)],
         ROW_NUMBER()
           OVER(ORDER BY FLOOR(h.SubTotal /1000) DESC) AS RowNumber,
         RANK()
           OVER(ORDER BY FLOOR(h.SubTotal /1000) DESC) AS Rank,
         DENSE_RANK()
           OVER(ORDER BY FLOOR(h.SubTotal /1000) DESC) AS DenseRank,
         NTILE(5)
           OVER(ORDER BY FLOOR(h.SubTotal /1000) DESC) AS NTile
FROM     Sales.Customer c
         INNER JOIN Sales.SalesOrderHeader h
           ON c.CustomerID = h.CustomerID
         INNER JOIN Sales.SalesTerritory t
           ON h.TerritoryID = t.TerritoryID
WHERE    t.Name = 'Germany'
  AND    YEAR(OrderDate) = 2004
  AND    SubTotal >= 4000.0
ORDER BY RowNumber;
GO
/* Results
CustAccountSubTotal (Thousands $)RowNumberRankDenseRankNTile
AW000002301001111
AW00000230882221
AW00000302773331
AW00000320684441
AW00000536685441
AW00000536646651
AW00000266587761
AW00000302448872
AW00000687439982
AW0000048236101092
AW0000017636111092

Import & export images in SQL

USE AdventureWorks;

-- Create image warehouse for importing image into sql database
CREATE TABLE dbo.ImageWarehouse (
  ImageWarehouseID INT    IDENTITY ( 1 , 1 )    PRIMARY KEY,
  ImageName        VARCHAR(100),
  Photo            VARBINARY(MAX))
GO

-- SQL Server import image - sql storing images database sql server
INSERT INTO dbo.ImageWarehouse
           ([ImageName])
VALUES     ('5THAVE.JPG')

UPDATE dbo.ImageWarehouse
SET    Photo = (SELECT *
                FROM   OPENROWSET(BULK 'e:\image\photo\5THAVE.JPG',
                       SINGLE_BLOB) AS x)
WHERE  [ImageName] = '5THAVE.JPG'
GO

-- Check table population
SELECT *
FROM   dbo.ImageWarehouse
GO

-- SQL Server export image
DECLARE  @SQLcommand NVARCHAR(4000)

-- Keep the command on ONE LINE - SINGLE LINE!!! - broken here for presentation
SET @SQLcommand = 'bcp "SELECT Photo FROM AdventureWorks.dbo.ImageWarehouse"
                   queryout "e:\image\photo\exp5THAVE.jpg" -T -n -SPROD\SQL2005'

PRINT @SQLcommand -- debugging

EXEC xp_cmdshell   @SQLcommand
GO
------------
-- T-SQL Export all images in table to file system folder
-- Source table: Production.ProductPhoto  - Destination: K:\data\images\productphoto\
------------

USE AdventureWorks2008;
GO

DECLARE  @Command       VARCHAR(4096),   -- dynamic command
         @PhotoID       INT,
         @ImageFileName VARCHAR(128)

DECLARE crsImage CURSOR  FOR             -- Cursor for each image in table
SELECT ProductPhotoID,
       LargePhotoFileName
FROM   Production.ProductPhoto
WHERE  LargePhotoFileName != 'no_image_available_large.gif'

OPEN crsImage

FETCH NEXT FROM crsImage
INTO @PhotoID,
     @ImageFileName

WHILE (@@FETCH_STATUS = 0) -- Cursor loop 
  BEGIN
-- Keep the bcp command on ONE LINE - SINGLE LINE!!! - broken up for presentation
    SET @Command = 'bcp "SELECT LargePhoto FROM
    AdventureWorks2008.Production.ProductPhoto WHERE ProductPhotoID = ' +
    convert(VARCHAR,@PhotoID) + '" queryout "K:\data\images\productphoto\' +
    @ImageFileName + '" -T -n -SYOURSERVER'
    
    PRINT @Command -- debugging 
/* bcp "SELECT LargePhoto FROM AdventureWorks2008.Production.ProductPhoto
WHERE ProductPhotoID = 69" queryout
"K:\data\images\productphoto\racer02_black_f_large.gif" -T -n -SYOURSERVER
*/
    
    EXEC xp_cmdshell @Command     -- Carry out image export to file from db table
    
    FETCH NEXT FROM crsImage
    INTO @PhotoID,
         @ImageFileName
  END  -- cursor loop

CLOSE crsImage
DEALLOCATE crsImage
/*output
NULL
Starting copy...
NULL
1 rows copied.
Network packet size (bytes): 4096
Clock Time (ms.) Total     : 16     Average : (62.50 rows per sec.)
NULL
.....
*/