FIGURE 7-4The SMO Database Table, Index, and Foreign Key objects Database Tables Table Columns Column Name DataType Identity IdentitySeed IdentityIncrement Nullable Create Alter Indexes
Trang 1$s = new-object (’Microsoft.SqlServer.Management.Smo.Server’) ‘SQLTBWS\INST01’
$bkdir = $s.Settings.BackupDirectory
$dbs = $s.Databases
$dbs | foreach-object {
$db = $_
if ($db.IsSystemObject -eq $False -and $db.IsMirroringEnabled -eq $False) {
$dbname = $db.Name
$dt = get-date -format yyyyMMddHHmmss
$dbbk = new-object (’Microsoft.SqlServer.Management.Smo.Backup’)
$dbbk.Action = ‘Database’
$dbbk.BackupSetDescription = "Full backup of " + $dbname
$dbbk.BackupSetName = $dbname + " Backup"
$dbbk.Database = $dbname
$dbbk.MediaDescription = "Disk"
$dbbk.Devices.AddDevice($bkdir + "\" + $dbname + "_db_" + $dt +
".bak", ‘File’)
$dbbk.SqlBackup($s)
# Simple Recovery Model has a Value Property of 3
if ($db.recoverymodel.value -ne 3) {
$dt = get-date -format yyyyMMddHHmmss
$dbtrn = new-object (’Microsoft.SqlServer.Management.Smo.Backup’)
$dbtrn.Action = ‘Log’
$dbtrn.BackupSetDescription = "Trans Log backup of " + $dbname
$dbtrn.BackupSetName = $dbname + " Backup"
$dbtrn.Database = $dbname
$dbtrn.MediaDescription = "Disk"
$dbtrn.Devices.AddDevice($bkdir + "\" + $dbname +
"_tlog_" + $dt + ".trn", ‘File’)
$dbtrn.SqlBackup($s) }
}
}
While the best method for creating and modifying tables in SQL Server is to use Transact-SQL scripts,
in some cases embedding this activity in application code is beneficial Remote installation of application
software is one good example
Take a look at the objects, shown in Figure 7-4, used in creating tables
As shown in Figure 7-4, the database has aTablescollection, which holdsTableobjects Each
Tableobject has aColumnscollection, which holdsColumnobjects Listing 7-6 shows the complete
script to define our tables, indexes, and foreign keys
Define theTableobject first, and then create theColumnobjects, set their properties, and add the
Columnobjects to the table’sColumnscollection
Trang 2FIGURE 7-4
The SMO Database Table, Index, and Foreign Key objects
Database
Tables
Table
Columns Column Name DataType Identity IdentitySeed IdentityIncrement Nullable Create Alter Indexes Index Name IndexKeyType IndexedColumns IndexedColumn
Name ForeignKeys
ForeignKey
Name Columns ForeignKeyColumn ReferencedTable ReferencedTableSchema Create
Alter
Trang 3After the table and columns have been defined, the next step is to define an index and set its
IndexKeyTypeproperty to indicate the index is a primary key (Without a primary key, a foreign key
to the table can’t be defined.) The last step is to create a clustered index on one table to improve query
performance against that table Finally, theCreatemethod is used to create the table
In our example scenario, AdventureWorks has acquired another company Their employees are being
merged into the AdventureWorks HR application, but key data items need to be maintained for the
short term based on their old company’s records To this end, two new tables are required
The first table, calledAcquisitionCompany, will hold the name and date the acquisition occurred,
plus an identity-based key calledCompanyID (In this scenario, AdventureWorks will do this again.)
The second table, calledAcquisitionEmployee, contains the employee’s original employee ID,
original start date, number of hours available for time off this year, plus a column that indicates whether
this employee earned a kind of reward called a Star Employee TheAcquisitionEmployeetable
also needs a column to reference theAcquisitionCompanytable These tables will be created in the
HumanResourcesschema
After creating the tables, the next step is to add foreign key references fromAcquisitionEmployee
to both theAcquisitionCompanyand the existingEmployeetables Because AdventureWorks
man-agement likes the Star Employee idea, that column will be added to the existingEmployeetable but
will be kept separate from theStarEmployeecolumn in theAcquisitionEmployeetable because
the criteria are different
Once the tables are created, the required foreign keys to theAcquisitionEmployeetable can be
added by creating aForeignKeyobject, defining theForeignKeycolumns, and adding them to the
ForeignKeyobject and defining the referenced table and schema
LISTING 7-6
CreateTable.ps1
#createtable.ps1
#Creates the Acquisition tables in the AdventureWorks database
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
| out-null
$s = new-object ("Microsoft.SqlServer.Management.Smo.Server")
"MyServer\MyInstance"
#Reference the AdventureWorks database
$db = $s.Databases["AdventureWorks"]
#Create reusable datatype objects
$dtint = [Microsoft.SqlServer.Management.Smo.Datatype]::Int
$dtvchar100 = [Microsoft.SqlServer.Management.Smo.Datatype]::NVarChar(100)
$dtdatetm = [Microsoft.SqlServer.Management.Smo.Datatype]::DateTime
$dtbit = [Microsoft.SqlServer.Management.Smo.Datatype]::Bit
#Create the table in the HumanResources schema
$tbcomp = new-object ("Microsoft.SqlServer.Management.Smo.Table")
($db, "AcquisitionCompany", "HumanResources")
Trang 4#Create the CompanyID column
$colcoid = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbcomp, "CompanyID", $dtint)
$colcoid.Identity = $true
$colcoid.IdentitySeed = 1
$colcoid.IdentityIncrement = 1
$tbcomp.Columns.Add($colcoid)
#Create the CompanyName column
$colconame = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbcomp, "CompanyName", $dtvchar100)
$colconame.Nullable = $false
$tbcomp.Columns.Add($colconame)
#Create the AcquisitionDate column
$colacqdate = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbcomp, "AcquisitionDate", $dtdatetm)
$colacqdate.Nullable = $false
$tbcomp.Columns.Add($colacqdate)
#Create the Primary Key
$idxpkcompany = new-object ("Microsoft.SqlServer.Management.Smo.Index")
($tbcomp, "PK_AcquisitionCompany")
$idxpkcompany.IndexKeyType = "DriPrimaryKey"
$idxpkcompany.IsClustered = $true
$idxpkcompanycol = new-object
("Microsoft.SqlServer.Management.Smo.IndexedColumn")
($idxpkcompany, "CompanyID")
$idxpkcompany.IndexedColumns.Add($idxpkcompanycol)
$tbcomp.Indexes.Add($idxpkcompany)
#Create the table
$tbcomp.Create()
#Create the table in the HumanResources schema
$tbemp = new-object ("Microsoft.SqlServer.Management.Smo.Table")
($db, "AcquisitionEmployee", "HumanResources")
#Create the EmployeeID column
$colempiddt = [Microsoft.SqlServer.Management.Smo.Datatype]::Int
$colempid = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "EmployeeID", $dtint)
$colempid.Nullable = $false
$tbemp.Columns.Add($colempid)
#Create the CompanyID foreign key column
$colempcolid = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "CompanyID", $dtint)
$colempcolid.Nullable = $false
Trang 5#Create the OriginalEmployeeID column
$colorigempid = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "OriginalEmployeeID", $dtint)
$colorigempid.Nullable = $false
$tbemp.Columns.Add($colorigempid)
#Create the OriginalHireDate column
$colemporigdate = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "OriginalHireDate", $dtdatetm)
$colemporigdate.Nullable = $false
$tbemp.Columns.Add($colemporigdate)
#Create the TimeOffHours column
$colemptoh = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "TimeOffHours", $dtint)
$colemptoh.Nullable = $false
$tbemp.Columns.Add($colemptoh)
#Create the StarEmployee column
$colempstar = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbemp, "StarEmployee", $dtbit)
$colempstar.Nullable = $false
$tbemp.Columns.Add($colempstar)
#Create the Primary Key
$idxpkemp = new-object ("Microsoft.SqlServer.Management.Smo.Index")
($tbemp, "PK_AcquisitionEmployee")
$idxpkemp.IndexKeyType = "DriPrimaryKey"
$idxpkempcol = new-object("Microsoft.SqlServer.Management.Smo.IndexedColumn")
($idxpkemp, "EmployeeID")
$idxpkemp.IndexedColumns.Add($idxpkempcol)
$tbemp.Indexes.Add($idxpkemp)
#Create the Clustered Index
$idxciemp = new-object ("Microsoft.SqlServer.Management.Smo.Index")
($tbemp, "CI_AcquisitionEmployee")
$idxciemp.IndexKeyType = "DriUniqueKey"
$idxciemp.IsClustered = $true
$idxciempcol = new-object("Microsoft.SqlServer.Management.Smo.IndexedColumn")
($idxciemp, "OriginalEmployeeID")
$idxciemp.IndexedColumns.Add($idxciempcol)
$tbemp.Indexes.Add($idxciemp)
#Create the table
$tbemp.Create()
Trang 6#Connect to the HumanResources.Employee table
$tbhremp = $db.Tables | where
{$_.Name -eq ‘Employee’ -and $_.Schema -eq ‘HumanResources’}
#Add the StarEmployee column to the HumanResources.Employee table
$colhrempstar = new-object ("Microsoft.SqlServer.Management.Smo.Column")
($tbhremp, "StarEmployee", $dtbit)
$colhrempstar.Nullable = $true
$tbhremp.Columns.Add($colhrempstar)
#Alter the HumanResources.Employee table
$tbhremp.Alter()
#Define the HumanResources.Employee foreign key
$fkemp = new-object ("Microsoft.SqlServer.Management.Smo.ForeignKey")
($tbemp, "FK_AcqEmployee_HREmployee")
$fkcolemp = new-object("Microsoft.SqlServer.Management.Smo.ForeignKeyColumn")
($fkemp, "EmployeeID", "EmployeeID")
$fkemp.Columns.Add($fkcolemp)
$fkemp.ReferencedTable = "Employee"
$fkemp.ReferencedTableSchema = "HumanResources"
$fkemp.Create()
#Define the HumanResources.AcquisitionCompany foreign key
$fkco = new-object ("Microsoft.SqlServer.Management.Smo.ForeignKey")
($tbemp, "FK_AcqEmployee_AcqCompany")
$fkcoid = new-object ("Microsoft.SqlServer.Management.Smo.ForeignKeyColumn")
($fkco, "CompanyID", "CompanyID")
$fkco.Columns.Add($fkcoid)
$fkco.ReferencedTable = "AcquisitionCompany"
$fkco.ReferencedTableSchema = "HumanResources"
$fkco.Create()
One of the great improvements in SMO over its predecessor, DMO, is in the area of scripting With
SMO, Transact-SQL scripts can be created from objects even if they don’t yet exist Almost all
mainte-nance dialogs in SQL Server Management Studio include a button that enables a script to be generated
from the changes made in that dialog That way, the script can be executed, rather than making the
changes from the dialog, and the script can be saved for future use
Another useful feature of scripting existing objects is the capability to generate scripts of all database
objects for documentation or to put into source code control This enables administrators to rebuild a
database in the form it existed at the time the script was created, should some problem arise requiring
that effort
At any time while creating or working with objects in SMO, those objects can be scripted for archival or
later use (see Figure 7-5)
Trang 7FIGURE 7-5
SMO scripting objects
Scripter
ScriptDrops WithDependencies FileName IncludeHeaders AppendToFile ToFileOnly
Script
ClusteredIndexes Indexes DriAll
Options Server
TheServerproperty enables theScripterobject to connect to the server The remaining properties
needing to be set are in the Scripter Options collection
TheScriptDropsproperty specifies whether the script will consist of drops for the objects or creates
for the objects If this property is set toTrue, the script will contain aDROPstatement for each object
(within anIFcondition to ensure that it exists), but aFalsevalue causes the scripter to generate the
CREATEstatement for each object TheWithDependenciesproperty, if true, causes the objects to
be scripted in an order that respects the dependency of one scripted object on another TheFileName
property contains the full path of the resultant script file TheIncludeHeadersproperty, whenTrue,
includes a comment indicating the name of the object and when the object was created in the script
TheAppendToFileproperty appends the script to the end of an existing file if True, and overwrites
the file ifFalse
By default, the scripting process sends the results to the console, so setting theToFileOnly
property toTruecauses the scripter to send the script only to the file specified Setting the
ClusteredIndexesproperty toTruecauses the clustered index for a table to be included in the
script, and setting theIndexesproperty toTruecauses the non-clustered indexes to be included
Trang 8in the script TheDriAllproperty, when set toTrue, causes all objects with enforced declarative
referential integrity to be included in the script
The objects to be scripted need to be added to an array of typeSqlSmoObject Once the array has
been populated with all the objects to be scripted, invoke theScriptmethod and the script will
be created Listing 7-7 shows the script to create a T-SQL script of all tables in the AdventureWorks
database
LISTING 7-7
Scripting.ps1
#Script all the table objects in the AdventureWorks database
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.SMO")
| out-null
$s = new-object (’Microsoft.SqlServer.Management.Smo.Server’)
‘MyServer\MyInstance’
$db = $s.Databases[’AdventureWorks’]
$scrp = new-object (’Microsoft.SqlServer.Management.Smo.Scripter’) ($s)
$scrp.Options.ScriptDrops = $False
$scrp.Options.WithDependencies = $True
$scrp.Options.FileName = ‘c:\dbscript.sql’
$scrp.Options.IncludeHeaders = $True
$scrp.Options.AppendToFile = $True
$scrp.Options.ToFileOnly = $True
$scrp.Options.ClusteredIndexes = $True
$scrp.Options.DriAll = $True
$scrp.Options.Indexes = $True
$scrp.Script($db.Tables)
Data-based tasks
In the section on ADO.NET you saw an example that returned the results from a query to the user,
using aDataAdapterobject to fill aDataSet This method is fine as long as there’s enough
memory to hold all the results of the query If the result set is very large, though, it is better to use the
ExecuteReadermethod of theSqlCommandobject
The following example, shown in Listing 7-8, uses theAdventureWorks2008database to extract
department employee information byDepartmentID, and creates a separate physical file for each
department The files will be text files with commas separating the columns returned This format
is easily understood by most programs that import data TheExecuteReadermethod returns a
DataReaderobject, and the columns must be retrieved from the object using the GetValuemethod
of theDataReader, supplying the column index number toGetValue The script sets a local variable
for each of the columns retrieved for each row Once those have been set, the script tests whether the
DepartmentIDvalue has changed If so, a ‘‘header’’ row is created in a string variable ($r) and written
to a file with the name of the departmentNamevalue and a txtextension Once the header has been
written, the data row just read is written to the file
Trang 9LISTING 7-8
dept_birthdays.ps1
#dept_birthdays.ps1
#This script will extract information for employees by Department
#and write the results into text files named with the department name
$cn = new-object System.Data.SqlClient.SqlConnection("Data Source=MyServer\
MyInstance;Integrated Security=SSPI;Initial Catalog=AdventureWorks2008");
$cn.Open()
$q = "SELECT d.[DepartmentID]"
$q = $q + " ,d.[Name]"
$q = $q + " ,p.[FirstName]"
$q = $q + " ,p.[LastName]"
$q = $q + " ,e.[JobTitle]"
$q = $q + " ,e.[BirthDate]"
$q = $q + " ,e.[SalariedFlag]"
$q = $q + " FROM [AdventureWorks2008].[Person].[Person] p"
$q = $q + " INNER JOIN [AdventureWorks2008].[HumanResources].[Employee] e"
$q = $q + " ON p.[BusinessEntityID] = e.[BusinessEntityID]"
.[HumanResources].[EmployeeDepartmentHistory] dh"
$q = $q + " ON p.[BusinessEntityID] = dh.[BusinessEntityID]"
$q = $q + " INNER JOIN [AdventureWorks2008].[HumanResources].[Department] d"
$q = $q + " ON dh.[DepartmentID] = d [DepartmentID]"
$q = $q + " WHERE p.[PersonType] = ‘EM’"
$q = $q + " AND dh.[EndDate] IS NULL"
$q = $q + " ORDER BY d.DepartmentID, p.LastName"
$cmd = new-object "System.Data.SqlClient.SqlCommand" ($q, $cn)
$cmd.CommandTimeout = 0
$dr = $cmd.ExecuteReader()
$did = ""
while ($dr.Read()) {
$DepartmentID = $dr.GetValue(0)
$Name = $dr.GetValue(1)
$FirstName = $dr.GetValue(2)
$LastName = $dr.GetValue(3)
$JobTitle = $dr.GetValue(4)
$BirthDate = $dr.GetValue(5)
$SalariedFlag = $dr.GetValue(6)
if ($DepartmentID -ne $did) {
$r = """DepartmentID"",""Name"",""FirstName"",""LastName"
$r = $r + """,""JobTitle"",""BirthDate"",""SalariedFlag"""
$f = $Name + ".txt"
Trang 10$r | out-file $f -append -encoding ASCII
$did = $DepartmentID
}
$r = """" + $DepartmentID + """,""" + $Name + ""","""
$r = $r + $FirstName + """,""" + $LastName + ""","""
$r = $r + $JobTitle + """,""" + $BirthDate + """,""" + $SalariedFlag +
""""
$f = $Name + ".txt"
$r | out-file $f -append -encoding ASCII
}
$dr.Close()
$cn.Close()
SQL Server PowerShell Extensions
With the release of SQL Server 2008, Microsoft incorporated new extensions to PowerShell specifically
for working with SQL Server The first of these is a mini-shell preconfigured with the required DLLs
loaded and the providers available for use All of the examples shown previously in this chapter run fine
in standard PowerShell 1.0 The rest of the chapter focuses on the new features created for SQL Server
2008
SQLPS.exe
SQL Server 2008 incorporates PowerShell into its management toolset As part of Microsoft’s Common
Engineering Criteria, all server tools now incorporate PowerShell to enable administrators to script tasks
easily
Snap-ins to PowerShell are fairly easily achieved by writing what’s called a provider, which is a set of
tools designed to allow easy access to data In the case of SQL Server, the provider creates a PowerShell
drive that directly connects with SQL Server, and a set of cmdlets to communicate with SQL Server
from PowerShell Using the PowerShell drive, you can browse SQL Server as though it were a file
system The cmdlets enable you to run Transact-SQL commands, evaluate policies, and convert names
between the characters that SQL Server supports and the more limited set of characters that PowerShell
supports
Microsoft created a special version of PowerShell (1.0) calledsqlps.exethat includes the provider and
preloads all of the DLLs that the provider requires, including the DLLs for SMO Another difference
between standard PowerShell andsqlps.exeis that the execution policy of PowerShell insqlps.exe
is set to RemoteSigned This means that as soon as SQL Server 2008 is installed,sqlps.exeis ready to
run scripts (on the local system, at least)
The good news, considering that PowerShell 2.0 is out (or will be soon), is that the SQL Server provider
can be easily loaded into standard PowerShell Michiel Wories developed the SQL Server provider, and
in his blog post athttp://blogs.msdn.com/mwories/archive/2008/06/14/SQL2008 5F00
Powershell.aspxhe provides the script that loads it into PowerShell