简体   繁体   中英

is it possible to alter stored procedure via query in SQL Server?

I need to change the profile names from all stored procedures having email alerts. Is it possible to alter stored procedure via query in SQL Server.

It will help me to update all stored procedures having email alerts.

SELECT object_definition(object_id) as [Proc Definition]
  , OBJECT_NAME(object_id) [Stored Proc Name]
FROM sys.objects 
WHERE type='P' and object_definition(object_id) like '%sp_send_dbmail%'

Via above query I need to alter the body of stored procedure.

you need to run a loop to update all the procedures and get the procedure code using sp_helptext and store it in a string variable. Replace string value you need in your variable and then use the updated string variable to run the alter command and update your procs.

It is possible to make mass changes to stored procedure code but the task can be fairly complex due to the many variations of T-SQL constructs. Simple string replacements may work in some cases but is fragile because the text to be replaced can occur it other contexts where it should not be changed. For example, if your original profile is named "mail", a dumb text replace would also change the name of the "sp_send_dbmail" proc.

Below is a PowerShell example that uses the Microsoft.SqlServer.TransactSql.ScriptDom assembly to more intelligently parse T-SQL code and make targeted changes. This isn't a complete solution that can change all occurrences of the profile name (eg local variable assignments, positional sp_send_db_mail parameters, etc.) but will change the value specified in sp_send_dbmail named parameter syntax and is more robust than replacing text. You can test and tweak the code in a dev environment to suit your needs.

Also, as a disclaimer, changing the parser token text in this way is undocumented so use at your own risk.

Function Replace-DatabaseMailProfileNames($script, $originalProfileName, $newProfileName) {

    # use the appropriate TSqlParser version for the target SQL Server version
    $parser = New-Object Microsoft.SqlServer.TransactSql.ScriptDom.TSql140Parser($true)

    $parseErrors = New-Object System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]
    $scriptReader = New-Object System.IO.StringReader($script)
    $fragment = $parser.Parse($scriptReader, [ref]$parseErrors)
    $scriptChanged = $false
    if($parseErrors.Count -eq 0) {
        $fragment.Batches[0].Statements[0].ScriptTokenStream[$fragment.Batches[0].Statements[0].FirstTokenIndex].Text = "ALTER"
        foreach($statement in $fragment.Batches[0].Statements[0].StatementList.Statements) {
            switch($statement.GetType().ToString())
            {
                "Microsoft.SqlServer.TransactSql.ScriptDom.ExecuteStatement" {
                    if($statement.ExecuteSpecification.ExecutableEntity.ProcedureReference.ProcedureReference.Name.BaseIdentifier.Value.ToLower() -eq "sp_send_dbmail") {
                        foreach($parameter in $statement.ExecuteSpecification.ExecutableEntity.Parameters) {
                            if(($parameter.Variable.Name.ToLower() -eq "@profile_name") -and ($parameter.ParameterValue.Value.ToLower() -eq $originalProfileName.ToLower())) {
                                $parameter.ParameterValue.ScriptTokenStream[$parameter.ParameterValue.FirstTokenIndex].Text = $newProfileName
                                $scriptChanged = $true
                            }
                        }
                    }
                    break
                }
            }
        }

        if($scriptChanged) {
            $fragmentText = New-Object System.Text.StringBuilder
            # reconstrunct script from tokens containing new values
            for($i = $fragment.FirstTokenIndex; $i -le $fragment.LastTokenIndex; ++$i) {
                [void]$fragmentText.Append($fragment.ScriptTokenStream[$i].Text)
            }
            return $fragmentText.ToString()
        }
        else {
            # return null to indicate script was not changed
            return $null
        }

    }
    else {
        throw "Error(s) parsing script"
    }

}

############
### main ###
############

# Specify path to Microsoft.SqlServer.TransactSql.ScriptDom.dll.
# This assembly is available from the Microsoft DacFx NuGet package: https://www.nuget.org/packages/Microsoft.SqlServer.DacFx.x64/
Add-Type -Path "C:\Temp\Microsoft.SqlServer.TransactSql.ScriptDom.dll"

$originalProfileName = "Original mail profile name"
$newProfileName = "'New mail profile name'"

try {

    $connectionString = "Data Source=.;Initial Catalog=YourDatabase;Integrated Security=SSPI;MultipleActiveResultSets=True";
    $connection = New-Object System.Data.SqlClient.SqlConnection($connectionString);
    $query = @"
SELECT QUOTENAME(OBJECT_SCHEMA_NAME(p.object_id)) + '.' + QUOTENAME(p.name) AS procedure_name, sm.definition
FROM sys.procedures AS p
JOIN sys.sql_modules AS sm ON sm.object_id = p.object_id
WHERE sm.definition LIKE N'%sp_send_dbmail%';
"@
    $selectCommand = New-Object System.Data.SqlClient.SqlCommand($query, $connection)
    $connection.Open();
    $reader = $selectCommand.ExecuteReader()
    while($reader.Read()) {
        $newScript = Replace-DatabaseMailProfileNames -script "$($reader["definition"])" -originalProfileName $originalProfileName -newProfileName $newProfileName
        if($newScript -ne $null) {
            $alterCommand = New-Object System.Data.SqlClient.SqlCommand($newScript, $connection)
            [void]$alterCommand.ExecuteNonQuery()
            "Stored procedure $($reader["procedure_name"]) changed"
        }
    }

}
catch {
    throw
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM