views:

260

answers:

3

This relates to computed columns and default constraints (and possibly other expressions) in SQL Server 2005 (or above). Both of these use an arbitrary expression to generate a value, e.g. (year+1) for a computed column representing "next year" (this is obviously a simple and stupid example).

What I'm trying to do: I want to be able to determine whether an existing computed column (or default constraint) in some table matches the intended definition, where the latter is defined in a software-generated schema that was used to create the table. I can get the definition of a computed column using sp_helptext, and the definition of a default constraint from the sys.default_constraints catalog view.

What I'm having trouble with: The expressions I get back from the above sources are in a normalized/standard form that doesn't match the form used to create the column/constraint. In the example above, SQL normalizes the expression to ([year]+(1)). Therefore, a simple string comparison between this form and the original form will not reliably determine whether they are the same.

Solutions I've thought of already:

  • Generate the original expressions so that they match SQL's form. This requires knowing the rules SQL uses to generate its form, which are not documented, so it's not a great solution.
  • Parse both forms into ASTs and compare those. I already an AST for the original form, but I don't have a parser, and would rather not write one.
  • Create a temporary table and add a computed column using the original expression, and then read back the normalized expression. This would be pretty reliable, but it feels dirty, since in theory this comparison should be a read-only operation.

Can anyone think of another good option for handling this? My hope is that maybe someone knows of some debugging/diagnostic tool that will spit back the input expression in normalized/standard form.

+2  A: 

I will also add, that "normalization" of expression sometimes do not only adds brackets, but it also changes some expressions in a very different way!

For example in MS SQL 2008 Express I created a default with the following expression:

year(getdate()) + 1

but SQL Server changes it to be

(datepart(year,getdate())+(1))

So, I don't believe that any kind of rules or regular expressions will solve your problem for 100% of cases, so I do recommend you to combine several methods

1) First of all I think that in your case there is a limited number of typical constraints, that usually exists in most databases. As a rule, there are getdate(), and constant numeric expressions (0), (1). You can have a table of those typical constraints that will help you to match expected and real expressions.

2) Then you can try very simple rule to include all fields names in [] brackets and all constants and math operations in (), so you will have year + 1 transformed into ([year] + (1)). I suppose this could be done with a Regular Expressions.

3) for all cases where you was not able to compare expected and actual results using 1th or 2nd method, you will do what you suggested - create a temp table and compare results.


EDIT 04.Aug:

I found that when you create a database-level defaults, they will not be normalized. Strange, eh? But probably, you can use this fact and create database-level defaults that you bind to columns instead of creating default constraints for columns (though, I suppose this will be a very big change in design and will require a huge update of existing databases)

As for columns default constraints, and the approach to create/drop defaults dynamically in order to get their normalized form, here is a simple C# code using Microsoft.SqlServer.Management.Smo library. I will suggest to create one test_table with columns IntTest int, VarcharTest varchar(1), DateTimeTest datetime and so on - i.e. only one column for each type. In this case you will create/drop defaults but will not have to create drop table and columns and this will increase the performance.

C# code will follow (include using Microsoft.SqlServer.Management.Smo;)

        Server server = new Server("localhost\\SQLEXPRESS");
        Database db = server.Databases["test"];
        Table t = db.Tables["test_defaults"];
        //here should be some logic to select column name depending on default data type
        //for example for DateTime defaults we will use "DateTimeTest" column
        Column c = t.Columns["DateTimeTest"];

        //clean up previous results if they exist
        DefaultConstraint constr = c.DefaultConstraint;
        if (constr != null) constr.Drop();

        //create new constraint
        constr = c.AddDefaultConstraint();
        constr.Text = "getdate()";
        constr.Create();
        //after refresh we will have a new text
        constr.Refresh();
        string result = constr.Text;

        //drop it if we don't need it
        constr.Drop();
Bogdan_Ch
Thanks for the ideas, I agree that I probably will have to do something like this. Hopefully an easier solution will come up, though.
Charlie
I added some sample code.
Bogdan_Ch
I like the idea of reusing one table for this, if I end up needing to do the normalization via a temporary table. Thanks for all the good ideas.
Charlie
A: 

I feel like there should be answer by connecting to the DAC (so that you can query the system tables), but I can't actually find out how the function 'object_definition' works.

This may be a question for someone like Kalen Delaney, to find out if there's a public function that these things get parsed with.

Rob

Rob Farley
A: 

I'd lean towards your third solution (create a table with that constraint and read it back) - you can do it with a temp table, so it would be semi-clean, and if you cache the normalized form, you'd only need to do it when what you're searching for changes. I'm not sure how static the expressions you're searching for are, but when they change, just have part of the save process be that it creates a temp table, applies the constraint, reads it from the definition, and saves it along with the native form of the constraint.

If this isn't what you're looking for (besides that it's not completely clean), let me know and I can adjust it as needed.

rwmnau