将多个SELECT语句合并为一个SQL查询?ConcatRelated()和Sum()

huangapple go评论62阅读模式
英文:

Combining multiple select statements in one SQL Query? ConcatRelated() and Sum()

问题

我们有一个用于跟踪数据并在共享网络驱动器上发送通信的数据库。最近,我们不得不加入一个流程,用于预填写M1签证申请表格,其中包含我们的表格中没有跟踪的字段,但可以根据我们跟踪的信息计算出来。

我创建了几个SQL查询,从两个表中提取数据,以生成申请所需的某些字段格式。所有查询都从相同的两个表中提取数据。当我将数据库放在我的桌面上时,它运行得非常快。然而,当我将其放在共享网络驱动器上供所有用户访问时,我创建的新功能需要很长时间才能生成结果。数据库在除了使用我创建的新SQL查询之外的所有情况下都可以正常工作。

我在网上阅读到,数据库可能因为在添加生成的结果到表格之前需要运行3-4个查询而变慢。今天我想问一下如何将我的多个SQL查询合并为一个,以希望减少等待时间。

我不熟悉SQL或数据库编程。我们系统中的一切都能正常工作,因为我花了很多时间阅读和试错来生成所需的结果。我将主要的两个需要合并的SQL查询代码发布在下面,希望有人能帮助我找出如何使它工作。我已经花了过去两天时间搜索和阅读,结果只能得出我可能需要像CASE、UNION ALL之类的语句。但我完全不知所措。请帮忙!谢谢!!

查询1

SELECT DISTINCT Request.PermNumber, Request.Status,
Request.Class, Request.StartDate, Request.Days,
ConcatRelated("Class",
"MALTESTConfirmationInformationQry2022",
"PermNumber= " & Request.PermNumber) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE (((Request.Status)="Confirmed") AND ((Request.StartDate)>Date()))
ORDER BY Request.StartDate;

查询2

SELECT Request.PermNumber, Request.Status, Sum(Request.Days) AS TotalDays
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
GROUP BY Request.PermNumber, Request.Status
HAVING (((Request.Status) Like "Confirmed"));

英文:

We have a database that we use for tracking data and sending communications on a shared network drive. Recently we’ve had to incorporate a process to prefill out forms for an M1 Visa Application that has fields we do not track by in our tables but can be calculated based on the information we do track.

I have created several SQL Queries that pull data from two tables to produce certain field formats required on the application. All the queries pull from the same two tables. When I have the database on my desktop, it functions incredibly. However, when I put on the shared network drive for all users to access, the new functions I created take forever to produce results. The database works normally for everything except when utilizing the new SQL Queries I created.

I read online that the database is likely slowing down because it’s having to run 3-4 queries before adding the produced results to the form. I am asking today how to combine my multiple SQL Queries into one, to hopefully reduce the wait time.

I am not familiar with SQL or database coding really. Everything in our system works because I spend hours reading and using trial/error to produce the results needed. Posting the SQL code for the main two queries I need to combine below in hopes that someone can help me figure out how to get it to work. I've spent the past two days googling and reading, only to come up with I may need statements like CASE, UNION ALL, or something of that sort. But I am at a complete loss. Please help! Thank you!!

Query 1

SELECT DISTINCT Request.PermNumber, Request.Status,
     Request.Class, Request.StartDate, Request.Days, 
     ConcatRelated("Class", 
         "MALTESTConfirmationInformationQry2022",
         "PermNumber= " & Request.PermNumber) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE (((Request.Status)="Confirmed") AND ((Request.StartDate)>Date()))
ORDER BY Request.StartDate;

Query 2

SELECT Request.PermNumber, Request.Status, Sum(Request.Days) AS TotalDays
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
GROUP BY Request.PermNumber, Request.Status 
HAVING (((Request.Status) Like "Confirmed"));

答案1

得分: 0

我终于找时间更深入地研究了这个问题。以下是应该运行非常快的最终解决方案。我由于没有任何数据示例并且必须做出一些假设,因此无法完全测试它。但是,您可能会遇到的任何问题应该都相当小。将下面的代码复制到一个模块中:

'**********************************************************************************
' Parameters:
' intAction: 1 = 返回查询的类别字符串
'            2 = 将数据加载到类别字符串集合中
'            3 = 系统维护。将明确清除类别集合
'
' strPermNumber: 用于从集合中检索类别字符串的PermNumber。
'                如果intAction = 2,则为必填项,否则可选
' **********************************************************************************
Public Function fClassConcat(intAction As Integer, _
                             Optional strPermNumber As String) As String

  Dim rsClass           As DAO.Recordset, _
      strLastPermNumber As String, _
      strClasses        As String

  ' 静态声明。集合将保持填充状态
  ' 直到明确清除为止。
  Static colClasses As Collection

  Select Case intAction
    Case 1  ' 返回类别字符串
      fClassConcat = colClasses(strPermNumber)

    Case 2  ' 初始化
      fClassConcat = "@NEVER_EQUAL@"
      strClasses = ""
      Set colClasses = Nothing
      Set colClasses = New Collection

      ' 查询MALTESTConfirmationInformationQry2022的结果必须按PermNumber和Class排序。
      ' 如果已经是这种情况,则只需用以下行替换: Set rsClass = CurrentDb.OpenRecordset(MALTESTConfirmationInformationQry2022, dbOpenDynaset)
      Set rsClass = CurrentDb.OpenRecordset("SELECT PermNumber, Class " & _
                                            "FROM MALTESTConfirmationInformationQry2022 " & _
                                            "ORDER BY PermNumber, Class;", dbOpenDynaset)
      With rsClass
        If Not .BOF Then
          .MoveFirst

          ' 强制第一个循环
          strLastPermNumber = CStr(!PermNumber)

          Do While Not .EOF

            ' PermNumber未更改,因此继续累积类别
            If CStr(!PermNumber) = strLastPermNumber Then
              strClasses = strClasses & !Class & ","

            ' PermNumber更改。将类别字符串(去掉尾随逗号)添加到集合中,并应用PermNumber作为键。
            Else
              colClasses.Add Left(strClasses, Len(strClasses) - 1), strLastPermNumber

              ' 保存当前PermNumber
              strLastPermNumber = CStr(!PermNumber)

              ' 开始累积新PermNumber的类别
              strClasses = !Class & ","
            End If

            .MoveNext
          Loop

          ' 将最后的类别字符串添加到集合中
          colClasses.Add Left(strClasses, Len(strClasses) - 1), strLastPermNumber
        End If

        .Close
      End With

      Set rsClass = Nothing

    Case 3  ' 释放内存(可选。在查询执行后,只需调用此函数,intAction=3)
      Set colClasses = Nothing
  End Select
End Function

现在按照下面所示修改查询,或者只需复制并粘贴它:

SELECT DISTINCT Request.PermNumber, Request.Status,
     Request.Class, Request.StartDate, Request.Days, 
     (SELECT TOP 1 Sum(T1.Days) AS SumDays
      FROM Request AS T1
      GROUP BY T1.PermNumber, T1.Status 
      HAVING T1.PermNumber = Request.PermNumber AND T1.Status = Request.Status;) AS SumRequestDays, 
      fClassConcat(1, [Request].[PermNumber]) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE Request.Status="Confirmed" AND Request.StartDate>Date() AND fClassConcat(2)
ORDER BY Request.StartDate;

我认为您会感到愉快的惊喜。

英文:

I finally got around to looking at this in more depth. Here is the final solution that should run very quickly. I have not been able to fully test it because I do not have any data examples and I have had to make a few assumptions as a result. However, any issues you might come across should be quite minor. Copy the below code into a module:

'**********************************************************************************
' Parameters:
' intAction: 1 = Returns classes string to the query
'            2 = Load data into the class string collection
'            3 = Housekeeping. Will explicitly clear the classes collection
'
' strPermNumber: PermNumber used to retrieve the classes string from the collection.
'                Mandatory if intAction = 2, otherwise optional
' **********************************************************************************
Public Function fClassConcat(intAction As Integer, _
                             Optional strPermNumber As String) As String

  Dim rsClass           As DAO.Recordset, _
      strLastPermNumber As String, _
      strClasses        As String

  ' Static declaration. Collection will remain populated
  ' until explicitly cleared.
  Static colClasses As Collection

  Select Case intAction
    Case 1  ' Return string of classes
      fClassConcat = colClasses(strPermNumber)

    Case 2  ' Initialisation
      fClassConcat="@NEVER_EQUAL@"
      strClasses = ""
      Set colClasses = Nothing
      Set colClasses = New Collection

      ' The result of query MALTESTConfirmationInformationQry2022 must be ordered
      ' by PermNumber and Class. If this is already the case then simply replace the
      ' following line with: Set rsClass = CurrentDb.OpenRecordset(MALTESTConfirmationInformationQry2022, dbOpenDynaset)
      Set rsClass = CurrentDb.OpenRecordset("SELECT PermNumber, Class " & _
                                            "FROM MALTESTConfirmationInformationQry2022 " & _
                                            "ORDER BY PermNumber, Class;", dbOpenDynaset)
      With rsClass
        If Not .BOF Then
          .MoveFirst

          ' Force first loop
          strLastPermNumber = CStr(!PermNumber)

          Do While Not .EOF

            ' PermNumber has not changed so keep accumulating the class
            If CStr(!PermNumber) = strLastPermNumber Then
              strClasses = strClasses & !Class & ","

            ' Change of PermNumber. Add the class string (minus the trailing comma)
            ' to the collection and apply the PermNumber to the key.
            Else
              colClasses.Add Left(strClasses, Len(strClasses) - 1), strLastPermNumber

              ' Save the current PermNumber
              strLastPermNumber = CStr(!PermNumber)

              ' Start accumulating classes for the new PermNumber
              strClasses = !Class & ","

            End If

            .MoveNext
          Loop

          ' Add the last class string to the collection
          colClasses.Add Left(strClasses, Len(strClasses) - 1), strLastPermNumber

        End If

        .Close

      End With

      Set rsClass = Nothing

    Case 3  ' Free up memory (optional. Just call this function with intAction=3 after query execution)
      Set colClasses = Nothing

  End Select

End Function

And now modify the query as shown below or just copy and paste it:

SELECT DISTINCT Request.PermNumber, Request.Status,
     Request.Class, Request.StartDate, Request.Days, 
     (SELECT TOP 1 Sum(T1.Days) AS SumDays
      FROM Request AS T1
      GROUP BY T1.PermNumber, T1.Status 
      HAVING T1.PermNumber = Request.PermNumber AND T1.Status = Request.Status;) AS SumRequestDays, 
      fClassConcat(1, [Request].[PermNumber]) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE Request.Status="Confirmed" AND Request.StartDate>Date() AND fClassConcat(2)
ORDER BY Request.StartDate;

I think you will be pleasantly surprised.

答案2

得分: -1

现在,既然大家已经停止抨击你并争论你问题和标签的语义,试试这个解决方案,它应该回答你原来的问题,即如何将两个查询合并成一个。结果将给你一个额外的列(SumRequestDays),其中包含员工/状态组合的总天数,尽管你可能看不到性能改进太多。正如 @Pointy 所提到的,你应该检查表格上的索引。在 Request 表上的 PermNumber 和 Status 的联合索引将提高性能:

SELECT DISTINCT Request.PermNumber, Request.Status,
     Request.Class, Request.StartDate, Request.Days, 
     (SELECT TOP 1 Sum(T1.Days) AS SumDays
      FROM Request AS T1
      GROUP BY T1.PermNumber, T1.Status 
      HAVING T1.PermNumber = Request.PermNumber AND T1.Status = Request.Status) AS SumRequestDays, 
     ConcatRelated("Class", 
         "MALTESTConfirmationInformationQry2022",
         "PermNumber=" & Request.PermNumber) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE Request.Status="Confirmed" AND Request.StartDate>Date()
ORDER BY Request.StartDate;

我认为你的主要瓶颈是 ConcatRelated 函数,我也相信这个函数起源于 Allen Browne。将一个函数放入查询中总会导致性能下降。你可以考虑预执行选项,通过提前缓冲数据并只需检索一行数据,而不是为每个员工构建和执行完整查询。修改 Allen Browne 的代码以初始化和填充一个 Static Collection,该集合将保存每个员工的所有连接类别的信息,然后包括一些代码来从集合中检索员工项目。初始化和检索操作可以通过单个附加参数来控制。初始化可以放入 SQL 的 WHERE 子句中,SQL 引擎方便地只执行一次,而检索操作将放入 SELECT 子句中。

英文:

Now that everyone has finished bashing you and arguing over the semantics of your question and tags, try this solution which should answer your original question of combining two queries into one. The result will give you an additional column (SumRequestDays) which contains the total days for the employee/status combination although you may not see much of a performance improvement. As @Pointy mentioned, you should check the indexes on your tables. A combined index of PermNumber and Status on the Request table would boost performance:

SELECT DISTINCT Request.PermNumber, Request.Status,
     Request.Class, Request.StartDate, Request.Days, 
     (SELECT TOP 1 Sum(T1.Days) AS SumDays
      FROM Request AS T1
      GROUP BY T1.PermNumber, T1.Status 
      HAVING T1.PermNumber = Request.PermNumber AND T1.Status = Request.Status;) AS SumRequestDays, 
     ConcatRelated("Class", 
         "MALTESTConfirmationInformationQry2022",
         "PermNumber= " & Request.PermNumber) AS AllClasses
FROM Employee INNER JOIN Request ON Employee.PermNumber = Request.PermNumber
WHERE Request.Status="Confirmed" AND Request.StartDate>Date()
ORDER BY Request.StartDate;

I believe your main bottleneck is the ConcatRelated function which I also believe originated from Allen Browne. Putting a function into a query will always result in a performance hit. You might want to consider a pre-execution option to prepare the data in advance by buffering it and only having to retrieve one line rather than building and performing a full-on query for every employee. It would be a quite straight-forward task to modify Allen's code to initialise and populate a Static Collection which would hold all the concatenated classes for each employee and then include some code to retrieve the employee item from the Collection. Initialise and retrieve actions can be controlled by a single additional parameter. The initialisation can be placed into the SQL WHERE clause which the SQL engine conveniently only executes once and the retrieve action would go into the SELECT clause.

huangapple
  • 本文由 发表于 2023年7月20日 21:58:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76730658.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定