如何识别查询语句中有子查询?

时间:2019-05-31 07:31:10

标签: c# sql sql-server tsql

开发一个用于SQL培训的C#项目,并根据每个主题的培训进行不同的练习。练习之一是使用子查询编写查询。需要评估用户是否在查询语句中使用/实现了子查询。

Q:编写一个SQL查询,以使用Sub查询显示Sales.SalesOrderDetail表中的SalesOrderID,LineTotal,平均LineTotal

Select SalesOrderID,LineTotal [LineTotal],
(Select AVG(LineTotal) from Sales.SalesOrderDetail) as [AverageLineTotal] 
from Sales.SalesOrderDetail 

[AverageLineTotal]是子查询。

我们可以通过任何方式识别它吗?例如执行计划或SP,以标识其在语句中具有子查询

有什么方法可以通过执行计划来识别它吗?

2 个答案:

答案 0 :(得分:0)

如果这是一个c#项目,则可以使用正则表达式解析查询,以查找查询是否包含(选择{any other text})。

public static void Main()
{
    var sql = @"Select SalesOrderID,LineTotal [LineTotal],(Select AVG(LineTotal) from Sales.SalesOrderDetail) as [AverageLineTotal] from Sales.SalesOrderDetail";
    Console.WriteLine(DoesSqlContainSubquery(sql));
}

public bool DoesSqlContainSubquery(string sql)
{
    var regexTest = new Regex(@"\( *Select .*\)", RegexOptions.IgnoreCase);
    var containsSubquery = regexTest.IsMatch(sql);
    return containsSubquery;
}

答案 1 :(得分:0)

Parsing ad-hoc scripts is inherently complex due to the plethora T-SQL constructs and options. That being said, a robust method for targeted use cases is parsing scripts with the Microsoft.SqlServer.TransactSql.ScriptDom.

Below is an example PowerShell script that uses the script DOM assembly from the official Microsoft Dacfx NuGet package, downloading and extracting it if needed.

# Add TSqlScript DOM assembly reference, downloading and extracting to the specified location if needed
$scriptDomAssemblyPath = "C:\Temp\Microsoft.SqlServer.TransactSql.ScriptDom.dll"
$scriptDomNuGetUrl = "https://www.nuget.org/api/v2/package/Microsoft.SqlServer.DacFx.x64/150.4384.2"
if(![System.IO.File]::Exists($scriptDomAssemblyPath)) {
    $response = Invoke-WebRequest -Uri $scriptDomNuGetUrl
    if ($response.StatusCode -ne 200) {
        throw "Unable to download Microsoft.SqlServer.TransactSql.ScriptDom NuGet package: $response.StatusCode : $response.StatusDescription"
    }
    $tempZipFilePath = "$([System.IO.Path]::GetTempPath())/$([System.IO.Path]::GetRandomFileName()).zip"
    [System.IO.File]::WriteAllBytes($tempZipFilePath, $response.Content)
    $response.BaseResponse.Dispose()
    $tempUnzipFolderPath = "$([System.IO.Path]::GetTempPath())/$([System.IO.Path]::GetRandomFileName())"
    Expand-Archive -Path $tempZipFilePath -DestinationPath $tempUnzipFolderPath
    $tempZipFilePath | Remove-Item
    Move-Item "$tempUnzipFolderPath\lib\net46\Microsoft.SqlServer.TransactSql.ScriptDom.dll" "$scriptDomAssemblyPath"
    $tempUnzipFolderPath | Remove-Item -Recurse
}
Add-Type -Path $scriptDomAssemblyPath

# script to be parsed
$scriptText = @"
Select SalesOrderID,LineTotal [LineTotal],
(Select AVG(LineTotal) from Sales.SalesOrderDetail) as [AverageLineTotal] 
from Sales.SalesOrderDetail
"@

#parse script
$parser = New-Object Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser($true)
$parseErrors = New-Object System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]
$scriptReader = New-Object System.IO.StringReader($scriptText)
$script = $parser.Parse($scriptReader, [ref]$parseErrors)
if($parseErrors.Count -gt 0) {
    throw "$($parseErrors.Count) parsing errors"
}

# sanity check for expected SELECT query
if(($script.Batches.Count -ne 1) -or ($script.Batches[0].Statements.Count -ne 1) -or ($script.Batches[0].Statements[0].QueryExpression -eq $null)) {
    throw "script with single SELECT statement expected"
}

# find scalar subquery expression in select list
$subQueryFound = $false
foreach($selectElement in $script.Batches[0].Statements[0].QueryExpression.SelectElements) {
    if($selectElement.Expression.ToString() -eq "Microsoft.SqlServer.TransactSql.ScriptDom.ScalarSubquery") {
        $subQueryFound = $true
        break
    }
}

# show if subquery was used
if($subQueryFound) {
    Write-Host "A subquery is used"
}
else {
    Write-Host "A subquery is not used"
}