USE tempdb;
Go
-- Create a table with case-insenstive collation
CREATE TABLE CaseTest ( 
        Col1 varchar( 30 ) COLLATE SQL_Latin1_General_CP1_CI_AI
);
go
-- Create an index for searches
CREATE CLUSTERED INDEX IX_CaseTest_Col1 ON CaseTest( col1 ) ;
go
INSERT INTO CaseTest VALUES( 'Test String.' );
Go
/*
        How to perform case-sensitive searches on the column?
*/
/*
        Method #1: ANSI SQL compliant solution. The COLLATE clause can be used
        in the comparison / WHERE clause. But this precludes the use of the 
        index & hence the search needs to be optimized. This can be done by 
        providing redundant SARGs in the WHERE clause that will use the index &
        also perform the case-sensitive matching.
                   This is a very powerful & flexible solution that allows you to
        change the collation dynamically & perform searches.
*/
SET SHOWPLAN_TEXT ON;
GO
DECLARE @SearchStr varchar( 30 );
SET @SearchStr = 'test string.';

-- First, show the unoptimized method using the COLLATE clause alone:
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 COLLATE SQL_Latin1_General_CP1_CS_AS = @SearchStr;
/*
  |--Index Scan(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1_BinaryCheckSum] AS [t1]),
     WHERE:(Convert([t1].[Col1])=Convert([@SearchStr])))
*/

-- Now, use the redundant SARG in the WHERE clause to force an index seek
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr And
      t1.Col1 COLLATE SQL_Latin1_General_CP1_CS_AS = @SearchStr;
/*
  |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1] AS [t1]),
     SEEK:([t1].[Col1]=Convert([@SearchStr])),  WHERE:(Convert([t1].[Col1])=Convert([@SearchStr])) ORDERED FORWARD)
*/

SET @SearchStr = 'Test String.';
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr And
      t1.Col1 COLLATE SQL_Latin1_General_CP1_CS_AS = @SearchStr;
GO

/*
        Method #2: T-SQL specific solution. This method uses the SQL2000 specific
        BINARY_CHECKSUM function to perform case-sensitive searches. The binary checksum
        value will differ based on the string value & hence is different for each string.
*/       
DECLARE @SearchStr varchar( 30 );
SET @SearchStr = 'test string.';

-- First, show the unoptimized method using the COLLATE clause alone:
SELECT * FROM CaseTest AS t1
WHERE BINARY_CHECKSUM( t1.Col1 ) = BINARY_CHECKSUM( @SearchStr );

-- Now, use the redundant SARG in the WHERE clause to force an index seek
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr And
      BINARY_CHECKSUM( t1.Col1 ) = BINARY_CHECKSUM( @SearchStr );

SET @SearchStr = 'Test String.';
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr And
      BINARY_CHECKSUM( t1.Col1 ) = BINARY_CHECKSUM( @SearchStr );
GO
SET SHOWPLAN_TEXT OFF;
GO

/*
        Now let's take this approach a step further & use the SQL2000 feature of
        defining indexes on computed columns to optimize these searches. Modify
        the table 'CastTest'
*/
ALTER TABLE CaseTest ADD Col1BinChkSum AS ( BINARY_CHECKSUM( Col1 ) );
CREATE NONCLUSTERED INDEX IX_CaseTest_Col1_BinaryCheckSum ON CaseTest( Col1BinChkSum );
GO
-- Add one more row
INSERT INTO CaseTest VALUES( 'test string.' );
-- View the rows from 'CaseTest' with the new computed column
SELECT * FROM CaseTest;
/*
Col1                           Col1BinChkSum 
------------------------------ ------------- 
Test String.                      1412553240 
test string.                      1949555224 
*/
GO

SET SHOWPLAN_TEXT ON;
GO
DECLARE @SearchStr varchar( 30 );
SET @SearchStr = 'test string.';

-- The usual case-insensitive search:
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr;
/*
  |--Compute Scalar(DEFINE:([t1].[Col1BinChkSum]=binary_checksum([t1].[Col1])))
       |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1] AS [t1]), 
          SEEK:([t1].[Col1]=Convert([@SearchStr])) ORDERED FORWARD)
*/

-- Search using the computed binary checksum value
SELECT * FROM CaseTest AS t1
WHERE t1.Col1BinChkSum = BINARY_CHECKSUM( @SearchStr );
/*
  |--Index Seek(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1_BinaryCheckSum] AS [t1]),
     SEEK:([t1].[Col1BinChkSum]=binary_checksum([@SearchStr])) ORDERED FORWARD)
*/

SET @SearchStr = 'Test String.';
SELECT * FROM CaseTest AS t1
WHERE t1.Col1BinChkSum = BINARY_CHECKSUM( @SearchStr );
/*
  |--Index Seek(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1_BinaryCheckSum] AS [t1]),
     SEEK:([t1].[Col1BinChkSum]=binary_checksum([@SearchStr])) ORDERED FORWARD)
*/
GO

/*
        Method #3: Another ANSI compliant solution.
*/
DECLARE @SearchStr varchar( 30 );
SET @SearchStr = 'test string.';

-- Convert to a binary pattern & perform the case-sensitive search.
-- The CAST function precludes the use of the index as revealed in the SHOWPLAN output.
SELECT * FROM CaseTest AS t1
WHERE CAST( t1.Col1 AS varbinary( 10 ) ) = CAST( @SearchStr AS varbinary( 10 ) );
/*
  |--Index Scan(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1_BinaryCheckSum] AS [t1]),
     WHERE:(Convert([t1].[Col1])=Convert([@SearchStr])))
*/

SET @SearchStr = 'Test String.';
-- Optimized version of the search:
SELECT * FROM CaseTest AS t1
WHERE t1.Col1 = @SearchStr And
      CAST( t1.Col1 AS varbinary( 10 ) ) = CAST( @SearchStr AS varbinary( 10 ) );
/*
       |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[CaseTest].[IX_CaseTest_Col1] AS [t1]),
          SEEK:([t1].[Col1]=Convert([@SearchStr])),  WHERE:(Convert([t1].[Col1])=Convert([@SearchStr])) ORDERED FORWARD)
*/
GO
SET SHOWPLAN_TEXT OFF;
GO

DROP TABLE CaseTest;
GO
This page was last updated on May 01, 2006 04:28 PM.