Thursday, April 4, 2013

SQL SERVER – Group by Rows and Columns using XML PATH – Efficient Concating Trick

I hardly get hard time to come up with the title of the blog post. This was one of the blog post even though simple, I believe I have not come up with appropriate title. Any way here is the question I received.
"I have a table of students and the courses they are enrolled with the name of the professor besides it. I would like to group the result with course and instructor name.
For example here is my table:
How can I generate result as following?
"
Now you can see how easy the question is but so hard to come up with either solution or  title of this blog post. We can use XML PATH and come up with the solution where we combine two or more columns together and display desired result.
Here is the quick script which does the task ask, I have used temporary tables so you can just take this script and quickly run on your machine and see how it returns results.
Let me know if there are any better ways to do the same.
-- Create tableCREATE TABLE #TestTable (StudentName VARCHAR(100), Course VARCHAR(100), Instructor VARCHAR(100), RoomNo VARCHAR(100))GO-- Populate tableINSERT INTO #TestTable (StudentName, Course, Instructor, RoomNo)SELECT 'Mark', 'Algebra', 'Dr. James', '101'UNION ALLSELECT 'Mark', 'Maths', 'Dr. Jones', '201'UNION ALLSELECT 'Joe', 'Algebra', 'Dr. James', '101'UNION ALLSELECT 'Joe', 'Science', 'Dr. Ross', '301'UNION ALLSELECT 'Joe', 'Geography', 'Dr. Lisa', '401'UNION ALLSELECT 'Jenny', 'Algebra', 'Dr. James', '101'GO-- Check orginal dataSELECT *FROM #TestTableGO-- Group by Data using column and XML PATHSELECTStudentName,STUFF((SELECT ', ' + Course + ' by ' + CAST(Instructor AS VARCHAR(MAX)) + ' in Room No ' + CAST(RoomNo AS VARCHAR(MAX))FROM #TestTableWHERE (StudentName = StudentCourses.StudentName)FOR XML PATH (''))
,
1,2,'') AS NameValuesFROM #TestTable StudentCoursesGROUP BY StudentName
GO
-- Clean upDROP TABLE #TestTableGO

Create Indexed Views

http://msdn.microsoft.com/en-us/library/ms191432.aspx

Server 2008 R2
2 out of 4 rated this helpful - Rate this topic

A view must meet the following requirements before you can create a clustered index on it:
  • The ANSI_NULLS and QUOTED_IDENTIFIER options must have been set to ON when the CREATE VIEW statement was executed. The OBJECTPROPERTY function reports this for views through the ExecIsAnsiNullsOn or ExecIsQuotedIdentOn properties.
  • The ANSI_NULLS option must have been set to ON for the execution of all CREATE TABLE statements that create tables referenced by the view.
  • The view must not reference any other views, only base tables.
  • All base tables referenced by the view must be in the same database as the view and have the same owner as the view.
  • The view must be created with the SCHEMABINDING option. Schema binding binds the view to the schema of the underlying base tables.
  • User-defined functions referenced in the view must have been created with the SCHEMABINDING option.
  • Tables and user-defined functions must be referenced by two-part names in the view. One-part, three-part, and four-part names are not allowed.
  • All functions referenced by expressions in the view must be deterministic. The IsDeterministic property of the OBJECTPROPERTY function reports whether a user-defined function is deterministic. For more information, see Deterministic and Nondeterministic Functions.
    NoteNote
    When you refer to datetime and smalldatetime string literals in indexed views in SQL Server 2008, we recommend that you explicitly convert the literal to the date type you want by using a deterministic date format style. For a list of the date format styles that are deterministic, see CAST and CONVERT (Transact-SQL). Expressions that involve implicit conversion of character strings to datetime or smalldatetime are considered nondeterministic, unless the compatibility level is set to 80 or earlier. This is because the results depend on the LANGUAGE and DATEFORMAT settings of the server session. For example, the results of the expression CONVERT (datetime, '30 listopad 1996', 113) depend on the LANGUAGE setting because the string 'listopad' means different months in different languages. Similarly, in the expression DATEADD(mm,3,'2000-12-01'), SQL Server interprets the string '2000-12-01' based on the DATEFORMAT setting.
    Implicit conversion of non-Unicode character data between collations is also considered nondeterministic, unless the compatibility level is set to 80 or earlier.
    Creating indexes on views that contain these expressions is not allowed in 90 compatibility mode. However, existing views that contain these expressions from an upgraded database are maintainable. If you use indexed views that contain implicit string to date conversions, be certain that the LANGUAGE and DATEFORMAT settings are consistent in your databases and applications to avoid possible indexed view corruption.
  • If the view definition uses an aggregate function, the SELECT list must also include COUNT_BIG (*).
  • The data access property of a user-defined function must be NO SQL, and external access property must be NO.
  • Common language runtime (CLR) functions can appear in the select list of the view, but cannot be part of the definition of the clustered index key. CLR functions cannot appear in the WHERE clause of the view or the ON clause of a JOIN operation in the view.
  • CLR functions and methods of CLR user-defined types used in the view definition must have the properties set as shown in the following table.
    PropertyNote
    DETERMINISTIC = TRUEMust be declared explicitly as an attribute of the Microsoft .NET Framework method
    PRECISE = TRUEMust be declared explicitly as an attribute of the .NET Framework method.
    DATA ACCESS = NO SQLDetermined by setting DataAccess attribute to DataAccessKind.None and SystemDataAccess attribute to SystemDataAccessKind.None.
    EXTERNAL ACCESS = NOThis property defaults to NO for CLR routines.
    For more information about how to set attributes of CLR routine methods, see Custom Attributes for CLR Routines.
    Caution noteCaution
    We do not recommend setting the properties of CLR routine methods in contradiction to the functionality of the method. Doing this could lead to data corruption.
  • The SELECT statement in the view cannot contain the following Transact-SQL syntax elements:
    • The * or table_name.* syntax to specify columns. Column names must be explicitly stated.
    • A table column name used as a simple expression cannot be specified in more than one view column. A column can be referenced multiple times provided all, or all but one, reference to the column is part of a complex expression or a parameter to a function. For example, the following select list is not valid:
      SELECT ColumnA, ColumnB, ColumnA
      
      This select list is valid:
      SELECT SUM(ColumnA) AS SumColA, ColumnA % ColumnB AS ModuloColAColB, COUNT_BIG(*) AS cBig FROM dbo.T1 GROUP BY ModuloColAColB
      
    • An expression on a column used in the GROUP BY clause, or an expression on the results of an aggregate.
    • A derived table.
    • A common table expression (CTE).
    • Rowset functions.
    • UNION, EXCEPT or INTERSECT operators.
    • Subqueries.
    • Outer or self joins.
    • TOP clause.
    • ORDER BY clause.
    • DISTINCT keyword.
    • COUNT (COUNT_BIG(*) is allowed.)
    • The AVG, MAX, MIN, STDEV, STDEVP, VAR, or VARP aggregate functions. If AVG(expression) is specified in queries referencing the indexed view, the optimizer can frequently calculate the needed result if the view select list contains SUM(expression) and COUNT_BIG(expression). For example, an indexed view SELECT list cannot contain the expression AVG(column1). If the view SELECT list contains the expressions SUM(column1) and COUNT_BIG(column1), SQL Server can calculate the average for a query that references the view and specifies AVG(column1).
    • A SUM function that references a nullable expression.
    • The OVER clause, which includes ranking or aggregate window functions.
    • A CLR user-defined aggregate function.
    • The full-text predicates CONTAINS or FREETEXT.
    • COMPUTE or COMPUTE BY clause.
    • The CROSS APPLY or OUTER APPLY operators.
    • The PIVOT or UNPIVOT operators
    • Table hints (applies to compatibility level of 90 or higher only).
    • Join hints.
    • Direct references to Xquery expressions. Indirect references, such as Xquery expressions inside a schema-bound user-defined function, are acceptable.
  • If GROUP BY is specified, the view select list must contain a COUNT_BIG(*) expression, and the view definition cannot specify HAVING, ROLLUP, CUBE, or GROUPING SETS.
The first index created on a view must be a unique clustered index. After the unique clustered index has been created, you can create additional nonclustered indexes. The naming conventions for indexes on views are the same as for indexes on tables. The only difference is that the table name is replaced with a view name. For more information, see CREATE INDEX (Transact-SQL).
The CREATE INDEX statement must meet the following requirements as well as the regular CREATE INDEX requirements:
  • The user that executes the CREATE INDEX statement must be the view owner.
  • The following SET options must be set to ON when the CREATE INDEX statement is executed:
    • ANSI_NULLS
    • ANSI_PADDING
    • ANSI_WARNINGS
    • CONCAT_NULL_YIELDS_NULL
    • QUOTED_IDENTIFIER
  • The NUMERIC_ROUNDABORT option must be set to OFF. This is the default setting.
  • If the database is running in 80 compatibility mode or earlier, the ARITHABORT option must be set to ON.
  • When you create a clustered or nonclustered index, the IGNORE_DUP_KEY option must be set to OFF (the default setting).
  • The view cannot include text, ntext, or image columns, even if they are not referenced in the CREATE INDEX statement.
  • If the SELECT statement in the view definition specifies a GROUP BY clause, the key of the unique clustered index can reference only columns specified in the GROUP BY clause.
  • An imprecise expression that forms the value of an index key column must reference a stored column in a base table underlying the view. This column may be a regular stored column or a persisted computed column. No other imprecise expressions can be part of the key column of an indexed view.
The setting of the large_value_types_out_of_row option of columns in an indexed view is inherited from the setting of the corresponding column in the base table. This value is set by using sp_tableoption. The default setting for columns formed from expressions is 0. This means that large value types are stored in-row. For more information, see Using Large-Value Data Types.
After the clustered index is created, any connection that tries to modify the base data for the view must also have the same option settings required to create the index. SQL Server generates an error and rolls back any INSERT, UPDATE, or DELETE statement that will affect the result set of the view if the connection executing the statement does not have the correct option settings. For more information, see SET Options That Affect Results.
All indexes on a view are dropped when the view is dropped. All nonclustered indexes and auto-created statistics on the view are dropped when the clustered index is dropped. User-created statistics on the view are maintained. Nonclustered indexes can be individually dropped. Dropping the clustered index on the view removes the stored result set, and the optimizer returns to processing the view like a standard view.
Although only the columns that make up the clustered index key are specified in the CREATE UNIQUE CLUSTERED INDEX statement, the complete result set of the view is stored in the database. As in a clustered index on a base table, the B-tree structure of the clustered index contains only the key columns, but the data rows contain all the columns in the view result set.
If you want to add indexes to views in an existing system, you must schema bind any view on which you want to place an index. You can perform the following operations:
  • Drop the view and re-create it specifying WITH SCHEMABINDING.
  • You can create a second view that has the same text as the existing view but a different name. The optimizer considers the indexes on the new view, even if it is not directly referenced in the FROM clause of queries.
    NoteNote
    Views or tables that participate in a view created with the SCHEMABINDING clause cannot be dropped, unless the view is dropped or changed so that it no longer has schema binding. Additionally, ALTER TABLE statements on tables that participate in views having schema binding will fail if these statements affect the view definition.
You must make sure that the new view meets all the requirements of an indexed view. This may require that you change the ownership of the view and all base tables it references so they are all owned by the same user.
Indexes on tables and views can be disabled. When a clustered index on a table is disabled, indexes on views associated with the table are also disabled. For more information, see Disabling Indexes.
The following example creates a view and an index on that view. Two queries are included that use the indexed view.
USE AdventureWorks2008R2;
GO
--Set the options to support indexed views.
SET NUMERIC_ROUNDABORT OFF;
SET ANSI_PADDING, ANSI_WARNINGS, CONCAT_NULL_YIELDS_NULL, ARITHABORT,
    QUOTED_IDENTIFIER, ANSI_NULLS ON;
GO
--Create view with schemabinding.
IF OBJECT_ID ('Sales.vOrders', 'view') IS NOT NULL
DROP VIEW Sales.vOrders ;
GO
CREATE VIEW Sales.vOrders
WITH SCHEMABINDING
AS
    SELECT SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Revenue,
        OrderDate, ProductID, COUNT_BIG(*) AS COUNT
    FROM Sales.SalesOrderDetail AS od, Sales.SalesOrderHeader AS o
    WHERE od.SalesOrderID = o.SalesOrderID
    GROUP BY OrderDate, ProductID;
GO
--Create an index on the view.
CREATE UNIQUE CLUSTERED INDEX IDX_V1 
    ON Sales.vOrders (OrderDate, ProductID);
GO
--This query can use the indexed view even though the view is 
--not specified in the FROM clause.
SELECT SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rev, 
    OrderDate, ProductID
FROM Sales.SalesOrderDetail AS od
    JOIN Sales.SalesOrderHeader AS o ON od.SalesOrderID=o.SalesOrderID
        AND ProductID BETWEEN 700 and 800
        AND OrderDate >= CONVERT(datetime,'05/01/2002',101)
GROUP BY OrderDate, ProductID
ORDER BY Rev DESC;
GO
--This query can use the above indexed view.
SELECT  OrderDate, SUM(UnitPrice*OrderQty*(1.00-UnitPriceDiscount)) AS Rev
FROM Sales.SalesOrderDetail AS od
    JOIN Sales.SalesOrderHeader AS o ON od.SalesOrderID=o.SalesOrderID
        AND DATEPART(mm,OrderDate)= 3
        AND DATEPART(yy,OrderDate) = 2002
GROUP BY OrderDate
ORDER BY OrderDate ASC;
GO


ERRORFILE and MAXERRORS option with BULK INSERT


In the below code MAXERRORS argument defines the total number of records which can be rejected before entire file will be rejected by the system. It is a tolerant level and can be any integer number based on your discretion and requirement
ERRORFILE will define the name and path of the error log file which will contain the erroneous records rejected during Bulk Insert. It will contain maximum of MAXERRORS+1 records.
So in the above script complete file will be rejected only if more than 500 records are rejected during BULK INSERT and 501 records will be logged into Error log file which will be present at the path defined in ERRORFILE argument.
While if less than 500 records are rejected (or no records are rejected) than those records will be logged into error log file while rest of the records will be successfully loaded into the staging table.

Conclusion :


Although these two arguments are rarely used but they can be very helpful if you want to know about the records which are rejected and also to set the tolerance level of the file loaded by defining MAXERRORS value.
DECLARE @SQL   varchar(2000)
DECLARE @FileToLoad   varchar(100)
DECLARE @DestinationTableName   varchar(50)
DECLARE @StartingRow   int
DECLARE @FormatFile  varchar(50)
DECLARE @ErrorLogFile  varchar(50)
SET @FileToLoad = '\\servername\foldername\test.txt' --This is the name and path of the file which is to be BULK INSERTED into the staging table
              
SET @DestinationTableName = 'STAGING_TABLE' --Name of the staging table into which data from .txt file need to be loaded
        
SET @StartingRow = 2 --Tell us about the starting row in text file which needs to be loaded into staging table. 1st row is left as header
                 
SET @FormatFile = '\\servername\Format_file\test.fmt' --path and name of the format file.Format file with extension.fmt is used to define the mapping between text file columns and staging table columns
              
SET @ErrorLogFile  = '\\servername\TestErrorLog.txt' --path and name of the errorlog file.Records rejected during Bulk Insert will be saved in this error log file                                     

 SELECT @SQL = 'BULK INSERT DATABASENAME.dbo.' + @DestinationTableName + ' FROM ''' + @ FileToLoad + '''
   WITH ( FIRSTROW = ' + RTRIM(STR(@StartingRow)) + ',
    MAXERRORS = 500,
    FORMATFILE = ''' + @FormatFile + ''',
    ERRORFILE =''' + @ ErrorLogFile + ''',
    ) '
EXECUTE (@SQL)

Error logging with Bulk Insert

Error logging with Bulk Insert

The bulk insert is used to bulk load data into staging tables from .txt or .csv files. It is very fast compared to normal insert statements because by default, CHECK and FOREIGN KEY constraints are disabled. Although this behavior can be controlled by using CHECK_CONSTRAINTS, it is highly recommended for better performance not to use this argument. For more information on the Bulk Insert statement please refer to the Microsoft documentation: http://msdn.microsoft.com/en-us/library/ms188365.aspx
The Bulk Insert statement rejects all the records that do not match the column data type or size such as:
  1. We have a column defined as an Integer and a value present in that column is actually composed of characters.
  2. The size defined is varchar(10) and actually the length of the value is more than 10 characters.
Only the records that violates the above rules are rejected and not the entire file. Again this behavior can be controlled by using the MAXERRORS argument. This argument defines how many records can be rejected before rejecting the complete file.
Let's examine a couple scenarios to show how this works
Scenario 1: Suppose we have defined the value of MAXERRORS to be 10 and 5 records are rejected during the Bulk Insert process. The rest of the records will be loaded successfully, and only those 5 records that don't meet the criteria are not loaded.
Scenario 2: Suppose we have defined the value of MAXERRORS to be 10 and 11 records are rejected during the Bulk Insert process. Then the entire file will be rejected and no records will be loaded into staging table.
Now the question arises about the 5 records rejected during Scenario 1. We can save those records in a different file if we enable error logging features. Use the argument ERRORFILE and give the path and name of the file where you want to log such records that are rejected during BULK INSERT.
Remember if the entire file is rejected due to exceeding the value of MAXERRORS value then only MAXERRORS+1 records will be logged in the error log file. This is because the system checks the file only to the point where it gets to MAXERRORS + 1 and not beyond this record.
Along with the error log file, another file is created by default by the system which has the extension .txt.error. It will contain row number and offset of erroneous records. Note that there are some issues with this file (.txt.error) in SQL Server 2005 SP2, which are fixed in the SP4 patch. The issue is SQL Server creates this file two times during the execution of BULK INSERT. When it is created the second time, it gives the error that the file already exists, which leads to the failure of the BULK INSERT statement.

Conclusion

Using the argument ERRORFILE in BULK INSERT statement helps you in identifying the records which are rejected and to fix those records and reload into the system. The MAXERRORS parameter also allows you to control whether you want to allow a file to load that might have specified number of errors.

Wednesday, April 3, 2013

Importing CSV file into SQLServer and create table dynamically

public void Main()
{

try{
// TODO: Add your code hereSqlConnection myADONETConnection = new SqlConnection();
string sDatabase = Dts.Variables["mDatabase"].Value.ToString();
// sDatabase = "MCUK";//Dts.Connections["Test1"].ConnectionString = "Data source=172.29.17.56;initial catalog=" + sDatabase + ";uid=sa;password=password1";Dts.Connections["Con"].ConnectionString = "Data source=86.54.116.59;initial catalog=" + sDatabase + ";Integrated Security=SSPI;";myADONETConnection = (
SqlConnection)(Dts.Connections["Con"].AcquireConnection(Dts.Transaction) as SqlConnection);
//MessageBox.Show(myADONETConnection.ConnectionString, "Test1");string line1 = "";//Reading file names one by onestring filenameonly1 = "TempCSVParticipation";
//string CompleteDirectory = Dts.Variables["mFilePath"].Value.ToString();//string ActualFileName = Path.GetFileName(CompleteDirectory);string SourceDirectory = Dts.Variables["mFilePath"].Value.ToString();
//SourceDirectory = @"S:\Production\MCUK\Extracts\201302\Participation";TrialBalanceLog("database-"+sDatabase, myADONETConnection);TrialBalanceLog(
"directory-" + SourceDirectory, myADONETConnection);
//MessageBox.Show(sDatabase);//MessageBox.Show(ActualFileName);//MessageBox.Show(SourceDirectory);string[] fileEntries = Directory.GetFiles(SourceDirectory);TrialBalanceLog(SourceDirectory, myADONETConnection);

foreach (string fileName in fileEntries){

//MessageBox.Show("f-" + fileName.ToString());TrialBalanceLog(fileName, myADONETConnection);

//if (fileName.ToString().ToUpper() == (SourceDirectory +'\\'+ ActualFileName).ToUpper())if (fileName.ToString().Contains("Actual_Participation")){
TrialBalanceLog(
"1-" + fileName, myADONETConnection);
//MessageBox.Show("1" + fileName);System.IO.StreamReader file2 = new System.IO.StreamReader(fileName);
//MessageBox.Show("2");string filenameonly = (((fileName.Replace(SourceDirectory, "")).Replace(".csv", "")).Replace("\\", ""));
//MessageBox.Show("3");line1 = (" IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo]." + filenameonly1 + "') AND type in (N'U')) DROP TABLE [dbo]." + filenameonly1 + " Create Table dbo." + filenameonly1 + "([" + file2.ReadLine().Replace(",", "] NVARCHAR(4000), [") + "] NVARCHAR(4000))").Replace(".txt", ""); file2.Close();
//MessageBox.Show("4");SqlCommand myCommand = new SqlCommand(line1, myADONETConnection);
//MessageBox.Show(line1);myCommand.ExecuteNonQuery();
//MessageBox.Show("6");line1 = "BULK INSERT " + filenameonly1 + " FROM '" + fileName + "' WITH (FIELDTERMINATOR = ',',ROWTERMINATOR = '\\n' ) ";
//MessageBox.Show(line1);SqlCommand myCommand1 = new SqlCommand(line1, myADONETConnection);
//MessageBox.Show("8");myCommand1.CommandTimeout = 0;
//MessageBox.Show("9");myCommand1.ExecuteNonQuery();
//MessageBox.Show("10");//MessageBox.Show(fileName.ToString() + " Completed");}
}
Dts.TaskResult = (
int)ScriptResults.Success;}

catch (Exception e){

//MessageBox.Show("Error-" + e.Message);}
}

Call SSIS Package from SQL Stored Procedure

SET ANSI_NULLS ON
GO
SET
QUOTED_IDENTIFIER ONGO
CREATE
PROCEDURE [dbo].[GenerateIntPart](@Month as varchar(2)=NULL, @Year as varchar(4)=NULL)AS
BEGIN
SET
NOCOUNT ON; DECLARE @ssis_cmd VARCHAR(4000)DECLARE @Packagepath VARCHAR(500)DECLARE @FileName VARCHAR(500)Declare @database as varchar(500) Declare @filepath as varchar(5000)Declare @sqlquery as varchar(max)Declare @oem as varchar(50)Declare @PackageType as char(1)--Exec GenerateIntPart '01','2012'select @filename=FilePath,@oem=code from somemasterselect @database=db_name(dbid) from master..sysprocesses where spid=@@SPID
set @PackageType='F'if @PackageType='F'begin
SET
@Packagepath = 'D:\ImportCSV\ImporttCSV\bin\OvernightInt.dtsx' --Path of SSIS packageSELECT @ssis_cmd = 'dtexec /FILE "' + @Packagepath + '"'end
else
begin
SET
@Packagepath ='Import\NightData'SELECT @ssis_cmd = 'dtexec /SQL "' + @Packagepath + '"'end
set
@FileName='' + @FileName + '\' + @Year+@month+ '\ParticipationSELECT @ssis_cmd = @ssis_cmd + ' /CHECKPOINTING OFF /REPORTING EW /SET "\Package.Variables[User::mFilePath].Properties[Value]";"' + @FileName + '"'SELECT @ssis_cmd = @ssis_cmd + ' /SET "\Package.Variables[User::mDatabase].Properties[Value]";"' + @database + '"'--SELECT @ssis_cmd = @ssis_cmd + ' /SER CONS-109A'print @ssis_cmd --EXEC master..xp_cmdshell @ssis_cmd
DECLARE @returncode intEXEC
@returncode = xp_cmdshell @ssis_cmdselect @returncode
END



For Importing a SSIS job into SQL -

http://msdn.microsoft.com/en-IN/library/ms141235(v=sql.105).aspx


You can import or export an Integration Services package from or to the following locations:

  • You can import a package that is stored in an instance of Microsoft SQL Server, in the file system, or in the SSIS package store. The imported package is saved to SQL Server or to a folder in the SSIS package store.
  • You can export a package that is stored in an instance of SQL Server, the file system, or the SSIS Package Store to a different storage format and location.
However, there are some restrictions on importing and exporting a package between different versions of SQL Server:
  • On an instance of SQL Server 2008, you can import packages from an instance of SQL Server 2005, but you cannot export packages to an instance of SQL Server 2005.
  • On an instance of SQL Server 2005, you cannot import packages from, or export packages to, an instance of SQL Server 2008.
The following procedures describe how to use SQL Server Management Studio to import or export a package.

To import a package by Using SQL Server Management Studio

  1. Click Start, point to Microsoft SQL Server, and then click SQL Server Management Studio.
  2. In the Connect to Server dialog box set the following options:
    • In the Server type box, select Integration Services.
    • In the Server name box, provide a server name or click <Browse for more…> and locate the server to use.
  3. If Object Explorer is not open, on the View menu, click Object Explorer.
  4. In Object Explorer, expand the Stored Packages folder.
  5. Expand the subfolders to locate the folder into which you want to import a package.
  6. Right-click the folder, click Import Package. and then do one of the following:
    • To import from an instance of SQL Server, select the SQL Server option, and then specify the server and select the authentication mode. If you select SQL Server Authentication, provide a user name and a password.
      Click the browse button (…), select the package to import, and then click OK.
    • To import from the file system, select the File system option.
      Click the browse button (…), select the package to import, and then click Open.
    • To import from the SSIS Package Store, select the SSIS Package Store option and specify the server.
      Click the browse button (…), select the package to import, and then click OK.
  7. Optionally, update the package name.
  8. To update the protection level of the package, click the browse button (…) and choose a different protection level by using the Package Protection Level dialog box. If the Encrypt sensitive data with password or the Encrypt all data with password option is selected, type and confirm a password.
  9. Click OK to complete the import.

To export a package by Using SQL Server Management Studio

  1. Click Start, point to Microsoft SQL Server, and then click SQL Server Management Studio.
  2. In the Connect to Server dialog box, set the following options:
    • In the Server type box, select Integration Services.
    • In the Server name box, provide a server name or click <Browse for more…> and locate the server to use.
  3. If Object Explorer is not open, on the View menu, click Object Explorer.
  4. In Object Explorer, expand the Stored Packages folder.
  5. Expand the subfolders to locate the package you want to export.
  6. Right-click the package, click Export, and then do one of the following:
    • To export to an instance of SQL Server, select the SQL Server option, and then specify the server and select the authentication mode. If you select SQL Server Authentication, provide a user name and a password.
      Click the browse button (…), and expand the SSIS Packages folder to locate the folder to which you want to save the package. Optionally, update the default name of the package, and then click OK.
    • To export to the file system, select the File System option.
      Click the browse button (…) to locate the folder to which you want to export the package, type the name of the package file, and then click Save.
    • To export to the SSIS package store, select the SSIS Package Store option, and specify the server.
      Click the browse button (…), expand the SSIS Packages folder, and select the folder to which you want to save the package. Optionally, enter a new name for the package in the Package Name text box. Click OK.
  7. To update the protection level of the package, click the browse button (…) and choose a different protection level by using the Package Protection Level dialog box. If the Encrypt sensitive data with password or the Encrypt all data with password option is selected, type and confirm a password.
  8. Click OK to complete the export.

SQL Merge Statement

 

I love the SQL MERGE statement, introduced into MS SQL Server 2008.  It’s made the job of updating tables where many records need to be either added, updated or removed a plain joy.  No longer do you need to either do a DELETE first, or check for EXISTS and UPDATE then check for NOT EXISTS and INSERT.  As far as I’m concerned, it is the neatest thing to come along in the Data Manipulation Language (DML) world since sliced bread.
If you’ve never used one before, you simply have got to try it.  However there is one little wrinkle to it that can be a major hazard if you are caught unaware.  So today we’ll set up a problem, show you how to use a MERGE to solve it, and then demonstrate both the hazard and the very simple way that it can be solved.

Set Up the Test Conditions

Let’s populate a couple of tables so that we can demonstrate the MERGE.
CREATE TABLE #Test1

(ID INT, RowNo INT, Value MONEY);CREATE TABLE #Test2

(ID INT, RowNo INT, Value MONEY);INSERT INTO #Test1              -- Target           SELECT 1, 1, 25 UNION ALL SELECT 1, 2, 32 UNION ALL SELECT 2, 1, 38 UNION ALL SELECT 2, 2, 61 UNION ALL SELECT 2, 4, 43 UNION ALL SELECT 3, 1, 15 UNION ALL SELECT 3, 2, 99 UNION ALL SELECT 3, 3, 54;INSERT INTO #Test2              -- Source           SELECT 2, 1, 45 UNION ALL SELECT 2, 2, 88 UNION ALL SELECT 2, 3, 28;
Our plan is to merge our source table (#Test2) into our target table (#Test1).  Note that for the rows where the ID=2, we have 3 values of RowNo in the target (1, 2, 4) and three values in the source (1, 2, 3). Here’s the MERGE:
BEGIN TRANSACTION T1;

MERGE #Test1 t  -- Target 
USING #Test2 s  -- Source ON t.ID = s.ID AND t.RowNo = s.RowNo 
  WHEN MATCHED 
    THEN     
      UPDATE SET Value = s.Value 
  WHEN NOT MATCHED 
    THEN       -- Target     
      INSERT (ID, RowNo, Value)     
        VALUES (s.ID, s.RowNo, s.Value);SELECT 
  *
 FROM #Test1
 ORDER BY ID, RowNo;ROLLBACK TRANSACTION T1;SELECT *
 FROM #Test1 
 ORDER BY ID, RowNo;
Note that we’ve taken the precaution of wrapping our MERGE in a TRANSACTION and we do a ROLLBACK after the MERGE completes, so we can see the results of the MERGE displayed by the first SELECT.  Those results are:
ID    RowNo  Value
1     1      25.001     2      32.002     1      45.002     2      88.002     3      28.002     4      43.003     1      15.003     2      99.003     3      54.00
The rows with IDs 1 and 3 are unchanged.  For ID=2, we now have four rows where the Value column has been updated in RowNo 1 and 2, RowNo 3 was unchanged (because it didn’t exist in the source) and RowNo 4 was inserted.
Hold on, that wasn’t exactly what we wanted.  We really wanted to replace the entire block of records for ID=2.  Hastily, we consult MSDN about the MERGE statement.

Demonstrate the Hazard

A quick scan of the article identifies a clause of the MERGE where you can say WHEN NOT MATCHED BY SOURCE THEN DELETE.  Obviously this must be the ticket, so let’s give that a try.
MERGE #Test1 t       -- Target 
USING #Test2 s       -- Source ON t.ID = s.ID AND t.RowNo = s.RowNo
  WHEN MATCHED 
    THEN
      UPDATE SET Value = s.Value
  WHEN NOT MATCHED   -- Target
    THEN
       INSERT (ID, RowNo, Value)
         VALUES (s.ID, s.RowNo, s.Value)
  WHEN NOT MATCHED BY SOURCE 
    THEN
      DELETE;SELECT 
  *
 FROM #Test1
 ORDER BY ID, RowNo;ROLLBACK TRANSACTION T1;SELECT 
  *
 FROM #Test1
 ORDER BY ID, RowNo;
The results displayed by the first SELECT are now:
ID    RowNo  Value
2     1      45.002     2      88.002     3      28.00
Wait a minute.  What happened to the rows for IDs 1 and 3?  Holy, moldy guacamole!  It’s a good thing we wrapped our test MERGE in a transaction before we ran that nasty bit of work in Production!  Fortunately our second SELECT shows that our original data remains intact.
The problem of course is that when you say NOT MATCHED BY SOURCE, the source only contains records for ID=2, which as it turns out is what we’re left with.  The result is to completely replace the target with the contents of the source.

A Simple Approach to Avoid the Hazard

Fortunately there’s a simple solution to make our MERGE operate the way we want it to, which is to only delete the unmatched records (based on ID) that are within our source.  The answer is to do the MERGE to a Common Table Expression (CTE) or a VIEW that limits the row set in the target.  Here’s how to do that:
BEGIN TRANSACTION T1;WITH TargetRows AS
 (
     SELECT a.ID, RowNo, Value
     FROM #Test1 a
     INNER JOIN (
         SELECT ID
         FROM #Test2
         GROUP BY ID) b
         ON a.ID = b.ID
     )
 MERGE TargetRows t   -- Target
 USING #Test2 s       -- Source
 ON t.ID = s.ID AND t.RowNo = s.RowNo
   WHEN MATCHED 
     THEN
       UPDATE SET Value = s.Value
   WHEN NOT MATCHED   -- Target
     THEN
       INSERT (ID, RowNo, Value)
         VALUES (s.ID, s.RowNo, s.Value)
 WHEN NOT MATCHED BY SOURCE 
     THEN
       DELETE;SELECT
  *
 FROM #Test1
 ORDER BY ID, RowNo;ROLLBACK TRANSACTION T1;SELECT
  *
 FROM #Test1
 ORDER BY ID, RowNo;
Now we see our results displaying just what the doctor ordered:
ID    RowNo  Value
1     1      25.001     2      32.002     1      45.002     2      88.002     3      28.003     1      15.003     2      99.003     3      54.00
The rows for IDs 1 and 3 remain intact and the rows for ID=2 consist of only the RowNos that are in our source (i.e., RowNo=4 was deleted).
Whew!  What a relief!

Conclusion

Beware the hazards of using NOT MATCHED BY SOURCE because untested queries could land you in some seriously hot water when it deletes all the rows in your target table unexpectedly!
When you need to use it, make sure you only apply the MERGE to target rows that are within the scope of your source, so the operation doesn’t exceed its mandate.  Do that by either merging through a CTE or through a VIEW.
And don’t forget to create and rollback a transaction around the DML you’re testing!  That's especially good advice anytime you’re deleting rows.  Nothing can be more annoying and frustrating to have to restore, even in a test system.
In the end, enjoy the benefits of the new MERGE statement.  No longer do you need to worry about other transactions sneaking in and polluting your target row set while you execute multiple DML statements. 
I'd like to thank all of the interested readers for their time and attention and we sincerely hope that we've helped at least one of you to avoid an embarrassing incident when using MERGE.