当内容在单引号中时,使用 PowerShell 将 SQL 文件/字符串拆分为批处理排除拆分

Posted

技术标签:

【中文标题】当内容在单引号中时,使用 PowerShell 将 SQL 文件/字符串拆分为批处理排除拆分【英文标题】:Splitting SQL file/String into batches using PowerShell exclude split when content is in single quotes 【发布时间】:2022-01-06 17:07:59 【问题描述】:

我正在根据字符串 GO 将我的 SQL 文件分成多个批次。 我将***线程How do I split file on batches using powershell作为参考 并注意到此正则表达式不适用于少数情况

如果 go 在单引号中找到,则字符串将被拆分。 (一世 希望避免拆分单引号内的任何文本) 另一种情况是当我使用 go on Declare 语句时 声明@go

我不熟悉正则表达式模式。因此,我尝试搜索一些关于正则表达式的在线文档,并提出了在单引号内查找任何内容以及在拆分时如何忽略文件中的@go 的模式。以下是正则表达式

('([\s\S]*?)') - 让我得到单引号中的字符串,但我不知道如何添加这个匹配以排除当前的正则表达式模式 (?(?:\bGO\b) - 当任何非空白字符位于处理下面 sql 文件中 @go 的 GO 字符串之前时,可以避免拆分李>

SQL 文件内容:

select * from testTbl; GO
select * from testTbl2;
GO

Declare @go varchar(15) = 'IGo test'

select @go

GO

SELECT 'go', '   go  ', 'asdv Igo asdsad',
'
go
'

GO

create table #Temp
(
    IdGo int, 
    GoId Varchar(50)
)

select * from #Temp

drop table #Temp

GO

PowerShell 脚本行:

$batches = ( $scriptData -split "(?:\bGO\b)" ) | %  $_ + "`r`nGO" 

注意:var $scriptData 中包含 SQL 文件内容。

这是一种正确的方法,或者当字符串用单引号括起来时,我们如何排除拆分?有没有更清洁的方法来做到这一点?

仅供参考:我会更新另一个线程的答案,一旦我能找到解决方案。或者,如果有人认为它是重复的,我很乐意更新另一个线程并删除它。

更新:所需输出:

select * from testTbl;
GO
select * from testTbl2;
GO
Declare @go varchar(15) = 'IGo test' select @go
GO
SELECT 'go', ' go ', 'asdv Igo asdsad','go'
GO
create table #Temp ( IdGo int, GoId Varchar(50) ) select * from #Temp drop table #Temp
GO

【问题讨论】:

可能值得添加您想要的输出,以便让其他人更好地了解您的需求。此外,您希望定位的 GO 是否始终大写? GO 可以是大写/小写。 @SantiagoSquarzon 【参考方案1】:

为了将您的输入稳健地解析成批次,您需要一个能够可靠识别句法元素的语言解析器 - regexes 并不复杂足以对输入的语法进行建模。

在没有 T-SQL 解析器的情况下,[1]您可以可能使用 PowerShell 自己的语言解析器[System.Management.Automation.Language.Parser],鉴于两种语言之间存在高级别的共性,因此它应该能够识别您输入中孤立的、非@-前缀GO 标记:

警告关于评论支持

因为 T-SQL 的注释结构与 PowerShell 不同,使用 PowerShell 解析器会为(隔离的)GO 子字符串产生误报在 cmets 内强>.

因此,下面的解决方案使用基于 regex预处理,它删除所有 cmets(额外的工作涉及后处理步骤,可以保留 cmets),但这并不完全可靠,并且依赖于以下假设

引用字符串中没有类似注释的结构。 块引号 (/* ... */) 没有嵌套。 (通过使用balancing group definitions 的更复杂的正则表达式,您或许能够克服这一特殊限制。
# Get the file's content and preprocess it by *removing comments*,
# to prevent GO instances inside them from yielding false positives.
# CAVEAT: This isn't fully robust, but may work well enough in practice.
#         See the notes above this code snippet.
$fileContent = (Get-Content -Raw t.txt) -replace '(?m)^\s*--.*' -replace '(?s)/\*.*?\*/'

# Parse the file content into an AST (Abstract Syntax Tree),
# as if it were PowerShell code.
$ast = [System.Management.Automation.Language.Parser]::ParseInput($fileContent, [ref] $null, [ref] $null)

# Get all locations - in terms of line and column number - of isolated,
# unquoted GO tokens.
$locations = 
  $ast.FindAll( $args[0].Extent.Text -eq 'go' , $false) | 
    Select-Object -ExpandProperty Extent |
      Select-Object StartLineNumber, StartColumnNumber -Unique

# Split the file content into batches by the locations of the 
# isolated, unquoted GO tokens, resulting in an array of strings
# each representing a batch, stored in $batches.
$thisBatch = ''
$lineNo = $locNdx = 0
[string[]] $batches =
  $fileContent -split '\r?\n' | ForEach-Object 
    if (++$lineNo -eq $locations[$locNdx].StartLineNumber) 
      $fromCol = 0
      do 
        $thisBatch + $_.Substring($fromCol, $locations[$locNdx].StartColumnNumber - $fromCol + 2 - 1)
        $thisBatch = ''
        $fromCol = $locations[$locNdx].StartColumnNumber + 2 - 1
       while ($locations[++$locNdx].StartLineNumber -eq $lineNo)
      if ($fromCol -lt $_.Length) 
        $thisBatch =  $_.Substring($fromCol) + "`n"
      
     else 
      $thisBatch += "$_`n"
    
  
# If the last batch wasn't terminated with a GO, we must add it now.
# Remove + "`nGO" if you don't want to append a terminating GO.
if ($thisBatch.Trim())  $batches += $thisBatch + "`nGO" 

# Diagnostic output, to show the resulting batches:
$batches -join "`n-----------------`n"

上述输出,基于您的示例输入:

select * from testTbl; GO
-----------------
select * from testTbl2;
GO
-----------------

Declare @go varchar(15) = 'IGo test'

select @go

GO
-----------------

SELECT 'go', '   go  ', 'asdv Igo asdsad',
'
go
'

GO
-----------------

create table #Temp
(
    IdGo int, 
    GoId Varchar(50)
)

select * from #Temp

drop table #Temp

GO

注意

没有尝试将每个批次压缩成单行表示,但这应该不是问题。

代码还正确处理一行中的多个批次,例如以下示例中的两个完整批次和一个不完整批次:

select * from testTbl0;GO select * from testTbl1 GO Declare @go varchar(15) = 'IGo test'

此外,还包括碰巧没有以GO 终止的最后一批。


[1] 注意:“GO 不是 Transact-SQL 语句;它是 sqlcmdosql 实用程序和 SQL Server Management Studio 代码编辑器识别的命令。” - 见the docs 文档还声明“Transact-SQL 语句不能与 GO 命令占用同一行。”这会使问题中的第一个示例批次在技术上无效,但 Raj(OP)报告说它仍然有效。支持>

【讨论】:

非常感谢。我可以不将每批压缩成单行。我已经用上面的代码进行了测试并得到了预期的结果,除了一种情况,当文件在同一行有类似的东西时(select * from testTbl;GO select * from testTbl2 GO Declare @go varchar(15) = 'IGo test') .找到第一个 GO 语句后处理停止,结果显示为 (select * from testTbl;GO) 并且从未处理文件中的剩余内容 @RajK 根据 MS Docs,语法应该是 GO [count] -- (EOL) 你确定 GO SELECT.. 是有效的语法吗? 好点,@SantiagoSquarzon。基于此,即使当前问题中的第一个示例输入行也是无效的。致quote the docs:“Transact-SQL 语句不能与 GO 命令占用同一行。” @mklement0 有代码修复语法错误应该是不可能的,对吧? ;) @RajK,请查看我的更新,它应该正确处理多批次在线案例。至于 cmets 中的格式:参见***.com/help/formatting - 简而言之:您只能使用不支持换行符的 inline 代码格式 (`...`),并且您不能 double ` 实例 - 使用 \` 嵌入 `

以上是关于当内容在单引号中时,使用 PowerShell 将 SQL 文件/字符串拆分为批处理排除拆分的主要内容,如果未能解决你的问题,请参考以下文章

如何在单引号字符串中使用变量?

如何在单引号字符串中使用变量?

在空格处分割R字符串,但当空格在单引号之间时不分割

如何在单引号内使用单引号?

如何在单引号 php 中使用单引号?

如何使用powershell将文本($)替换为文本文件中的引号