获取不带列名插入的存储过程的列表

时间:2019-07-19 02:35:41

标签: sql-server tsql stored-procedures

我想获取所有插入到表中而不指定列名的存储过程的列表。

我通过使用以下存储过程获得的最接近的信息:

CREATE PROCEDURE [dbo].[CheckSQLModules]
AS
    DECLARE m CURSOR FOR
         SELECT '[' + s.name + ']'  + '.[' + o.name + ']' AS name, o.type_desc
         FROM sys.sql_modules m
         INNER JOIN sys.objects o ON m.object_id = o.object_id
         INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
         WHERE m.is_schema_bound = 0

    DECLARE @name sysname, @type_desc sysname

    OPEN m

    FETCH NEXT FROM m into @name, @type_desc

    WHILE @@FETCH_STATUS = 0
    BEGIN
        BEGIN TRY
            EXEC SP_REFRESHSQLMODULE @name
        END TRY
        BEGIN CATCH
            IF @@TRANCOUNT > 0 
                ROLLBACK
            PRINT ERROR_MESSAGE()
            PRINT  @type_desc + ': ' +  @name
            PRINT ''
        END CATCH

        FETCH NEXT FROM m INTO @name,  @type_desc
    END

    CLOSE m
    DEALLOCATE m
GO

但是我不确定结果是否100%正确。

4 个答案:

答案 0 :(得分:0)

通常,在问这样的问题时,最好包括一些到目前为止您尝试过的细节。无论如何,这应该可以帮助您入门。

SELECT      
  ObjectName = obj.Nm,
  Preview    = SUBSTRING(def.Txt, srch.Idx, 100)+'...',
  FullDef    = SUBSTRING(def.Txt, 1, 100)
FROM        INFORMATION_SCHEMA.ROUTINES                     AS r
CROSS APPLY (VALUES(r.SPECIFIC_SCHEMA+'.'+r.SPECIFIC_NAME)) AS obj(Nm)
CROSS APPLY (VALUES(OBJECT_DEFINITION(OBJECT_ID(obj.Nm))))  AS def(Txt)
CROSS APPLY (VALUES(CHARINDEX('INSERT ',def.Txt)))          AS srch(Idx)
WHERE       r.ROUTINE_TYPE = 'PROCEDURE'
AND         srch.Idx > 0;

返回的内容(为简洁起见被删节):

ObjectName                           Preview                             FullDef
------------------------------------ ----------------------------------- -----------------------------------
metadata.usp_objectReadmeSections    INSERT metadata.objectReadmeSe...   CREATE PROC metadata.usp_objec...
dbo.usp_BuildCalendarAB              INSERT dbo.CalendarAB SELECT d...   CREATE PROC dbo.usp_BuildCalen...
metadata.usp_diskUsageByDB           INSERT ##dbSizeInfo...              CREATE PROC metadata.usp_diskU...

这将返回任何包含INSERT语句的存储过程的详细信息。我在这里不区分要插入哪种表(临时表,表变量或常规表)。可以,但是可以根据需要添加其他过滤器。

答案 1 :(得分:0)

您可以使用正则表达式分析代码以查看是否存在类似的模式

  

插入表名选择
  插入表名值

由于TSQL不支持正则表达式,因此您可以编写CLR存储过程,也可以针对本地代码库运行该过程。

下面是检查这些情况的样本正则表达式。您必须启用忽略大小写并使用扩展的正则表达式。

INSERT\s+[a-zA-Z0-9$_]+\s+(SELECT|VALUES)

答案 2 :(得分:0)

不使用脚本DOM来解析T-SQL并不是一件容易的事,因为语义相同的语言构造有多种变体。尝试使用字符串搜索这样做最多是脆弱的,因为很难确定文本是否包含在可执行代码,注释,多行中等之间。

考虑使用Microsoft.SqlServer.TransactSql.ScriptDom中可用的official Microsoft SQL Server Data-Tier Application Framework NuGet package程序集。可以在.NET中以编程方式使用此程序集来满足临时需要,例如您的代码分析规则。下面是一个PowerShell示例,该示例分析指定数据库中的所有存储过程,并列出具有INSERT语句而没有显式列列表的存储过程。

# add script dom assembly type, downloading if needed
Function Add-TSqlScriptDomType() {

    $dacFxNuGetUrl = "https://www.nuget.org/api/v2/package/Microsoft.SqlServer.DacFx.x64"
    $scriptDomAssemblyPath = "$PSScriptRoot\Microsoft.SqlServer.TransactSql.ScriptDom.dll"

    if(![System.IO.File]::Exists($scriptDomAssemblyPath)) {
        # assembly doesn't exist in this script folder, download latest DacFx package from NuGet and extract the T-SQL Script DOM assembly here
        #download DacFx NuGet package containing assembly
        $response = Invoke-WebRequest -Uri $dacFxNuGetUrl
        if ($response.StatusCode -ne 200) {
            throw "Unable to download Microsoft.SqlServer.TransactSql.ScriptDom NuGet package: $($response.StatusCode) : $($response.StatusDescription)"
        }

        # decompress NuGet package to temp folder
        $tempZipFilePath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName() + ".zip")
        [System.IO.File]::WriteAllBytes($tempZipFilePath, $response.Content)
        $response.BaseResponse.Dispose()
        $tempUnzipFolderPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
        Expand-Archive -Path $tempZipFilePath -DestinationPath $tempUnzipFolderPath
        $tempZipFilePath | Remove-Item

        # copy Microsoft.SqlServer.TransactSql.ScriptDom.dll assembly and remove temp files
        Copy-Item "$tempUnzipFolderPath\lib\net*\Microsoft.SqlServer.TransactSql.ScriptDom.dll" $scriptDomAssemblyPath
        $tempUnzipFolderPath | Remove-Item -Recurse
    }

    Add-Type -Path $scriptDomAssemblyPath

}

# return count of insert statements in script without explict column list
Function Get-InsertStatementCountWithoutColumnList($script) {

    $insertStatementCountWithoutColumnList = 0

    # 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) {
        foreach($statement in $fragment.Batches[0].Statements[0].StatementList.Statements) {
            switch($statement.GetType().ToString())
            {
                "Microsoft.SqlServer.TransactSql.ScriptDom.InsertStatement" {
                    # $statement.InsertSpecification.Columns
                    if($statement.InsertSpecification.Columns.Count -eq 0)
                    {
                        $insertStatementCountWithoutColumnList += 1
                    }
                    break
                }
            }
        }

    }
    else {
        throw "Error(s) parsing script"
    }
    return $insertStatementCountWithoutColumnList

}

############
### main ###
############
try {

    Add-TSqlScriptDomType

    $connectionString = "Data Source=.;Initial Catalog=YourDatabase;Integrated Security=SSPI";
    $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;
"@
    $selectCommand = New-Object System.Data.SqlClient.SqlCommand($query, $connection)
    $connection.Open();
    $reader = $selectCommand.ExecuteReader()
    while($reader.Read()) {
         $insertStatementCountWithoutColumnList = Get-InsertStatementCountWithoutColumnList -script "$($reader["definition"])"
        if($insertStatementCountWithoutColumnList -gt 0) {
            Write-Host "Procedure $($reader["procedure_name"]) has $insertStatementCountWithoutColumnList insert statements without a column list"
        }
    }

}
catch {
    throw
}

请注意,此示例下载了NuGet程序包,并将程序集提取到当前脚本文件夹中,并将该位置用于Add-Type引用。如果配置了NuGet存储库,则可以改用NuGet程序包管理器。

答案 3 :(得分:0)

按照 Alan Burstein 的建议,我设法找到了这个解决方案:

  • 创建一个名为 replacespace 的函数以更好地过滤程序文本。
CREATE FUNCTION dbo.ReplaceSpace(@txt varchar(MAX)) RETURNS varchar(MAX)
AS 
BEGIN
   RETURN replace(replace(replace(replace(@txt, char(13), ''), char(10), ''), char(9), ''), ' ', '') 
END
GO DROP TABLE IF EXISTS # t 
GO 
DECLARE @tb_name varchar(100) = 'table_name', @len int 
SELECT
   @len = len(@tb_name) 
   SELECT
      @len = @len + 1 
      SELECT DISTINCT
         object_id INTO # t 
      FROM
         sys.procedures 
      WHERE
         object_definition(object_id) like '%' + @tb_name + '%' CREATE nonclustered INDEX # t_i1 
         ON # t(object_id) 
         DELETE
            # t 
         WHERE
            object_id not in
            (
               SELECT DISTINCT
                  t2.[object_id] ProcedureName 
               FROM
                  sys.sql_dependencies t1 
                  INNER JOIN
                     sys.procedures t2 
                     ON t1.object_id = t2.object_id 
                  INNER JOIN
                     sys.objects t3 
                     ON t1.referenced_major_id = t3.object_id 
               WHERE
                  t3.[name] = @tb_name
            )
            SELECT
               ObjectName = obj.Nm,
               Preview = SUBSTRING(def.Txt, srch.Idx, 100) + '...',
               Preview2 = dbo.ReplaceSpace(replace(replace(replace(SUBSTRING(def.Txt, srch.Idx, 100), 'insert', ''), 'into', ''), 'dbo.', '')),
               Preview3 = substring(dbo.ReplaceSpace(replace(replace(replace(SUBSTRING(def.Txt, srch.Idx, 100), 'insert', ''), 'into', ''), 'dbo.', '')), @len, 1),
               FullDef = SUBSTRING(def.Txt, 1, 100) 
            FROM
               INFORMATION_SCHEMA.ROUTINES AS r CROSS APPLY ( 
            VALUES
               (
                  r.SPECIFIC_SCHEMA + '.' + r.SPECIFIC_NAME
               )
) AS obj(Nm) CROSS APPLY ( 
            VALUES
               (
                  OBJECT_DEFINITION(OBJECT_ID(obj.Nm))
               )
) AS def(Txt) CROSS APPLY ( 
            VALUES
               (
                  CHARINDEX('INSERT ', def.Txt)
               )
) AS srch(Idx) 
            WHERE
               r.ROUTINE_TYPE = 'PROCEDURE' 
               AND dbo.ReplaceSpace(replace(replace(replace(SUBSTRING(def.Txt, srch.Idx, 100), 'insert', ''), 'into', ''), 'dbo.', '')) like @tb_name + '%' 
               AND srch.Idx > 0 
               AND object_id(obj.nm) in 
               (
                  SELECT
                     object_id 
                  FROM
                     # t
               )
               AND substring(dbo.ReplaceSpace(replace(replace(replace(SUBSTRING(def.Txt, srch.Idx, 100), 'insert', ''), 'into', ''), 'dbo.', '')), 16, 1) <> '('