#0171-SQL Server-Scripts-Backup and Restore a database over a network using UNC paths


I am sure this post would ring a bell with most development teams. Development servers are generally constrained for space. Whenever a development team requests more hardware, be it in terms of a new machine or even additional storage space, I am sure that most IT departments would have told them “Why do you need all these servers? You already have n number of servers, which are more than the number of people on the team!”

Ultimately, this results in lack of disk space to store both a database and it’s backup on the same drive. In fact, we had this issue some time ago, wherein we had to restore a reasonably large database 50GB+. Both the backup and the database simply could not reside on the same server due to disk space issues.

So, we decided to place the backup file on a network share, map it to a drive on the server and restore the database from there. However, that just wouldn’t work. Attempting to restore through the SSMS results in the following error:

image

But, as they say – “Where there is a will, there is a way.” The core issue here is that mapped network drives are not supported by SQL Server. UNC paths however, are a different story.

SQL Server fully supports backups & restores over UNC paths. There are 2 methods that you can use:

  1. Directly type the UNC path where the backup needs to be taken/restored from
  2. Create a backup device pointing to the UNC path and then backup to/restore from this device

After looking at how easy and convenient it is to use UNC paths with SQL Server, our entire team has started using them. Here are a some scripts that you may be interested in:

  1. Backup Databases across the network: http://beyondrelational.com/modules/30/scripts/485/scripts/15042/backup-database-across-the-network.aspx
  2. Restore Databases across the network: http://beyondrelational.com/modules/30/scripts/485/scripts/15043/restore-databases-from-a-backup-file-across-a-network.aspx

There are a couple of security considerations that you need to take care about. They are available in the following MS KB article: http://support.microsoft.com/kb/207187

These scripts are available in the Scripts module on BeyondRelational.com (http://beyondrelational.com/modules/30/default.aspx?s=stream&tab=scripts). If you haven’t been to the Scripts module page, do pay it a visit – you will find a lot of useful scripts that you can customize for your use.

Until we meet next time,

Be courteous. Drive responsibly.

#0170-SQL Server-Deprecated Features-Column Alias defined by a string enclosed in quotation marks


I recently wrote a piece about the impact of table and column aliases on query performance and plan cache size. As I was writing the post, I recalled that there are multiple ways in which column aliases can be defined in a T-SQL query, depending upon the developer’s preference. However, one of these methods is marked for deprecation in one of the future releases of Microsoft SQL Server (it is still a valid method for SQL Server 2012, so there is no cause of worry in the immediate future).

The available methods for column aliasing and their status is shown in the table below:

Format Status
‘column_alias’ = expression Deprecated
expression AS column_alias Active
expression AS ”column_alias” Active
expression AS [column_alias] Active
[column_alias] = expression Active
expression AS “column_alias” Active

All of these methods are demonstrated in the query below:

USE AdventureWorks2012;
GO

SELECT                                            --           Format            ;   Status
    "Employee Birth Date" = Employee.BirthDate,   -- "column_alias" = expression ; Deprecated
    Employee.HireDate AS JoiningDate,             -- expression AS column_alias  ; Active
    Employee.BusinessEntityID AS "EmployeeId",    -- expression AS "column_alias"; Active
    Employee.OrganizationLevel AS [Org. Level],   -- expression AS [column_alias]; Active
    [Salary Flag] = Employee.SalariedFlag,        -- [column_alias] = expression ; Active
    Employee.SickLeaveHours AS "SickHours",       -- expression AS “column_alias"; Active
    "VacationHours" = Employee.VacationHours      -- "column_alias" = expression ; Deprecated
FROM HumanResources.Employee AS Employee;
GO

My recommendations

My recommendations around any such feature is:

  • To have one standard for the entire organization/product
  • The element adopted as the standard should not have been marked for deprecation for at least the next 2 major releases of Microsoft SQL Server
  • Finally, and the most important consideration is that the feature should help the team become more productive and efficient

I personally use either expression AS [column_alias] OR [column_alias] = expression methods.

Before you leave, do share your preferred format for column aliasing.

Until we meet next time,

Be courteous. Drive responsibly.

#0169-SQL Server-Table/Column Aliasing does not affect query performance-Part 2


Earlier this week, I wrote a post on whether table and column aliasing affects query performance or not (http://beyondrelational.com/modules/2/blogs/77/Posts/14948/0168-sql-server-tablecolumn-aliasing-does-not-affect-query-performance.aspx) . We were able to determine that overall time-wise, both queries were performing equally well. However, one of the comments received as part of the feedback was to also include results of the following in addition to the use of simply the execution plan.

  • Statistics IO
  • Statistics Profile
  • Actual CPU times

I therefore repeated the tests after modifying the query a little bit to include the necessary SET statements and setting the buffer to a clean state.

IMPORTANT NOTE: In this script, we use 2 DBCC commands – FREEPROCCACHE and DROPCLEANBUFFERS to simulate a cold buffer condition, which is generally achieved when SQL Server restarts. Doing so indiscriminately can have severe performance implications. Please do not execute the scripts shown in this example in your production or quality assurance environments.

USE AdventureWorks2012
GO

/*************************************************************************************************************
                                      IMPORTANT WARNING
--------------------------------------------------------------------------------------------------------------
DBCC FREEPROCCACHE and DROPCLEANBUFFERS are used to simulate a cold buffer condition, 
that is generally achieved when SQL Server restarts. 

Doing so indiscriminately can have severe performance implications. 

Please do not execute the scripts shown in this example in your production or quality assurance environments.

The author, this site or Microsoft are not responsible
for any damage caused by indiscriminate use of these DBCC commands
*************************************************************************************************************/
DBCC FREEPROCCACHE WITH NO_INFOMSGS
GO
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS
GO

SET STATISTICS IO ON
SET STATISTICS PROFILE ON

SELECT 'Non-Aliased Query',
       HRE.BusinessEntityID,
       HRE.LoginID,
       HRE.NationalIDNumber,
       HRE.OrganizationNode,
       HRE.OrganizationLevel,
       HRE.JobTitle,
       HRE.BirthDate,
       HRE.MaritalStatus,
       HRE.Gender,
       HRE.HireDate,
       HRE.SalariedFlag,
       HRE.VacationHours,
       HRE.SickLeaveHours,
       HRE.CurrentFlag,
       HRE.rowguid,
       HRE.ModifiedDate,
       Dept.DepartmentID,
       Dept.ShiftID,
       Dept.StartDate,
       Dept.EndDate,
       Dept.ModifiedDate,
       HumanResources.Department.Name,
       HumanResources.Department.GroupName,
       HumanResources.Department.ModifiedDate,
       HumanResources.Shift.Name,
       HumanResources.Shift.ShiftID,
       HumanResources.Shift.StartTime,
       HumanResources.Shift.EndTime,
       HumanResources.Shift.ModifiedDate
FROM HumanResources.Employee AS HRE 
INNER JOIN HumanResources.EmployeeDepartmentHistory AS Dept ON HRE.BusinessEntityID = Dept.BusinessEntityID
INNER JOIN HumanResources.Department ON Dept.DepartmentID = HumanResources.Department.DepartmentID 
INNER JOIN HumanResources.Shift ON Dept.ShiftID = HumanResources.Shift.ShiftID
GO

DBCC FREEPROCCACHE WITH NO_INFOMSGS
GO
DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS
GO

SELECT 'Aliased Query',
       EmployeeID = HRE.BusinessEntityID,
       LoginID = HRE.LoginID,
       NationalIDNumber = HRE.NationalIDNumber,
       OrganizationNode = HRE.OrganizationNode,
       OrganizationLevel = HRE.OrganizationLevel,
       JobTitle = HRE.JobTitle,
       BirthDate = HRE.BirthDate,
       MaritalStatus = HRE.MaritalStatus,
       Gender = HRE.Gender,
       HireDate = HRE.HireDate,
       SalariedFlag = HRE.SalariedFlag,
       VacationHours = HRE.VacationHours,
       SickLeaveHours = HRE.SickLeaveHours,
       CurrentFlag = HRE.CurrentFlag,
       rowguid = HRE.rowguid,
       EmployeeModifiedDate = HRE.ModifiedDate,
       DepartmentID = Dept.DepartmentID,
       ShiftID = Dept.ShiftID,
       StartDate = Dept.StartDate,
       EndDate = Dept.EndDate,
       DepartmentModifiedDate = Dept.ModifiedDate,
       DepartmentName = HumanResources.Department.Name,
       GroupName = HumanResources.Department.GroupName,
       DepartmentModifiedDate = HumanResources.Department.ModifiedDate,
       ShiftName = HumanResources.Shift.Name,
       ShiftID = HumanResources.Shift.ShiftID,
       StartTime = HumanResources.Shift.StartTime,
       EndTime = HumanResources.Shift.EndTime,
       ShiftModifiedDate = HumanResources.Shift.ModifiedDate
FROM HumanResources.Employee AS HRE 
INNER JOIN HumanResources.EmployeeDepartmentHistory AS Dept ON HRE.BusinessEntityID = Dept.BusinessEntityID
INNER JOIN HumanResources.Department ON Dept.DepartmentID = HumanResources.Department.DepartmentID 
INNER JOIN HumanResources.Shift ON Dept.ShiftID = HumanResources.Shift.ShiftID
GO

SET STATISTICS IO OFF
SET STATISTICS PROFILE OFF

Running the query gives us the following results:

  • Query output
  • After each query’s output, we receive a result set containing the performance statistics for the given query
  • In the “Messages” tab, the IO statistics for the query execution are available

Let us evaluate each one-by-one:

Statistics I/O

We started with clean buffers for both queries to avoid any impact to the I/O statistics arising out of data already being available in the buffer (i.e. a “logical” read). The statistics I/O for both queries yields the same output, which is:

Table ‘Worktable’. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table ‘Employee’. Scan count 1, logical reads 9, physical reads 0, read-ahead reads 7, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table ‘EmployeeDepartmentHistory’. Scan count 1, logical reads 4, physical reads 1, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table ‘Shift’. Scan count 1, logical reads 2, physical reads 1, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table ‘Department’. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Here’s the screenshot:

Output of STATISTICS IO is the same for aliased & non-alised queries

Statistics Profile

The statistics Profile is nothing but a textual representation of the query profile information that can be obtained from the execution plan also. Besides many others, the output contains the following values of interest:

  1. The number of rows processed by each operation
  2. The number of times a particular operation was executed
  3. Operator used
  4. Estimated Rows
  5. Estimated CPU
  6. Average Row Size
  7. Total Subtree cost

To compare the result sets, I simply used Excel. You can use any file comparison tool of your choice.

Comparison of the Profiler statistics for aliased v/s non-alised queries shows that apart from a slight increase in average row size, the values are the same.

Comparing the output reveals that the two result sets are identical except for:

  1. The obvious difference in the query text
  2. A slight increase in the Average Row Size is observed for the aliased query (which can be attributed to the increased number of characters used for aliasing)
  3. The Estimated CPU time and the Total Subtree Cost remains the same

Summarizing

Combining the results of the experiments above and the observations from my previous post, we can summarize that:

  Affected By Aliasing?
Query Performance – IO No
Query Performance – CPU No
Query Performance – Avg. Row Size Possible
Query Performance – Time No
Plan Cache Size No
Plan reuse Possible

What have been your observations related to aliasing? Do leave a note as you go.

Until we meet next time,

Be courteous. Drive responsibly.

#0168-SQL Server-Table/Column Aliasing does not affect query performance


It’s interesting to know about and think over some of the old-wives tales surrounding SQL Server and it’s features. One such tale is that the use of table and column aliases affects query performance.


The very purpose of table and column aliases is to improve the read-ability of the query, and nothing else. They help in eliminating ambiguity, but do not in any way impact the internal workings of SQL Server, and today I will be proving just that.


My example will be for using column aliases. You can work out a similar example for table aliases.


The Example


Below are two straight-forward T-SQL queries, intended for nothing else but to demonstrate the theory mentioned above. The first query does not use column aliases, whereas the second query uses them.


IMPORTANT NOTE: In this script, we use 2 DBCC commands – FREEPROCCACHE and DROPCLEANBUFFERS to simulate a cold buffer condition, that is generally achieved when SQL Server restarts. Doing so indiscriminately can have severe performance implications. Please do not execute the scripts shown in this example in your production or quality assurance environments.

USE AdventureWorks2012
GO

/*************************************************************************************************************
IMPORTANT WARNING
————————————————————————————————————–
DBCC FREEPROCCACHE and DROPCLEANBUFFERS are used to simulate a cold buffer condition,
that is generally achieved when SQL Server restarts.

Doing so indiscriminately can have severe performance implications.

Please do not execute the scripts shown in this example in your production or quality assurance environments.

The author, this site or Microsoft are not responsible
for any damage caused by indiscriminate use of these DBCC commands
*************************************************************************************************************/
DBCC FREEPROCCACHE
GO
DBCC DROPCLEANBUFFERS
GO

SELECT ””””Non-Aliased Query””””,
HRE.BusinessEntityID,
HRE.LoginID,
HRE.NationalIDNumber,
HRE.OrganizationNode,
HRE.OrganizationLevel,
HRE.JobTitle,
HRE.BirthDate,
HRE.MaritalStatus,
HRE.Gender,
HRE.HireDate,
HRE.SalariedFlag,
HRE.VacationHours,
HRE.SickLeaveHours,
HRE.CurrentFlag,
HRE.rowguid,
HRE.ModifiedDate,
Dept.DepartmentID,
Dept.ShiftID,
Dept.StartDate,
Dept.EndDate,
Dept.ModifiedDate,
HumanResources.Department.Name,
HumanResources.Department.GroupName,
HumanResources.Department.ModifiedDate,
HumanResources.Shift.Name,
HumanResources.Shift.ShiftID,
HumanResources.Shift.StartTime,
HumanResources.Shift.EndTime,
HumanResources.Shift.ModifiedDate
FROM HumanResources.Employee AS HRE
INNER JOIN HumanResources.EmployeeDepartmentHistory AS Dept ON HRE.BusinessEntityID = Dept.BusinessEntityID
INNER JOIN HumanResources.Department ON Dept.DepartmentID = HumanResources.Department.DepartmentID
INNER JOIN HumanResources.Shift ON Dept.ShiftID = HumanResources.Shift.ShiftID
GO

SELECT ””””Aliased Query””””,
EmployeeID = HRE.BusinessEntityID,
LoginID = HRE.LoginID,
NationalIDNumber = HRE.NationalIDNumber,
OrganizationNode = HRE.OrganizationNode,
OrganizationLevel = HRE.OrganizationLevel,
JobTitle = HRE.JobTitle,
BirthDate = HRE.BirthDate,
MaritalStatus = HRE.MaritalStatus,
Gender = HRE.Gender,
HireDate = HRE.HireDate,
SalariedFlag = HRE.SalariedFlag,
VacationHours = HRE.VacationHours,
SickLeaveHours = HRE.SickLeaveHours,
CurrentFlag = HRE.CurrentFlag,
rowguid = HRE.rowguid,
EmployeeModifiedDate = HRE.ModifiedDate,
DepartmentID = Dept.DepartmentID,
ShiftID = Dept.ShiftID,
StartDate = Dept.StartDate,
EndDate = Dept.EndDate,
DepartmentModifiedDate = Dept.ModifiedDate,
DepartmentName = HumanResources.Department.Name,
GroupName = HumanResources.Department.GroupName,
DepartmentModifiedDate = HumanResources.Department.ModifiedDate,
ShiftName = HumanResources.Shift.Name,
ShiftID = HumanResources.Shift.ShiftID,
StartTime = HumanResources.Shift.StartTime,
EndTime = HumanResources.Shift.EndTime,
ShiftModifiedDate = HumanResources.Shift.ModifiedDate
FROM HumanResources.Employee AS HRE
INNER JOIN HumanResources.EmployeeDepartmentHistory AS Dept ON HRE.BusinessEntityID = Dept.BusinessEntityID
INNER JOIN HumanResources.Department ON Dept.DepartmentID = HumanResources.Department.DepartmentID
INNER JOIN HumanResources.Shift ON Dept.ShiftID = HumanResources.Shift.ShiftID
GO


Impact on Query Performance


A quick way to compare the performance of two queries is to use the Actual Execution Plan. I therefore set the Actual Execution Plan to ON (press Ctrl + M) and then ran the entire batch (i.e. both queries) together against the AdventureWorks2012 database. The actual execution plan showed an equal – 50/50 split between the two queries.


The Actual Execution plan shows us an equal - 50/50 split between the two test queries - one with column aliasing, the one without


Conclusion 01: Using column aliases does not affect query performance.


Impact on Query Plan Cache


Now that we have derived the conclusion that column aliases do not affect query performance, do they impact the plan cache? We know that query plans are ultimately text, and therefore a query using aliases should have a bigger plan, bloating the plan cache. The Dynamic Management Views provide a powerful way of checking this one out.


I wrote the following query in an attempt to find the query plan size in bytes for a given ad-hoc query, which is:

SELECT cp.size_in_bytes,
st.text,
qs.sql_handle,
qs.plan_handle,
cp.usecounts,
cp.refcounts,
cp.cacheobjtype,
cp.objtype
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
INNER JOIN sys.dm_exec_cached_plans as cp ON qs.plan_handle = cp.plan_handle
WHERE cp.objtype = ””””Adhoc””””
AND st.text LIKE ””””%HumanResources.EmployeeDepartmentHistory%””””

Which gives us:


Screenshot showing the output of the DMV based query, demonstrating that aliasing does not impact the plan cache


As you can see, there is no difference in the cached plan size for both the queries.


Conclusion 02: Column aliasing does not impact the plan cache.


Conclusion 03: Last year, I wrote a piece on how writing your queries using different cases will cause SQL Server to create multiple query plans. Hence, it is advised to have a single, consistent standard towards using aliases in your product.  You can read the post at: Query Plan Re-use: Write your T-SQL queries with the proper case for plan re-use and better query performance


Summarizing


Based on the observations shared above, I made the following conclusions:















  Is affected by aliasing?
Query Performance No
Plan cache size No
Plan reuse Possible

What have been your observations with respect to aliasing? Do you use aliasing as part of your coding standards? Do leave a note as you go.


Until we meet next time,


Be courteous. Drive responsibly.

#0167-SQL Server-A tale (and Query) of using Catalog Views to get Index Maintenance (rebuild/reorganize) History


It’s been quite a while since I narrated a story to the audience. Today, I will narrate a scenario when Catalog Views helped a fellow colleague of mine – exactly the way it happened. Let’s call him Mike.


Mike: How can I check when a particular index was last rebuilt or defragmented? Are there any DMVs or system tables that can help me get this information?


Nakul: There is no direct way to get this information from SQL Server. There is one indirect way I know of, if you are interested.


Mike: Sure, I would like to know about it.


Nakul: Whenever indexes are rebuilt or reorganized, they update the statistics associated with them (vice-versa is not true). Hence, by looking at the date on which these statistics were last updated, you can determine when the index was last rebuilt/defragmented.


Mike: This is really interesting, and I will try it out. Can you share a query that would help me get this information?


Nakul: Sure, you can use a combination of the STATS_DATE() function and the sys.indexes Catalog View to achieve this. Below is a query that you may find useful:

USE AdventureWorks2008R2
GO
SELECT name AS index_name,
STATS_DATE(object_id, index_id) AS statistics_update_date
FROM sys.indexes
WHERE object_id = OBJECT_ID(‘HumanResources.Employee’);
GO

STATS_DATE() alongwith sys.indexes Catalog View can be used to find when an index was last rebuilt/reorganized


At that moment, a friend and colleague, Hardik Doshi (Blog), joined us.


Hardik: What’s up, guys?


Nakul: I was just working with Mike on a query he has. Would you like to share your thoughts on how to check when a particular index was rebuilt or reorganized?


Hardik: SQL Server does not track index rebuilt or defragmentation operation details automatically. You would need to setup trace or create DDL triggers to track these types of operations. However, I agree with your approach to look at the statistics update information to begin with.


Mike: Hmm… I will make it a point to explore DDL triggers today afternoon.


Hardik: I can help you with more detailed information. Would you be interested? The only issue is that the approach uses catalog views, which are marked for deprecation in one of the future SQL Server releases.


Us: Sure! Let’s go through your approach and see how it works out.


Hardik: Okay, here you go – this query will not only give you the last date/time when the index statistics were updated, but also gives the number & percentage of rows changed during the last operation and the associated index name.

USE AdventureWorks2008R2
GO
SELECT ss.name AS SchemaName,
st.name AS TableName,
s.name AS IndexName,
STATS_DATE(s.id, s.indid) AS ‘Statistics Last Updated’,
s.rowcnt AS ‘Row Count’,
s.rowmodctr AS ‘Number Of Changes’,
CASE WHEN s.rowmodctr > 0
THEN CAST ((CAST (s.rowmodctr AS DECIMAL (28, 8)) / CAST (s.rowcnt AS DECIMAL (28, 2)) * 100.0) AS DECIMAL (28, 2))
ELSE 0
END AS ‘% Rows Changed’
FROM sys.sysindexes AS s
INNER JOIN
sys.tables AS st
ON st.[object_id] = s.[id]
INNER JOIN
sys.schemas AS ss
ON ss.[schema_id] = st.[schema_id]
WHERE s.id > 100
AND s.indid > 0
ORDER BY SchemaName, TableName, IndexName

Using sys.sysindexes compatibility view to identify the changes made during an index rebuild/reorganize


Mike: Hardik & Nakul: Thank-you very, very much for helping me out – I think I now have enough material with me to work upon these queries.


Nakul: Hardik, as you rightly mentioned, it would not be a good idea to use catalog views – after all, they are marked for deprecation.


Hardik: I completely agree. One of these days, let’s figure out a way to replace the catalog view – sys.sysindexes used in my query.


Nakul: Deal!


References:



Until we meet next time,


Be courteous. Drive responsibly.