是否可以通过SQL Server中的查询更改存储过程?

时间:2019-06-25 06:26:12

标签: sql sql-server tsql stored-procedures

我需要从所有具有电子邮件警报的存储过程中更改配置文件名称。 是否可以通过SQL Server中的查询更改存储过程。

这将帮助我更新所有具有电子邮件警报的存储过程。

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%'

通过上述查询,我​​需要更改存储过程的主体。

2 个答案:

答案 0 :(得分:1)

您需要运行循环以更新所有过程,并使用sp_helptext获取过程代码并将其存储在字符串变量中。替换变量中所需的字符串值,然后使用更新的字符串变量来运行alter命令并更新proc。

答案 1 :(得分:0)

可以对存储过程代码进行大量更改,但是由于T-SQL构造的多种变体,任务可能相当复杂。简单的字符串替换在某些情况下可能有效,但它很脆弱,因为要替换的文本可能会在其他不应更改的上下文中出现。例如,如果您的原始配置文件名为“邮件”,则替换为哑文本也会更改“ sp_send_dbmail”过程的名称。

下面是一个PowerShell示例,该示例使用Microsoft.SqlServer.TransactSql.ScriptDom程序集更智能地解析T-SQL代码并进行有针对性的更改。这不是一个完整的解决方案,它可以更改所有出现的配置文件名称(例如,局部变量分配,位置sp_send_db_mail参数等),但会更改sp_send_dbmail命名参数语法中指定的值,并且比替换文本更可靠。您可以在开发环境中测试和调整代码以适应您的需求。

此外,作为免责声明,以这种方式更改解析器令牌文本是未记录的,因此后果自负。

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
}