Archive for SqlServer

SQL Server Database snapshot as a change control tool

Microsoft introduced database snapshot in Sql Server 2005. It is used to keep a point-in-time snapshot of the database state at the time the snapshot was taken. It is also good for reporting purposes when database mirroring is implemented, because while a mirroring partner database is not accessible, one can produce a snapshot of that mirror. And the snapshot can be queried. For example, for a time insensitive report, one can point to the snapshot created from the mirror, thus decrease the load on the principal.

Another potential useful case is during change control. For mission-critical applications, new changes to databases are done during off-peak hours, maintenance window, and/or weekends. It is also important to be able to back out those changes when necessary.

I think database snapshot can be great for the case listed above. Right before the change, a snapshot can be taken, like so:

CREATE DATABASE MyDbSnap ON
(NAME = MyDb, FILENAME = 'c:\MyDb.ss')
AS SNAPSHOT OF MyDb

And if we do need to back out, we can run the script below:

RESTORE DATABASE MyDb FROM DATABASE_SNAPSHOT = 'MyDb'

Obviously database snapshot is not a replacement for regular database backups, but it can be useful in cases like these.

Comments

SQL code for SQL and Relational Theory

I am reading SQL and Relational Theory by C. J. Date. Baron Schwartz wrote a nice review for it. I am reading the online version, about half way through, so am not sure if it has an accompanying CD with source code. In any case, if you want to play with some SQL code listed in the book, here is the script to generate the tables and rows (or should I say relations at a certain point in time with tuples?)

CREATE TABLE S
   ( SNO    VARCHAR(5)   NOT NULL ,
     SNAME  VARCHAR(25)  NOT NULL ,
     STATUS INTEGER      NOT NULL ,
     CITY   VARCHAR(20)  NOT NULL ,
     UNIQUE ( SNO ) ) ;

 CREATE TABLE P
   ( PNO    VARCHAR(6)   NOT NULL ,
     PNAME  VARCHAR(25)  NOT NULL ,
     COLOR  CHAR(10)     NOT NULL ,
     WEIGHT NUMERIC(5,1) NOT NULL ,
     CITY   VARCHAR(20)  NOT NULL ,
     UNIQUE ( PNO ) ) ;

 CREATE TABLE SP
   ( SNO    VARCHAR(5)   NOT NULL ,
     PNO    VARCHAR(6)   NOT NULL ,
     QTY    INTEGER      NOT NULL ,
     UNIQUE ( SNO , PNO ) ,
     FOREIGN KEY ( SNO )
        REFERENCES S ( SNO ) ,
     FOREIGN KEY ( PNO )
        REFERENCES P ( PNO ) ) ;

INSERT INTO S (SNO, SNAME, STATUS, CITY) VALUES ('S1', 'Smith', 20, 'London');
INSERT INTO S (SNO, SNAME, STATUS, CITY) VALUES ('S2', 'Jones', 10, 'Paris');
INSERT INTO S (SNO, SNAME, STATUS, CITY) VALUES ('S3', 'Blake', 30, 'Paris');
INSERT INTO S (SNO, SNAME, STATUS, CITY) VALUES ('S4', 'Clark', 20, 'London');
INSERT INTO S (SNO, SNAME, STATUS, CITY) VALUES ('S5', 'Adams', 30, 'Athens');

INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P1', 'Nut', 'Red', 12.0, 'London');
INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P2', 'Bolt', 'Green', 17.0, 'Paris');
INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P3', 'Screw', 'Blue', 17.0, 'Oslo');
INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P4', 'Screw', 'Red', 14.0, 'London');
INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P5', 'Cam', 'Blue', 12.0, 'Paris');
INSERT INTO P (PNO, PNAME, COLOR, WEIGHT, CITY) VALUES ('P6', 'Cog', 'Red', 19.0, 'London');

INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P1', 300);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P2', 200);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P3', 400);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P4', 200);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P5', 100);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S1', 'P6', 100);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S2', 'P1', 300);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S2', 'P2', 400);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S3', 'P2', 200);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S4', 'P2', 200);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S4', 'P4', 300);
INSERT INTO SP (SNO, PNO, QTY) VALUES ('S4', 'P5', 400);

Comments

Perl script to split sql script into separate files

Below is a quick script I put together to split one single file that contains many stored procedures into many files that contain just a single stored procedure. It can be easily modified for views, user-defined functions, etc..

Notes:
1. It does put BEGIN and END before and after the procedure creation. Because some script has grant statements in it, and I want to put END right after the procedure creation, therefore there is a variable called $FirstGrant;
2. The new files created will be named like StoredProcedureName.sql.

#!/usr/bin/perl
use strict;

open(MYDATA, "c:\\junk\\MyDb\\AllProc.sql") or
  die("Error: cannot open file 'AllProc.sql'\n");

    my $FirstGrant = 0;
while( my $line = <mydata> ){
if ($line =~ /^create\s+proc/i) {
   close (FILE);
   $FirstGrant = 0;
   (my $filename) = $line =~ /create\s+proce?d?u?r?e?\s+\[?d?b?o?]?\.?\[?(\w+)/i;
   open (FILE, ">", "c:\\junk\\MyDb\\".$filename.".sql");
   print FILE "IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[$filename]') AND OBJECTPROPERTY(id,N'IsProcedure') = 1)\n";
   print FILE "BEGIN\n";
   print FILE $line;
}
else {
if (($line =~ /^grant\s+execute/i) && ($FirstGrant == 0)) {
   print FILE "END\n";
   $FirstGrant = 1;
}
   print FILE $line;
}

}
close MYDATA;

Comments

Generate insert scripts for Sql Server tables with Perl

Here is a quick hack with Perl to generate insert statements to reproduce data already exist in a given table. It is similar to results mysqldump generates.

A few things worth mentioning here:
1. This script uses a text file, TableList, that contains a list of tables that you want insert statements created. Each line in the list contains the database name and table name, separated by space(s). This is a quick hack, one could easily pass the file name as an input;
2. Obviously the connection information needs to be modified for your own needs;
3. The script does escape single quote in character fields properly;
4. Use
perl ScriptName.pl > c:\MyFolder\MyTableInsert.sql
to collect the results into c:\MyFolder\MyTableInsert.sql file.
5. Thanks to Summit Amar and people who commented on this article for sql script ideas.
6. Update: modified the script so it handles identity field if it is present in a table.

By the way, Microsoft’s new scripting language, PowerShell, borrowed a lot of good stuff from Perl and Unix shell scripting, which is pretty powerful. My friends Yan Pan and Ananthakumar Muthusamy wrote a book, Microsoft SQL Server 2008 Administration with Windows PowerShell, which I helped to tech-edit. If you are looking to learn more automation and scripting tricks with PowerShell, definitely check out that book, which will come out next month.

#!/usr/bin/perl
use strict;
use Win32::SqlServer;

# Get server name from the command line
# my $ServerName = shift or die "Please enter server name as the first only parameter needed";
# my $TableName = "authors";

open(MYDATA, "RefList.txt") or
  die("Error: cannot open file 'RefList.txt'\n");

while( my $line = <mydata> ){
  $line =~ /(\w+)\s+(\w+)/;
  my $db;
  my $table;

  if ($db ne $1){
	print "-----Tables in $1-----\n";
  }
  $db = $1;
  my $sqlsrv = sql_init(".", "sa", "s3cr3t", $1);

  my $HasIdentityColumn = $sqlsrv->sql_one("select objectproperty(object_id('$2'), 'TableHasIdentity')");
  if ($HasIdentityColumn == 1) {
	print "SET IDENTITY_INSERT $2 ON\n";
  }
# Our SQL statement.
  my $stmnt = < <SQLEND;
--Declare a cursor to retrieve column specific information for the specified table
DECLARE cursCol CURSOR FAST_FORWARD FOR
SELECT column_name,data_type FROM information_schema.columns WHERE table_name = '$2'
OPEN cursCol
DECLARE \@string nvarchar(3000) --for storing the first half of INSERT statement
DECLARE \@stringData nvarchar(3000) --for storing the data (VALUES) related statement
DECLARE \@dataType nvarchar(1000) --data types returned for respective columns
SET \@string='INSERT '+ '$2' +'('
SET \@stringData=''

DECLARE \@colName nvarchar(50)

FETCH NEXT FROM cursCol INTO \@colName,\@dataType

IF \@\@fetch_status<>0
	begin
	--print 'Table ' + $2 + ' not found, processing skipped.'
	close curscol
	deallocate curscol
	return
END

WHILE \@\@FETCH_STATUS=0
BEGIN
IF \@dataType in ('varchar','char','nchar','nvarchar')
BEGIN
        SET \@stringData=\@stringData+''''+'''+isnull('''''+'''''+REPLACE('+\@colName+','''''+''''','''''+'''''+'''''+''''')+'''''+''''',''NULL'')+'',''+'
END
ELSE
if \@dataType in ('text','ntext') --if the datatype is text or something else
BEGIN
        SET \@stringData=\@stringData+'''''''''+isnull(REPLACE(cast('+\@colName+' as varchar(2000)),'''''+''''','''''+'''''+'''''+'''''),'''')+'''''',''+'
END
ELSE
IF \@dataType = 'money' --because money doesn't get converted from varchar implicitly
BEGIN
	SET \@stringData=\@stringData+'''convert(money,''''''+isnull(cast('+\@colName+' as varchar(200)),''0.0000'')+''''''),''+'
END
ELSE
IF \@dataType='datetime'
BEGIN
	SET \@stringData=\@stringData+'''convert(datetime,'+'''+isnull('''''+'''''+convert(varchar(200),'+\@colName+',121)+'''''+''''',''NULL'')+'',121),''+'
END
ELSE
IF \@dataType='image'
BEGIN
	SET \@stringData=\@stringData+'''''''''+isnull(cast(convert(varbinary,'+\@colName+') as varchar(6)),''0'')+'''''',''+'
END
ELSE --presuming the data type is int,bit,numeric,decimal
BEGIN
	--SET \@stringData=\@stringData+'''''''''+isnull(cast('+\@colName+' as varchar(200)),''0'')+'''''',''+'
	--SET \@stringData=\@stringData+'''convert(datetime,'+'''+isnull('''''+'''''+convert(varchar(200),'+\@colName+',121)+'''''+''''',''NULL'')+'',121),''+'
	SET \@stringData=\@stringData+''''+'''+isnull('''''+'''''+convert(varchar(200),'+\@colName+')+'''''+''''',''NULL'')+'',''+'
END

SET \@string=\@string+\@colName+','

FETCH NEXT FROM cursCol INTO \@colName,\@dataType
END
DECLARE \@Query nvarchar(4000)

SET \@query ='SELECT '''+substring(\@string,0,len(\@string)) + ') VALUES(''+ ' + substring(\@stringData,0,len(\@stringData)-2)+'''+'')'' Col1 FROM '+ '$2'
exec sp_executesql \@query

CLOSE cursCol
DEALLOCATE cursCol
SQLEND

# Run query. The return value is a reference to an array.
  my $result = $sqlsrv->sql($stmnt);
# Print results. Each array entry is a hash reference.
  foreach my $row (@$result) {
	print "$$row{Col1}\n";
  }

  if ($HasIdentityColumn == 1) {
	print "SET IDENTITY_INSERT $2 OFF\n";
  }
}
close MYDATA;

Comments

Moving data from Sql Server to MySQL

To move data from Sql Server to MySQL, it is certainly possible to use tools that can make connections to both data stores and manipulate data that way, such as Access, Excel, or SSIS. Here I will introduce a process that does not need any special tools or data drivers. Instead, we can use the utilities and methods that come with a standard Sql Server and MySQL install to accomplish that task.

With this approach, it is assumed that matching tables already exist on MySQL. If not, they need to be created first.

This process is comprised of these steps: first bcp command will be generated based on Sql Server database meta data (sysobjects, think information_schema in MySQL); then the generated bcp commands will be executed; the resulting csv files can then be transferred to the MySQL server, optionally it is possible to compress them if the size is big; and finally the csv files will be imported into MySQL with LOAD DATA LOCAL INFILE.

1. Run the code below on Sql Server to generate bcp commands, customize the data dump folder name and database name as needed. For database name, replace MyDb with the proper database name, 2 such places need to be changed:

set nocount on

/* Set BackupFolder name here */
declare @BackupFolder varchar(100)
set @BackupFolder = 'c:\MyDataDumpFolder\'

/* Initialize variables here */
declare @TableNames table (TableTempID
smallint IDENTITY(1,1) PRIMARY KEY CLUSTERED, TableName varchar(50))
declare @TableName varchar(50)
declare @BackupFileName char(50)
declare @BackupFolderFile varchar(150)
declare @Counter smallint
declare @MaxTableCount smallint
declare @BCPOutCommand varchar(500)

/* Get the list of tables that we want to dump out as csv */
insert into @TableNames (TableName) select name from
MyDb.dbo.sysobjects where xtype = 'U'
and name not in ('dtproperties')

select @MaxTableCount = max(TableTempID) from @TableNames
set @Counter = 1

/* Loop through all each table individually, generate bcp
commands and run bcp commands to export data */
while @Counter < = @MaxTableCount

Begin

/* Create backup file name */
select @TableName = ltrim(rtrim(TableName)) from
@TableNames where TableTempID = @Counter

select @BackupFileName = ltrim(rtrim(@TableName)) + '.txt'

/* Combine backup folder name and file name */
select @BackupFolderFile = @BackupFolder + @BackupFileName

/* Create BCP command */
select @BCPOutCommand = 'bcp ' + '"MyDb.dbo.' + @TableName +
'" out "' + ltrim(rtrim(@BackupFolderFile)) +
'" -c -q -S' + @@Servername + ' -T'

print @BCPOutCommand
set @Counter = @Counter + 1
end

2. Run the bcp commands generated above on Windows DOS command line. Suppose we only have one table called t1, below is what the bcp command would look like:

bcp “test.dbo.t1″ out “c:\junk\t1.txt” -c -q -SSqlServerInstanceName -T

3. Gather the text files and move them to MySQL server;
4. Run the command in mysql to import the data in. Adjust the directory name as needed:

mysql> LOAD DATA LOCAL INFILE ‘/home/haidong/Desktop/t1.txt’ INTO TABLE t1 LINES TERMINATED BY ‘\r\n’;

I’ve tested this for tables that have number, character, datetime, and sequence data successfully. Your mileage may vary. Hope this helps.

Comments (7)

Thoughts on Data Masking

Often times, production data needs to be moved to different environments for testing/developing purposes. However, some of that data can be people’s name, birthday, address, account number, etc., that we don’t want testers and/or developers to see, due to privacy and regulatory concerns. Hence the need to mask those data. I can certainly see this needs grow over time for all database platforms. There are software out there that does this sort of task, or similar tasks, such as data generation tool. Oracle actually has a Data Masking Pack since 10g for this purpose. Here are some of my thoughts on this topic.

One method of masking data is through reshuffling, which shuffles the value in target column(s) that you want to protect randomly across different rows.

Another way of doing it is through data generation. For instance, for target column(s), we just replace its value with something else.

For reshuffling, obviously the data element is still meaningful. In other words, a reshuffled account number is still a valid account number, only now its original owner has been changed. Depending on how stringent the requirements are, this may or may not be enough.

For data generation, we have this question to consider: is the format of the generated data important to us? If yes, then obviously some intelligence needs to be built in so that the generated data follows the format we define. For instance, a valid credit card number is 16 digits long, has certain prefix and/or suffix, the nth digit has a certain meaning, so on and so forth.

Another example is people’s name. Do we replace the name with some random letters we concoct together, or do we want the name to be realistic? If we want realistic names, then we may have to supply a dictionary for the masking software to pull that data from.

In either case, we also have the unique and foreign key constraints to deal with, if there are any. In certain instances where more than one schema/database is involved, the complexity increases exponentially.

Regardless of the method being used, performance of data masking process is important to consider. If the volume of data to be masked is small, then it may not be a big deal. But, as is often the case, you may have a huge transaction table that has millions and millions of rows to mask, then performance is a definite concern.

One idea I am toying around with for data masking performance issue is through low-level data manipulation. For instance, in MySQL, maybe play with rowid. And for Sql Server, play around with fileid, pageid, and such.

Another way to get around that is to do masking through batches. In other words, divide a big task into smaller tasks and tackle them one at a time.

Personally, I like the idea of data reshuffling. On one hand, the data element is meaningful. I know I don’t want to work with randomly generated gibberish that does not make sense to me. On the other hand, if one wants to do performance testing in test or development environment, one would like to have the data distribution as close to production as possible. And data reshuffling can probably keep the data distribution pretty close to that of production.

In my next entry, I will share a simple C# program I wrote to reshuffle data inside a CSV file.

Comments

Sql Server database mirroring automatic failover verification

Starting with Sql Server 2005, Sql Server provides an interesting high availability option at individual database level, called database mirroring. When configured in high availability mode (principal, mirror, and witness), provided that:

1. Failover Partner information is supplied in the connection string;
2. Application code knows to retry database operations;

then automatic fail over will occur.

This all sounds good, but seeing is believing. So I wrote a simple C# console program to simulate what happens when the principal database fails. This simple program connects to a mirrored database, grabs data, and then prints it out on the console. It is a bit contrived, and I used the somewhat dreaded goto statement to retry connections, but hopefully it demonstrated the point. I tested this on VMWare virtual machines, so I put in 1 second delay.

Here is what I tested while my console program is running:

1. Manual failover through the principal’s property page. The program didn’t skip a beat. Results below:

Haidong
12/4/2008 9:21:32 PM
Haidong
12/4/2008 9:21:33 PM
Haidong
12/4/2008 9:21:34 PM
Haidong
12/4/2008 9:21:35 PM
Haidong
12/4/2008 9:21:36 PM
Haidong
12/4/2008 9:21:37 PM
Haidong
12/4/2008 9:21:38 PM

2. Stopped Sql Server service on the principal. Similar results as above;

3. Yanked the power cable off the principal box. There was a noticeable delay. Notice the one minute delay in results below:

Haidong
12/4/2008 10:00:23 PM
Haidong
12/4/2008 10:00:24 PM
Haidong
12/4/2008 10:00:25 PM
Haidong
12/4/2008 10:01:24 PM
Haidong
12/4/2008 10:01:25 PM
Haidong
12/4/2008 10:01:26 PM
Haidong
12/4/2008 10:01:27 PM
Haidong
12/4/2008 10:01:28 PM
Haidong
12/4/2008 10:01:29 PM

All in all, I am pretty impressed. If you compile and run the program, use Ctrl - c to stop if you think you’ve seen enough. Source code below.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Threading;

namespace SqlDbConsole
{
    class Program
    {
        [STAThreadAttribute]
        static void Main(string[] args)
        {
            while (true)
            {
                RunSimpleSql();
            }
        }
        static void RunSimpleSql()
        {
            SqlConnection conn = new SqlConnection("Data Source=PrincipalServer;Failover Partner=MirrorServer;Initial Catalog=MyDb;Integrated Security=SSPI;");
        PointOfRetry:
            try
            {
                conn.Open();
                SqlCommand cmd = conn.CreateCommand();
                cmd.CommandText = "SELECT TOP 1 FirstName from Contact";
                cmd.CommandType = CommandType.Text;

                SqlDataReader rdr = cmd.ExecuteReader();
                while (rdr.Read() == true)
                {
                    string s;
                    s = (string)rdr["FirstName"];
                    Console.WriteLine(s);
                    //Console.WriteLine("Beijing: {0}", TimeZoneInfo.ConvertTimeBySystemTimeZoneId(DateTime.Now, TimeZoneInfo.Local.Id, "China Standard Time"));
                    Console.WriteLine(DateTime.Now);
                }
                Thread.Sleep(1000);
            }
            catch
            {
                goto PointOfRetry;
            }
        }
    }
}

Comments

Notes on analyzing a user minidump with WinDbg

One of my students’ Sql Server 2000 SP4 crashed. I volunteered to see if I could get anything out of it by looking at the dump file with WinDbg.

A few notes:
1. The processor or Windows version that the dump file was created on does not need to match the platform on which WinDbg is being run. However, you do need to provide symbol files that match the Windows edition and version where the dump was from. You can have more than one symbol file path, just separate them by a semicolon.

Since the dump file was generated on Windows 2003 SP2, I downloaded symbol files for it and installed on my Windows XP SP2 laptop. Afterwards, I added it to the symbol path. So my symbols are from 2 sources: Microsoft symbol server and the local symbol I downloaded;

2. I did install Sql Server 2000 SP4 on my laptop to match the Sql Server instance on the server where the dump was from. Afterwards, I added C:\Program Files\Microsoft SQL Server\MSSQL$SQL2K\Binn to the Image File Path

After running:
!analyze -v
~kv
lm

WinDbg points to the direction of:

INVALID_POINTER_READ_c0000005_sqlservr.exe!CIncPageMgr::FreeToMark

Not too sure how revealing this is, since we’ve already known it was an access violation. I suppose those kind of information is like bread crumbs that can be helpful to Microsoft support.

Questions:

1. I thought that I got all the right symbol path, but I still got the message below in the command window, why?
excerpt
Your debugger is not using the correct symbols
Type referenced: kernel32!pNlsUserInfo

2. It looks like viewing the memory content in the last call can be helpful (Alt + 5). Two questions related to this point:
a. Which display format is useful? To me, part of ASCII and Byte are meaningful to the naked eye.
b. How can I save the memory content in a text file? I can analyze text much faster in VI, but I haven’t found a good way to suck the text out of the memory viewer window?

Got to retire now. I have close to 20 students to teach tomorrow. It was a fun teaching day today.

By the way, X.T.X (谢天笑, 冷血动物), a Shandong boy like me, is not bad.

Any help regarding my WinDbg questions, dear reader?

Comments (2)

Resource database has been moved to a new location in Sql Server 2008

A new resource database was introduced in Sql Server 2005, and it is still with us in Sql Server 2008. In both editions, you will not be able to see it via Sql Server Management Studio. This hidden database is required. Without it, Sql Server cannot start.

In Sql Server 2005, mssqlsystemresource.mdf and mssqlsystemresource.ldf are stored under the DATA folder, along with master, model, msdb, and tempdb. In Sql Server 2008, however, the two files have been moved to the Binn folder, where sqlservr.exe executable is located.

By the way, do you know why the folder is called Binn? Why two “n”s? There is a bit of history there. Back in the 16-bit days, all binary files were stored under the Bin folder. However, as things moved to 32-bit and Windows NT came to the market, this new folder, Binn, was created to accommodate that. The extra n was taken from NT, which stands for “New Technology”.

Comments (2)

What does syspolicy_purge_history job do

After a brand new Sql Server 2008 installation or upgrade from a previous version, a job will be created during the installation/upgrade process: syspolicy_purge_history. What does it do?

Before answering that question, here is a little background. Sql Server 2008 introduced a new feature called Policy Management. For example, one could define a policy that says all tables must have a clustered index. Once defined, the policy can be evaluated against targets to check for compliance. Each evaluation of compliance will be recorded in tables in msdb, syspolicy_policy_execution_history_details_internal, syspolicy_policy_execution_history_internal, and syspolicy_policy_category_subscriptions_internal, specifically. The evaluation condition is built on top of object facets, which is predefined by Microsoft.

The purpose of that job, syspolicy_purge_history, is to remove records in those tables to maintain so it does not pile up and take up space unnecessarily. It has three steps. The first step verifies if Policy Management is enabled. If yes, it will go to the second step, which deletes policy evaluation history prior to cutoff date, defined by HistoryRetentionInDays. If Policy Management is not enabled, the whole job stops with an error. I haven’t quite figured out what the third step does, which is a PowerShell step. It calls the PolicyStore.EraseSystemHealthPhantomRecords method, and no detailed document for it was available as of this writing.

By the way, here is the code to create AllTablesNeedClusteredIndex condition. Another thing that is worth knowing is that if you only want to check a condition against only user databases, not system databases, the database facet to check against is called IsSystemObject.

Declare @condition_id int
EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'AllTablesNeedClusteredIndex', @description=N'', @facet=N'Table', @expression=N'<operator>
  <typeclass>Bool</typeclass>
  <optype>EQ</optype>
  <count>2</count>
  <attribute>
    <typeclass>Bool</typeclass>
    <name>HasClusteredIndex</name>
  </attribute>
  <function>
    <typeclass>Bool</typeclass>
    <functiontype>True</functiontype>
    <returntype>Bool</returntype>
    <count>0</count>
  </function>
</operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
Select @condition_id

GO

Comments

« Previous entries