将包含多个值的多行连接成 MS Access 中的单行

Posted

技术标签:

【中文标题】将包含多个值的多行连接成 MS Access 中的单行【英文标题】:Concatenating multiple rows, with multiple values in it, into single line in MS Access 【发布时间】:2019-11-07 21:12:50 【问题描述】:

我正在尝试创建简单的需求管理数据库。基本上我有 2 个如下表:

包含 2 列的合同要求:

CR_ReqID    |   Description
reqCR1      |   Contract req description 1
reqCR2      |   Contract req description 2

SW_requirements

Title               |   SW_ReqID     |  RootReq
SW req description 1|   reqSW1       |   reqCR1, reqCR2
SW req description 2|   reqSW2       |   reqCR1
SW req description 3|   reqSW3       |   reqCR2

我想编写查询来接收这样的表:

CR_ReqID  |Description                  |where used?
reqCR1    |Contract req description 1   |reqSW1, reqSW2  
reqCR2    |Contract req description 2   |reqSW1, reqSW3

“合同要求”和“软件要求”表通过“RootReq”列相互关联

我尝试实现 Allen Browne 的代码 http://allenbrowne.com/func-concat.html#Top

这是我的查询

SELECT Contract_requirements.CR_ReqID, ConcatRelated("SW_ReqID     ","SW_requirements","RootReq = """ & [CR_ReqID] & """") AS Expr1
FROM Contract_requirements;

但我在 Access 中遇到错误

"Error3831: 多值字段 'RootReq' 不能在 WHERE 或 HAVING 子句中使用"

你们能帮我解决这个问题吗? 提前致谢

【问题讨论】:

只有两个值吗? 所以根据error msg,RootReq是一个多值字段。规范化数据结构会更好。 可以有很多值,而不仅仅是2... 【参考方案1】:

构建一个查询,将多值字段元素扩展到单个记录。

查询1

SELECT SW_Requirements.Title, SW_Requirements.SW_ReqID, SW_Requirements.RootReq.Value 
FROM SW_Requirements;

然后将该查询用作 ConcatRelated() 函数的源。

SELECT Contract_Requirements.*, 
ConcatRelated("SW_ReqID","Query1","[SW_Requirements.RootReq.Value]='" & [CR_ReqID] & "'") AS WhereUsed
FROM Contract_Requirements;

建议不要在命名约定中使用空格或标点符号/特殊字符。

【讨论】:

"Query1" 工作正常,但是当我稍后尝试使用 ConcatRelated() 函数时,我收到错误 3464 标准表达式中的数据类型不匹配。我做错了什么? 很难说。我构建了表来复制您发布的数据,然后构建了非常适合我的查询。关于您的表结构,我需要了解什么?字段都是文本类型吗? 在表 SW_requirements 中,列“RootReq”是数字...当我创建查找规则以在 Contract_Requirements 中提交“CR_ReqID”时,它是由 Access 创建的...我无法将 RootReq 更改为文本... 那么CR_ReqID也必须是数字类型或者Contract_Requirements中还有一个字段是加入RootReq的关键字段。删除分隔 CR_ReqID 的撇号。 我不知道如何检查“SW_Requirements.RootReq.Value”的数据类型是什么...【参考方案2】:

您也可以使用我的 DJoin 函数,因为它会接受 SQL 作为源,因此您不需要额外保存的查询:

' Returns the joined (concatenated) values from a field of records having the same key.
' The joined values are stored in a collection which speeds up browsing a query or form
' as all joined values will be retrieved once only from the table or query.
' Null values and zero-length strings are ignored.
'
' If no values are found, Null is returned.
'
' The default separator of the joined values is a space.
' Optionally, any other separator can be specified.
'
' Syntax is held close to that of the native domain functions, DLookup, DCount, etc.
'
' Typical usage in a select query using a table (or query) as source:
'
'   Select
'       KeyField,
'       DJoin("[ValueField]", "[Table]", "[KeyField] = " & [KeyField] & "") As Values
'   From
'       Table
'   Group By
'       KeyField
'
' The source can also be an SQL Select string:
'
'   Select
'       KeyField,
'       DJoin("[ValueField]", "Select ValueField From SomeTable Order By SomeField", "[KeyField] = " & [KeyField] & "") As Values
'   From
'       Table
'   Group By
'       KeyField
'
' To clear the collection (cache), call DJoin with no arguments:
'
'   DJoin
'
' Requires:
'   CollectValues
'
' 2019-06-24, Cactus Data ApS, Gustav Brock
'
Public Function DJoin( _
    Optional ByVal Expression As String, _
    Optional ByVal Domain As String, _
    Optional ByVal Criteria As String, _
    Optional ByVal Delimiter As String = " ") _
    As Variant

    ' Expected error codes to accept.
    Const CannotAddKey      As Long = 457
    Const CannotReadKey     As Long = 5
    ' SQL.
    Const SqlMask           As String = "Select 0 From 1 2"
    Const SqlLead           As String = "Select "
    Const SubMask           As String = "(0) As T"
    Const FilterMask        As String = "Where 0"

    Static Values   As New Collection

    Dim Records     As DAO.Recordset
    Dim Sql         As String
    Dim SqlSub      As String
    Dim Filter      As String
    Dim Result      As Variant

    On Error GoTo Err_DJoin

    If Expression = "" Then
        ' Erase the collection of keys.
        Set Values = Nothing
        Result = Null
    Else
        ' Get the values.
        ' This will fail if the current criteria hasn't been added
        ' leaving Result empty.
        Result = Values.Item(Criteria)
        '
        If IsEmpty(Result) Then
            ' The current criteria hasn't been added to the collection.
            ' Build SQL to lookup values.
            If InStr(1, LTrim(Domain), SqlLead, vbTextCompare) = 1 Then
                ' Domain is an SQL expression.
                SqlSub = Replace(SubMask, "0", Domain)
            Else
                ' Domain is a table or query name.
                SqlSub = Domain
            End If
            If Trim(Criteria) <> "" Then
                ' Build Where clause.
                Filter = Replace(FilterMask, "0", Criteria)
            End If
            ' Build final SQL.
            Sql = Replace(Replace(Replace(SqlMask, "0", Expression), "1", SqlSub), "2", Filter)

            ' Look up the values to join.
            Set Records = CurrentDb.OpenRecordset(Sql, dbOpenSnapshot)
            CollectValues Records, Delimiter, Result
            ' Add the key and its joined values to the collection.
            Values.Add Result, Criteria
        End If
    End If

    ' Return the joined values (or Null if none was found).
    DJoin = Result

Exit_DJoin:
    Exit Function

Err_DJoin:
    Select Case Err
        Case CannotAddKey
            ' Key is present, thus cannot be added again.
            Resume Next
        Case CannotReadKey
            ' Key is not present, thus cannot be read.
            Resume Next
        Case Else
            ' Some other error. Ignore.
            Resume Exit_DJoin
    End Select

End Function

' To be called from DJoin.
'
' Joins the content of the first field of a recordset to one string
' with a space as delimiter or an optional delimiter, returned by
' reference in parameter Result.
'
' 2019-06-11, Cactus Data ApS, Gustav Brock
'
Private Sub CollectValues( _
    ByRef Records As DAO.Recordset, _
    ByVal Delimiter As String, _
    ByRef Result As Variant)

    Dim SubRecords  As DAO.Recordset

    Dim Value       As Variant

    If Records.RecordCount > 0 Then
        While Not Records.EOF
            Value = Records.Fields(0).Value
            If Records.Fields(0).IsComplex Then
                ' Multi-value field (or attachment field).
                Set SubRecords = Records.Fields(0).Value
                CollectValues SubRecords, Delimiter, Result
            ElseIf Nz(Value) = "" Then
                ' Ignore Null values and zero-length strings.
            ElseIf IsEmpty(Result) Then
                ' First value found.
                Result = Value
            Else
                ' Join subsequent values.
                Result = Result & Delimiter & Value
            End If
            Records.MoveNext
        Wend
    Else
        ' No records found with the current criteria.
        Result = Null
    End If
    Records.Close

End Sub

完整的文档可以在我的文章中找到:

Join (concat) values from one field from a table or query

如果您没有帐户,请浏览链接:阅读全文。

代码也在 GitHub 上:VBA.DJoin

【讨论】:

谢谢你的帖子,我也试试:)

以上是关于将包含多个值的多行连接成 MS Access 中的单行的主要内容,如果未能解决你的问题,请参考以下文章

MS Access - 创建并填充包含由逗号分隔且没有重复值的连接文本的列

SQL将多行左连接成一行

MS Access 中的多行插入语句

错误 3296:具有多个 JOIN 的 MS Access 查询中不支持连接表达式

MS Access表与多个外键在同一表中的关系。

使用条件连接 MS Access/SQLQuery 中的多个表