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

Wednesday, November 9, 2011

The Short-Curcuit WHERE Conditional

I challenge myself often to optimize all of my code when possible. One of my challenges was to write stored procedures that execute as part of "ad-hoc" filtering required by applications. For example, say the user CAN filter a grid by 0-6 different fields, such as first name, last name, phone number, zip code, age, and marital status. A stored procedure could execute a select statement that included all fields as parameters:

CREATE PROCEDURE ReadPerson
    @FirstName varchar,
    @LastName varchar,
    @PhoneNumber varchar,
    @ZipCode varchar,
    @Age int,
    @IsMarried bit
    AS
    BEGIN
        SELECT *
        FROM Person p
        WHERE p.FirstName = @FirstName
        AND   p.LastName = @LastName
        AND   p.PhoneNumber = @PhoneNumber
        AND   p.ZipCode = @ZipCode
        AND   p.Age = @Age
        AND   p.IsMarried = @IsMarried
    END
    GO
Which could be executed like this:
Exec ReadPerson 'John', 'Doe', '8005551234', '90210', 50, 1
But now, let's say the user doesn't know the phone number. We now have a dilema. Should we write another stored procedure that eliminates the phone number from the WHERE clause. Not a good idea, since we will probably have to write more stored procedures that provide ALL of the combinations of the filter. We could re-write this stored procedure to interrogate each parameter and "build" a SELECT statement with Dynamic SQL. Not a good idea either, since Dynamic SQL is (1) hard to read, and (2) cannot validate or intellisense table names or columns within the single quotes (''). There are other solutions that are equally bad, but there is one that works just great!

Enter Short-Circuit evaluation! T-SQL (SQL 2008 and above) now perfoms short-circuit evaluations of expressions. But what is "Short-Circuiting"? A brief explanation.

Most high-level programming languages are able to evaluate boolean expressions using an optimization called short-circuiting, which can stop evaluating an expression as soon as the result can be determined.

For example, an "AND" operator can stop evaluating a combined expression as soon as it finds the first expression that evaluates to false.
IF (1 = 1 AND 1 = 5 AND 5 = 5)
In this evaluation, the order of the combined expression is key. The result of the combined expression will obviously be false. However, that last expression { 5 = 5 } is NEVER evaluated! The reason is that an AND operator requires all expressions to evaluate to true. Since the second expression { 1 = 5 } evaluates to false, the combined expression can immediately evalutate to false.

The same short-circuit evaluation occurs with an "OR" operator, that stops evaluating a combined expression as soon as it finds the first expression that evaluates to true.
IF (1 = 1 OR 1 = 5 OR 5 = 5)
Again, the order of the combined expression is key. The result of the combined expression will obviously be true. However, that second and third expressions { 1 = 5 OR 5 = 5 } are NEVER evaluated! The reason is that an OR operator requires only one expressions to evaluate to true. Since the first expression { 1 = 1 } evaluates to true, the combined expression can immediately evalutate to true.

Since Short-Circuit evaluation is now available to us, we can tackle our challenge with a VERY optimized stored procedure. Let's take a look at a re-write of the above stored procedure that uses Short-Circuit evaluation.
CREATE PROCEDURE ReadPerson
    @FirstName varchar,
    @LastName varchar,
    @PhoneNumber varchar,
    @ZipCode varchar,
    @Age int,
    @IsMarried bit
    AS
    BEGIN
        SELECT *
        FROM Person p
        WHERE ((@FirstName = NULL) OR (p.FirstName = @FirstName))
        AND   ((@LastName = NULL) OR (p.LastName = @LastName))
        AND   ((@PhoneNumber = NULL) OR (p.PhoneNumber = @PhoneNumber))
        AND   ((@ZipCode = NULL) OR (p.ZipCode = @ZipCode))
        AND   ((@Age = NULL) OR (p.Age = @Age))
        AND   ((@IsMarried = NULL) OR (p.IsMarried = @IsMarried))
    END
    GO
Which could be executed the same as before:
Exec ReadPerson 'John', 'Doe', '8005551234', '90210', 50, 1
But now adds all different kinds of combinations
-- Returns any record with FirstName is John
    Exec ReadPerson 'John', NULL, NULL, NULL, NULL, NULL
    
    -- Returns any record with FirstName is John and age 50
    Exec ReadPerson 'John', NULL, NULL, NULL, 50, NULL

    -- Returns any record with LastName is Doe and ZipCode is 90210
    Exec ReadPerson NULL, 'Doe', NULL, '90201', NULL, NULL

    
Let's look at why. Check out the first condition in the WHERE clause
WHERE ((@FirstName = NULL) OR (p.FirstName = @FirstName))
Because it is an OR condition, if the first expression (@FirstName = NULL), then the rest of the combined expression (p.FirstName = @FirstName) won't be evaluated. If FirstName is NULL, the results are NOT Filtered by FirstName. However, if the first expression is false (such as when @FirstName is 'John'), then the rest of the combined expression will still be evaulated. Then, the results WILL BE Filtered by all records where FirstName field is 'John'. Since this combined expression is all encapsulated in it's own parenthesis, it is evaluated completely separate from all of the Other conbined expressions that are separated by AND.

Cool, huh???

Tuesday, July 5, 2011

SQL Server Management Studio: Access Remote Database over VPN

Note: This is a copy of the following ORIGINAL LINK on howtogeek.com by an unknown author originally written on 7/31/2008.  I am only republishing this content on my blog because I feel this is a GREAT ARTICLE and I don't know how long it will remain published on the ORIGINAL LINK.


This article is not only great for managing the UserNames and Passwords in mapped drives but also for accessing a remote database with SQL Management Studio over a VPN!



Original Article


Create a Shortcut to the Stored User Names and Passwords Dialog in Windows

If you’ve ever saved a password when connecting to a website that requires authentication, for a remote desktop session or a mapped drive, you might have wondered where those passwords are saved. If you are a long time reader, you already know where, but you might be interested in how to create a shortcut directly to the dialog where you can manage those logons.
You can add this into your folder of useful shortcuts… sure, you might not use it every day, but it’s good to know how to do it.


Create the Shortcut
Right-click on the desktop and choose New \ Shortcut from the menu.

In the location box, enter in the following command, and then on the next page give the shortcut a helpful name.
rundll32.exe keymgr.dll, KRShowKeyMgr


Once you have the shortcut, you’ll want to right-click on it and choose Properties, then click the Change Icon button on the Shortcut tab.

If you change the textbox value to the following file, you can find the matching icon for the shortcut (adjusting if your Windows is installed elsewhere)
C:\Windows\System32\keymgr.dll


Now you should have a nice matching icon…

Which will open up the Stored User Names and Passwords dialog.

Note that you can also use this to backup and restore your saved passwords, and it should work in either Windows 7, Vista or XP.

Tuesday, April 12, 2011

SP Recompiles ALL User Objects in the database

I worked on a project once where I needed to develop against a local copy of the database. This is not uncommon, especially if I am the only developer on the project. However, this project had three developers all working against our own local copy of the database. We were all off-site and separate from each other, and had to sync up our databases when we met each week. We used a database comparison tool that pointed out to us which of the stored procedures were different. One of us had updated the stored procedure while performing our work, while the other developer may not have touched his copy of the same stored procedure.

So we performed the comparison between my copy of the database and his copy of the database, decided on which stored procedure we wanted to keep, and told the database comparison tool to sync the master database with the selected stored procedure. Here is where we had the problem.

The problem was that the stored procedure we selected wouldn't work because the underlying database structure the stored procedure used had been changed, so the sync failed. We had to first figure out how to re-write the stored procedure to used the updated database structure, made sure it could be saved (compiled), and then go back and try to re-sync the databases objects again.

The underlying problem is that stored procedures can be created and saved (compiled) correctly. But then, days later, you can change the underlying table structure, and SQL gives you no warning (when you make the table structure change) which, or even that, stored procedures that depend on that table structure will no longer work (compile).

We had to come up with a way to easily re-compile all of our stored procedures (over 200) without manually opening up each one and clicking save.

Enter this stored procedure. After searching around on the internet on varios SQL related sites for what seemed like hours, I wasn't able to find anything like this. Maybe there's one out there somewhere, I just didn't have the time to keep looking.

So, I created this stored procedure to re-compile USER stored procedures, views, and functions in the database and tell me which ones didn't compile. Then, I could open only broken ones, fix them and save (compile) them. Then, I'd run this stored procedure again to check that I hadn't broken any others by fixing the reported broken ones. I repeated this procedure until all of the stored procedures, views, and functions in MY copy of the database compiled correctly.

I then had the other developers run the same procedure against their copy of the database. It was amazing to discover how many broken (uncompileable) objects we had between us. No wonder we were having so many problems syncing our databases. Once we ran this procedure on each of our databases, our synchronization only had to sync the database table changes, then select the stored procedure to sync that used the same database structure.

All in all, this procedure we begain enforcing among ourselves prior to any attempt to sync our disconnected databases. It worked like a charm. Hope you can find a use for it too.

/*
 =============================================
 Author:       Gary Janecek
 Create date:  5/4/2010
 Description:  Recompiles ALL User Objects in the database.
               
               The first part of the code inserted the names 
               of all stored procedures to a table variable,
               along with their schema names.  
               
               The next part of the code inserted the names 
               of all views to a table variable,
               along with their schema names.
               
               The next part of the code inserted the names 
               of all user defined functions to a table variable,
               along with their schema names.

               A table
               variable is used just to avoid a CURSOR.  The
               WHILE loop then reads each object name
               and passes it to the system stored procedure:
               sp_refreshsqlmodule.
               
               sp_refreshsqlmodule re-compiles the object
               and will throw an error if the validation fails.
               The CATCH block catches the error if the validation
               fails, and displays the error message in the 
               output window.
               
        NOTE:  Due to an unknown condition (so far) this script
               will fail on all objects AFTER A FAIL
               OCCURS.  Fix the first object that failed
               and re-run this again to find the next problem.
               Repeat until all objects are fixed.
               
 Sample Call: EXEC aDBMaint_ReCompileAllDatabaseObjects
 
 =============================================
*/
CREATE PROCEDURE [dbo].[aDBMaint_ReCompileAllDatabaseObjects]
AS
BEGIN
 -- SET NOCOUNT ON added to prevent extra result sets from
 -- interfering with SELECT statements.
 SET NOCOUNT ON;

    -- Insert statements for procedure here
    -- table variable to database object names.
    -- NOTE the IDENTITY FIELD so we don't need a CURSOR
 DECLARE @myListOfObjects TABLE (RowID INT IDENTITY(1,1)
                               , ObjectName sysname
                               , ObjectType varchar(max))
 
 -- retrieve the list of stored porcedures
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + sp.name + ']' AS ObjectName, 'StoredProcedure' AS ObjectType
   FROM sys.procedures sp
   INNER JOIN sys.schemas s ON s.schema_id = sp.schema_id
   WHERE is_ms_shipped = 0
   ORDER BY ObjectName
   
 -- retrieve the list of views
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + vw.name + ']' AS ObjectName, 'View' AS ObjectType
   FROM sys.views vw
   INNER JOIN sys.schemas s ON s.schema_id = vw.schema_id
   WHERE is_ms_shipped = 0
   ORDER BY ObjectName
   
 -- retrieve the list of functions
 INSERT INTO @myListOfObjects(ObjectName, ObjectType)
  SELECT
   '[' + s.[name] + '].[' + func.name + ']' AS FunctionName, 'Function' AS ObjectType
   FROM sys.objects func
   INNER JOIN sys.schemas s ON s.schema_id = func.schema_id
   WHERE is_ms_shipped = 0
   AND type_desc LIKE '%FUNCTION%' 
   ORDER BY FunctionName
      
 -- counter variables
 DECLARE @RowNumber INT
 DECLARE @TotalRows INT
 SELECT @RowNumber = 1
 SELECT @TotalRows = COUNT(*) FROM @myListOfObjects
 
 DECLARE @ThisObjectName sysname
 
 -- Start the loop
 WHILE @RowNumber < @TotalRows BEGIN
  SELECT @ThisObjectName = ObjectName FROM @myListOfObjects WHERE RowID = @RowNumber
  SELECT @ThisObjectType = ObjectType FROM @myListOfObjects WHERE RowID = @RowNumber
  
  PRINT N'Refreshing... ' + @ThisObjectType + SPACE(16-LEN(@ThisObjectType)) + ': ' + @ThisObjectName 
  
  BEGIN TRY
   -- Refresh the Object
   EXEC sp_refreshsqlmodule @ThisObjectName
  END TRY
  
  BEGIN CATCH
   PRINT 'Validation failed for : ' + @ThisObjectName + ', Error:' + ERROR_MESSAGE()
  END CATCH

  SET @RowNumber = @RowNumber + 1
 END  
END

Script DIAGRAM -- REALLY!

Introduction.

SQL Server allows you to draw diagrams of your schema.

These diagrams are stored in a binary format in dbo.[sysdiagrams]. That data is backed-up/restored with the actual database, but there is NO SUPPORTED METHOD to save to a file. This is where ScriptDiagram2008 is used: to script the diagram data into an sql script. Then you can save the script as a .sql file, and run it whenever you want to restore your database diagram.

This is extremely useful when performing various actions against your database that do not restore your diagrams.

It is also extremely useful if part of your required project documentation is a database schema diagram. Typically, you have already established certain "DOMAIN" diagrams, where all of the tables and relationships for a specific DOMAIN (such as Contacts, Orders, Patients, etc.). So you have more than one diagram.

Probably the most important benefit, to me at least, is this. Look, to recreate a diagram is pretty simple - re-select the tables, and poof - it's done. Or is it? Darnet, my original diagram was set up to print on 2 pages, with specific locations of each table that made it intuitive to me how the tables were related, where relationsip links (lines) did NOT go BEHIND other tables, overlap on top of each other, and minimized crossing each other. Although it may be easy to "generally" re-create a diagram, once you've customized it for YOUR improved comprehension it is nearly IMPOSSIBLE to re-create it exactly the same way - UNTIL NOW.

IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME = 'Tool_ScriptDiagram2008')
BEGIN    
 DROP PROCEDURE dbo.Tool_ScriptDiagram2008
END
GO
/**
 Author:        Craig Dunn
 Description:
    Script Sql Server 2008 diagrams
    (inspired by usp_ScriptDatabaseDiagrams for Sql Server 2000 by Clay Beatty,
    and Tool_ScriptDiagram2005 by yours truly)


 Example:
    USE [YourDatabaseName]
    EXEC Tool_ScriptDiagram2008 'DiagramName'

 Where: 
    @name is:
        Name of the diagram in the Sql Server database instance
        
 Helpful Articles:
    1) Upload / Download to Sql 2005
    http://staceyw.spaces.live.com/blog/cns!F4A38E96E598161E!404.entry 

    2) MSDN: Using Large-Value Data Types
    http://msdn2.microsoft.com/en-us/library/ms178158.aspx 

    3) "original" Script, Save, Export SQL 2000 Database Diagrams
    http://www.thescripts.com/forum/thread81534.html
     
    4) SQL2008 'undocumented' sys.fn_varbintohexstr
    http://www.sqlservercentral.com/Forums/Topic664234-1496-1.aspx
*/

CREATE PROCEDURE [dbo].[Tool_ScriptDiagram2008]
(    
 @name VARCHAR(128)
)
AS
BEGIN
    DECLARE @diagram_id        INT
    DECLARE @index            INT    
    DECLARE @size            INT
    DECLARE @chunk            INT
    DECLARE @line            VARCHAR(max)
    -- Set start index, and chunk 'constant' value
    SET @index = 1 --
     SET @chunk = 32    -- values that work: 2, 6
                    -- values that fail: 15,16, 64
    -- Get PK diagram_id using the diagram's name (which is what the user is familiar with)
    SELECT         @diagram_id=diagram_id
        ,    @size = DATALENGTH(definition)
     FROM sysdiagrams
     WHERE [name] = @name
     IF @diagram_id IS NULL
    BEGIN
        PRINT '/**
Diagram name [' + @name + '] could not be found.
*/'
     END
    ELSE -- Diagram exists
    BEGIN
        -- Now with the diagram_id, do all the work
        PRINT '/**'
        PRINT ''
        PRINT 'Restore diagram ''' + @name + ''''
        PRINT ''
        PRINT ''
        PRINT 'Generated by Tool_ScriptDiagram2008'
        PRINT 'Will attempt to create [sysdiagrams] table if it doesn''t already exist'
        PRINT ''
        PRINT '' + LEFT(CONVERT(VARCHAR(23), GETDATE(), 121), 16) + ''
        PRINT '*/'
        PRINT 'PRINT ''=== Tool_ScriptDiagram2008 restore diagram [' + @name + '] ==='''
        PRINT '    -- If the sysdiagrams table has not been created in this database, create it!
                IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ''sysdiagrams'')
                BEGIN
                    -- Create table script generated by Sql Server Management Studio
                    -- _Assume_ this is roughly equivalent to what Sql Server/Management Studio
                    -- creates the first time you add a diagram to a 2008 database
                    CREATE TABLE [dbo].[sysdiagrams](
                        [name] [sysname] NOT NULL,
                        [principal_id] [int] NOT NULL,
                        [diagram_id] [int] IDENTITY(1,1) NOT NULL,
                        [version] [int] NULL,
                        [definition] [varbinary](max) NULL,
                    PRIMARY KEY CLUSTERED
                     (
                        [diagram_id] ASC
                    )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ,
                     CONSTRAINT [UK_principal_name] UNIQUE NONCLUSTERED
                     (
                        [principal_id] ASC,
                        [name] ASC
                    )WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF)
                     )
                     EXEC sys.sp_addextendedproperty @name=N''microsoft_database_tools_support'', @value=1 , @level0type=N''SCHEMA'',@level0name=N''dbo'', @level1type=N''TABLE'',@level1name=N''sysdiagrams''
                    PRINT ''[sysdiagrams] table was created as it did not already exist''
                END
                -- Target table will now exist, if it didn''t before'
        PRINT 'SET NOCOUNT ON -- Hide (1 row affected) messages'
        PRINT 'DECLARE @newid INT'
        PRINT 'DECLARE @DiagramSuffix          varchar (50)'
        PRINT ''
        PRINT 'PRINT ''Suffix diagram name with date, to ensure uniqueness'''
            PRINT 'SET @DiagramSuffix = '' '' + LEFT(CONVERT(VARCHAR(23), GETDATE(), 121), 16)'
        PRINT ''
        PRINT 'PRINT ''Create row for new diagram'''
        -- Output the INSERT that _creates_ the diagram record, with a non-NULL [definition],
        -- important because .WRITE *cannot* be called against a NULL value (in the WHILE loop)
        -- so we insert 0x so that .WRITE has 'something' to append to...
        PRINT 'BEGIN TRY'
        PRINT '    PRINT ''Write diagram ' + @name + ' into new row (and get [diagram_id])'''
        SELECT @line =
                '    INSERT INTO sysdiagrams ([name], [principal_id], [version], [definition])'
            + ' VALUES (''' + [name] + '''+@DiagramSuffix, '+ CAST (principal_id AS VARCHAR(100))+', '+CAST (version AS VARCHAR(100))+', 0x)'
        FROM sysdiagrams WHERE diagram_id = @diagram_id
        PRINT @line
        PRINT '    SET @newid = SCOPE_IDENTITY()'
        PRINT 'END TRY'
        PRINT 'BEGIN CATCH'
        PRINT '    PRINT ''XxXxX '' + Error_Message() + '' XxXxX'''
        PRINT '    PRINT ''XxXxX END Tool_ScriptDiagram2008 - fix the error before running again XxXxX'''
        PRINT '    RETURN'
        PRINT 'END CATCH'
        PRINT ''
        PRINT 'PRINT ''Now add all the binary data...'''
        PRINT 'BEGIN TRY'
        WHILE @index < @size
        BEGIN
            -- Output as many UPDATE statements as required to append all the diagram binary
            -- data, represented as hexadecimal strings
            SELECT @line =
                   '    UPDATE sysdiagrams SET [definition] .Write ('
                + ' ' + UPPER(sys.fn_varbintohexstr (SUBSTRING (definition, @index, @chunk)))
                + ', null, 0) WHERE diagram_id = @newid -- index:' + CAST(@index AS VARCHAR(100))
            FROM    sysdiagrams
             WHERE    diagram_id = @diagram_id
            PRINT @line
            SET @index = @index + @chunk
        END
        PRINT ''
        PRINT '    PRINT ''=== Finished writing diagram id '' + CAST(@newid AS VARCHAR(100)) + ''  ==='''
        PRINT '    PRINT ''=== Refresh your Databases-[DbName]-Database Diagrams to see the new diagram ==='''
        PRINT 'END TRY'
        PRINT 'BEGIN CATCH'
        PRINT '    -- If we got here, the [definition] updates didn''t complete, so delete the diagram row'
        PRINT '    -- (and hope it doesn''t fail!)'
        PRINT '    DELETE FROM sysdiagrams WHERE diagram_id = @newid'
        PRINT '    PRINT ''XxXxX '' + Error_Message() + '' XxXxX'''
        PRINT '    PRINT ''XxXxX END Tool_ScriptDiagram2008 - fix the error before running again XxXxX'''
        PRINT '    RETURN'
        PRINT 'END CATCH'
    END
END
GO