Tag Archives: HowTo

All topics “HowTo” in Microsoft SQL Server.

#0359 – SQL Server – Script to reset a SQL Login


A few days ago, we were changing the passwords of SQL Logins on our development SQL Server instances.

Because IT installed them and our Windows credentials were given sysadmin fixed server role access, we had hardly ever used the SQL logins. The result was that we had forgotten the original passwords for many of these instances. We therefore wrote the following script to reset the logins and I thought it to be a script that many of you may find useful.

The script below does the following:

  1. Ensures that the SQL Server instances uses Mixed mode authentication (i.e. both Windows and SQL Logins are allowed)
  2. Ensures that the login referenced by the user is a SQL Login
  3. Ensures that the login is enabled or it is disabled and the user has chosen to activate users
  4. A valid SQL login exists with the name provided in the script
  5. Maintains the properties: MUST_CHANGE, CHECK_POLICY, CHECK_EXPIRATION

Disclaimer: This script are provided “as-is” and without warranty. Please use this only on your development and QA environments after thorough testing.

DECLARE @loginToResetPassword NVARCHAR(255) = N'sa';
DECLARE @newPasswordForLogin NVARCHAR(255) = N'Pa$$w0rd';
DECLARE @enableLoginIfDisabled BIT = 1;

/*********** NO USER CONFIGURABLE CODE BEYOND THIS POINT!! ****************/
SET NOCOUNT ON;
--Declarations of internal variables
BEGIN
    DECLARE @sqlStmnt NVARCHAR(MAX);
    DECLARE @loginIsMustChange INT;  --Per MSDN, this is an INT
    DECLARE @loginIsCheckPolicy BIT; --This will be computed based on the value of DaysUntilExpiration
    DECLARE @serverName NVARCHAR(255) = CAST(SERVERPROPERTY(N'ServerName') AS NVARCHAR(255));
END

--Validations
BEGIN
    --Check for mixed mode authentication
    IF (ISNULL(SERVERPROPERTY(N'IsIntegratedSecurityOnly'),1)=1)
    BEGIN
        PRINT N'<<<< INFO >>>> Environment uses Integrated Security (Windows Authentication) only.';
        PRINT SPACE(14) + N'Login passwords cannot be reset when Integrated Security is used.';
        GOTO SCRIPT_END;
    END

    IF NOT EXISTS (SELECT * 
                   FROM sys.server_principals AS sp 
                   WHERE sp.type = N'S'        --SQL Logins only
                     AND ((sp.is_disabled = 1 AND @enableLoginIfDisabled = 1) OR sp.is_disabled = 0)
                     AND sp.name = @loginToResetPassword
                  )
    BEGIN
        PRINT N'<<<< INFO >>> Login reset cannot be performed because of one of the following reasons:';
        PRINT SPACE(14) + N'1. Login is not a valid SQL Login';
        PRINT SPACE(14) + N'2. The login is disabled and user has chosen not to enable it';
        PRINT SPACE(14) + N'3. The login does not exist';
        GOTO SCRIPT_END;
    END
END

--If we got here, it means that the login is valid, and we can reset it
BEGIN
    --We begin by collecting the login properties
    BEGIN
        --If DaysUntilExpiration = 0, login is either expired or it will expire the day we are running this query 
        --                       = -1, the local security policy in Windows never expires the password
        --                       = NULL the CHECK_POLICY or CHECK_EXPIRATION is OFF for the login 
        --                         or the OS does not support password policies
        SELECT @loginIsMustChange = CAST(LOGINPROPERTY(@loginToResetPassword,N'IsMustChange') AS INT),
               @loginIsCheckPolicy = CASE WHEN LOGINPROPERTY(@loginToResetPassword,N'DaysUntilExpiration') IS NULL THEN 0
                                          ELSE 1
                                          END;  
    END

    --Now we reset the password to the one chosen by the user
    SELECT @sqlStmnt = N'ALTER LOGIN ' + @loginToResetPassword + N' WITH PASSWORD = ''' + @newPasswordForLogin + '''';

    IF (@loginIsMustChange=1)
        SELECT @sqlStmnt += N' MUST_CHANGE';

    IF (@loginIsCheckPolicy=0)
        SELECT @sqlStmnt += N', CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF';
    ELSE
        SELECT @sqlStmnt += N', CHECK_POLICY = ON, CHECK_EXPIRATION = ON';

    IF (@enableLoginIfDisabled = 1)
        SELECT @sqlStmnt += N'; ALTER LOGIN '+ @loginToResetPassword + N' ENABLE ;'

    ----Debug Point
    --SELECT @sqlStmnt;

    --Perform the update
    BEGIN TRY
        EXEC master.sys.sp_executesql @sqlStmnt = @sqlStmnt;

        PRINT N'<<<< INFO >>>> Password for login: ' + @loginToResetPassword + N' has been reset on SQL Server instance: ' + @serverName;
    END TRY
    BEGIN CATCH
        SELECT ERROR_NUMBER() AS ErrorNumber,
               ERROR_SEVERITY() AS ErrorSeverity,
               ERROR_STATE() AS ErrorState,
               ERROR_MESSAGE() AS ErrorMessage; 
        PRINT N'<<<< ALERT!! >>>> The process to reset a SQL login''s password failed.';
    END CATCH
END

SCRIPT_END:
PRINT N'<<<< INFO >>>> Login reset script completed. Please review statements prior to this for any messages.';
GO

References

Until we meet next time,
Be courteous. Drive responsibly.

#0355 – SQL Server – Implementing Case Sensitive Search in a case Insensitive database – Regular Expressions & PATINDEX


In my previous post (#354)on implementing case sensitive search in a database that was case insensitive, I had presented one possible implementation approach that used the COLLATE keyword to temporary convert the column of interest to a case sensitive collation.

I had mentioned that the mechanism internally used the CONVERT() function with the style parameter = 0, which means that it was performing a binary to character conversion and then executing the comparison, resulting in a performance overhead to the system.

As I was asking for feedback from friends and colleagues, I received a couple of interesting ideas, the top 2 being:

  • Regular Expressions
  • PATINDEX

The reason that these caught my attention is quite simple – logic would say that they have to be natively case sensitive – one is a regular expression (which is case sensitive in other programming languages) and the other looks for a pattern within the supplied sting. However, there were serious doubts on whether the underlying column collation will have any effect on the behaviour of these options or not. I therefore decided to test out both the options.

Generating the Test Data

Let us create the same test table that was used in the previous post and also allow it to use the same case insensitive collation as the AdventureWorks2012 database.

USE AdventureWorks2012;
GO

--Step 01: Create test data
IF OBJECT_ID('dbo.Product','U') IS NOT NULL
    DROP TABLE dbo.Product;
GO
CREATE TABLE dbo.Product 
    (ProductId INT NOT NULL IDENTITY(1,1),
     ProductName VARCHAR(100) NOT NULL,
     VendorName VARCHAR(100)
    );
GO

--Add some test data
INSERT INTO dbo.Product (ProductName, VendorName)
VALUES ('Cycles','Contosso'),
       ('cBike', 'AdventureWorks'),
       ('SomeOtherProduct','SomeOtherVendor');
GO

Using Regular Expressions

Now, the regular expression to execute a search for all products whose name begins with “c” (small case) is “[c]%”.

Let me try to use this regular expression in my search query:

USE AdventureWorks2012;
GO
SELECT ProductId,
       ProductName,
       VendorName
FROM dbo.Product 
WHERE ProductName LIKE '[c]%';
GO

The result is shown in the screenshot below. Records for all products beginning with the letter “c” were returned – irrespective of the case.

image

Using PATINDEX

The second search mechanism that I will be evaluating is the PATINDEX, which basically returns the index or the position where a particular character pattern is found in a given string. If the pattern is not found, the value returned is 0.

USE AdventureWorks2012;
GO
SELECT ProductId,
       ProductName,
       VendorName
FROM dbo.Product 
WHERE PATINDEX('c%',ProductName) > 0;
GO

Again the result is as shown in the screenshot below – a case in-sensitive search was performed by SQL Server.

image

Conclusion

The collation of a particular column determines whether a search on a column will be case sensitive or  case in-sensitive. This is not dependent upon the mechanism used for the comparison (i.e. a simple query v/s regular expressions v/s pattern matching).

Do you have any ideas on implementing  case sensitive search on a case in-sensitive database column? If so, please share in the blog comments.

Further Reading

Until we meet next time,
Be courteous. Drive responsibly.

#0354 – SQL Server – Implementing case sensitive search in a case insensitive database


Searching data from a column is a very common requirement – but with unique variants in each application. Recently, I came across one such requirement:

  1. Underlying database, table and columns use a case in-sensitive (CI) collation
  2. Application must be able to switch between a case sensitive v/s a  case insensitive comparison

The ability to switch between a case sensitive and case insensitive comparison is the challenge because it needs to be done on a database that inherently does not discriminate between the two.

In this post, I will present one method of implementing this requirement. If you have any others that you would like to share, please feel free to do so in the comments area.

Temporarily using Case Sensitive Collations

The most common implementation pattern to realize the case sensitive search requirement is to temporarily use a collation that is case sensitive as shown below.

To begin with, let us create a test table and insert some fictional test data.

USE AdventureWorks2012;
GO

--Step 01: Create test data
IF OBJECT_ID('dbo.Product','U') IS NOT NULL
    DROP TABLE dbo.Product;
GO
CREATE TABLE dbo.Product 
    (ProductId INT NOT NULL IDENTITY(1,1),
     ProductName VARCHAR(100) NOT NULL,
     VendorName VARCHAR(100)
    );
GO

--Add some test data
INSERT INTO dbo.Product (ProductName, VendorName)
VALUES ('Cycles','Contosso'),
       ('cBike', 'AdventureWorks'),
       ('SomeOtherProduct','SomeOtherVendor');
GO

Next, let us fetch the collation of the database where our test table resides. You may also want to use the system stored procedure (sp_help) to study the collation of the table columns (which by default would be same as that of the database in which the table resides). On my server, the collation was – [SQL_Latin1_General_CP1_CI_AS] which is case in-sensitive (notice the CI in the collation name).

--Step 02: Check the database collation
USE AdventureWorks2012;
GO
SELECT DATABASEPROPERTYEX(N'AdventureWorks2012',
                          N'Collation');
GO
--For AdvenutreWorks2012 
--SQL_Latin1_General_CP1_CI_AS

--Check the collation of the table columns
sp_help [dbo.Product];
GO

Now, attempt to query the table for all products whose names begin with the letter ‘c’ (small case). To do so, we need to temporarily COLLATE the column – dbo.Product.ProductName to a case sensitive collation (in my case, this is SQL_Latin1_General_CP1_CS_AS).

USE AdventureWorks2012;
GO
SELECT ProductId,
       ProductName,
       VendorName
FROM dbo.Product 
WHERE ProductName 
    COLLATE SQL_Latin1_General_CP1_CS_AS
    LIKE 'c%';
GO

An observation

As can be seen from the image below, desired results are obtained. However, if one has the actual execution plan opened up, an interesting observation can be made if we look at the properties of the SELECT operation.

Results of case-sensitive comparison

image

We can observe that SQL Server internally uses the CONVERT function with a style of 0. Per Books On Line (see references below), this indicates a binary to character conversion. This conversion is performed for all records in the table, which in-turn affects performance.

Conclusion

Because we have the overhead of applying a conversion on all rows of a particular table, this method should be used only when the column cannot be modified to permanently use a case sensitive collation and a business need exists to provide both case sensitive & insensitive search.

Further Reading

Until we meet next time,
Be courteous. Drive responsibly.

#0353 – SQL Server – Locate an object in a SQL Server Instance (using documented features)


A while back, I wrote a post on locating an object in a SQL Server instance (post #351). One of the items I received as part of the feedback was to implement the logic using documented (and therefore supported) features.

As you may know, I had used the sp_MSforeachDB system stored procedure in my earlier post. This is an undocumented procedure and therefore the recommendation is to assume that it is unsupported and not use it in production environments. In today’s post, I present before you a script that achieves the same objective (i.e. locate an object in a SQL Server instance), but using fully documented (and therefore fully supported) features.

Improvements over the script using sp_MSforeachDB:

  • Uses fully documented features
  • Does a comparison on both – the schema name and the object name
  • Search is only limited to user databases by default (system databases can be easily reintroduced by commenting out appropriate filter conditions in the query below)

The entire script is provided below for your reference:

USE master;
GO

SET NOCOUNT ON;

--Declarations
BEGIN
    --User to fill in
    DECLARE @schemaToSearch NVARCHAR(255) 
                            = N'dbo';
    DECLARE @objectToSearch NVARCHAR(255) 
                            = N'uspGetBillOfMaterials';

    --Internal variables
    DECLARE @databaseInformation TABLE 
                (DatabaseId INT NOT NULL,
                 DatabaseName NVARCHAR(255),
                 IsProcessed BIT NOT NULL
                );
    DECLARE @databaseId INT = 0;
    DECLARE @databaseName NVARCHAR(255) = N'';
    DECLARE @sqlStmnt NVARCHAR(1000) = N'';
END

--Fetch all user databases into temporary table variable
--for convenience
BEGIN
    INSERT INTO @databaseInformation (DatabaseId, 
                                      DatabaseName, 
                                      IsProcessed)
    SELECT sdb.database_id AS DatabaseId,
           sdb.name AS DatabaseName,
           0 AS IsProcessed
    FROM sys.databases AS sdb 
    WHERE CAST((CASE WHEN SUBSTRING(sdb.name,0,14) 
                          IN ('master',
                              'msdb',
                              'model',
                              'tempdb',
                              'ReportServer$') 
                          THEN 1 
                     ELSE sdb.is_distributor 
                END) AS BIT) = 0;
END

--Begin processing one database after another
WHILE EXISTS (SELECT dbi.DatabaseId 
              FROM @databaseInformation AS dbi 
              WHERE dbi.IsProcessed = 0
             )
BEGIN
    SELECT TOP 1
           @databaseId = dbi.DatabaseId,
           @databaseName = dbi.DatabaseName
    FROM @databaseInformation AS dbi
    WHERE dbi.IsProcessed = 0;

    --Build the statement to fetch the objectId of an object 
    --and then check for it's existence
    SET @sqlStmnt = N'USE ' + QUOTENAME(@databaseName) + N';
    DECLARE @objectId INT;
    SET @objectId = OBJECT_ID(''' 
                + QUOTENAME(@schemaToSearch) 
                + N'.' 
                + QUOTENAME(@objectToSearch) 
                + N''')
    IF EXISTS (SELECT *
               FROM sys.objects AS so
               WHERE so.is_ms_shipped = 0
               AND so.object_id = @objectId
             )
    BEGIN
        SELECT DB_NAME() AS DatabaseName;
    END;'

    --Execute the query
    --PRINT @sqlStmnt;
    EXEC sp_executesql @sqlStmnt = @sqlStmnt;

    --Mark the database as done
    UPDATE dbi
    SET dbi.IsProcessed = 1
    FROM @databaseInformation AS dbi
    WHERE dbi.DatabaseId = @databaseId
      AND dbi.DatabaseName = @databaseName; 

END
GO

Until we meet next time,
Be courteous. Drive responsibly.

#0351 – SQL Server – Locate an object in a SQL Server instance


Interacting with people on a SQL Server forum is always something that I look forward to. It gives me an insight into the challenges that are being faced out in the field – some of which I may have already encountered & addressed myself in the past, but thought it to be something trivial. A recent question was before me which prompted this post.

A friend of mine was involved in a knowledge transfer of a legacy product from one team to another. Documentation was insufficient and most of the product knowledge was in the brains of the people who had originally developed the system. One of the points covered in the transfer that all databases of that product would have a particular database object (a table or a stored procedure). If that object did not exist, the database was not a valid database for that product. The question therefore was:

“Given a SQL Server instance, is it possible to check for existence of a particular database object across all databases in the instance?”

The simple answer is yes.

However, the answer involves the use of a undocumented (and therefore unsupported) system stored procedure – sp_MSforeachdb.

USE master ;
GO
EXEC sp_MSforeachdb 
       N'USE ?;
         IF EXISTS (SELECT *
                    FROM sys.objects AS so
                    WHERE so.is_ms_shipped = 0
                      AND so.name = ''uspGetBillOfMaterials''
                   )
            SELECT ''?'' AS DatabaseName;' ;
GO

/*RESULTS

DatabaseName
------------------
AdventureWorks2012

*/

Disclaimer: Because of the use of unsupported system stored procedure, it is recommended that the script provided below is limited to use in the development and quality assurance environments only.

Further Reading

Until we meet next time,

Be courteous. Drive responsibly.