在不使用 VBA 的情况下从 Access 数据库创建子 table 值的串联列表

Create a concatenated list of child table values from an Access database without using VBA

我有一个 Access 2010 数据库,父子关系 table。我希望能够从外部应用程序查询数据库,并将子 table 的值显示为单个列中的串联值列表,类似于 MySQL 可以用其 GROUP_CONCAT()函数。

这个问题以前在这里被问过很多次,例如,在这里:

Combine values from related rows into a single concatenated string value

但这些解决方案依赖于外部查询无法使用的自定义 VBA 函数。

有没有办法让外部查询可以使用这样的串联列表,而无需在其他应用程序中手动构建列表?

问题

从历史上看,GROUP_CONCAT() 类型查询的 Access 解决方案一直使用 VBA 函数,如 Allen Browne 的 ConcatRelated()(参考:here)。但是,自定义 VBA 函数仅可用于 Microsoft Access 本身的查询 运行,因此对于从其他应用程序(例如,使用OLEDB 或 ODBC)。

解决方案

使用 Access 2010(或更新版本)数据库,我们可以通过向 [=107] 添加长文本 ("Memo") 字段来模拟 MySQL GROUP_CONCAT() 查询的行为=] table 并在 child table 上使用 data macros 来维护串联列表。

例如,对于 tables [Parents] ...

ParentID  ParentName   
--------  -------------
       1  Homer Simpson
       2  Ned Flanders 

...和[Children]...

ChildID  ParentID  ChildName          DisplayOrder
-------  --------  -----------------  ------------
      1         1  Lisa                          2
      2         1  Bart                          1
      3         2  Rod, the elder                1
      4         1  Maggie                        3
      5         2  Todd, the younger             2

...我们可以在[Parents] table中添加一个名为[ChildList]的新Memo/Long Text字段,然后添加以下数据宏到 [Children] table:

[命名宏:UpdateChildList]

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
    <DataMacro Name="UpdateChildList">
        <Parameters>
            <Parameter Name="prmParentID"/>
        </Parameters>
        <Statements>
            <Action Collapsed="true" Name="SetLocalVar">
                <Argument Name="Name">newList</Argument>
                <Argument Name="Value">Null</Argument>
            </Action>
            <ForEachRecord>
                <Data Alias="c">
                    <Query>
                        <References>
                            <Reference Source="Children" Alias="c"/>
                        </References>
                        <Results>
                            <Property Source="c" Name="ChildName"/>
                        </Results>
                        <Ordering>
                            <Order Source="c" Name="DisplayOrder"/>
                        </Ordering>
                    </Query>
                    <WhereCondition>[c].[ParentID]=[prmParentID] And [c].[ChildName] Is Not Null</WhereCondition>
                </Data>
                <Statements>
                    <ConditionalBlock>
                        <If>
                            <Condition>Not IsNull([newList])</Condition>
                            <Statements>
                                <Action Collapsed="true" Name="SetLocalVar">
                                    <Argument Name="Name">newList</Argument>
                                    <Argument Name="Value">[newList] &amp; ";" &amp; Chr(160)</Argument>
                                </Action>
                            </Statements>
                        </If>
                    </ConditionalBlock>
                    <Action Collapsed="true" Name="SetLocalVar">
                        <Argument Name="Name">newList</Argument>
                        <Argument Name="Value">[newList] &amp; [c].[ChildName]</Argument>
                    </Action>
                </Statements>
            </ForEachRecord>
            <LookUpRecord>
                <Data>
                    <Reference>Parents</Reference>
                    <WhereCondition>[Parents].[ParentID]=[prmParentID]</WhereCondition>
                </Data>
                <Statements>
                    <EditRecord>
                        <Data/>
                        <Statements>
                            <Action Collapsed="true" Name="SetField">
                                <Argument Name="Field">Parents.ChildList</Argument>
                                <Argument Name="Value">[newList]</Argument>
                            </Action>
                        </Statements>
                    </EditRecord>
                </Statements>
            </LookUpRecord>
        </Statements>
    </DataMacro>
</DataMacros>

[插入后]

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
    <DataMacro Event="AfterInsert">
        <Statements>
            <Action Name="RunDataMacro">
                <Argument Name="MacroName">Children.UpdateChildList</Argument>
                <Parameters>
                    <Parameter Name="prmParentID" Value="[ParentID]"/>
                </Parameters>
            </Action>
        </Statements>
    </DataMacro>
</DataMacros>

[更新后]

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
    <DataMacro Event="AfterUpdate">
        <Statements>
            <ConditionalBlock>
                <If>
                    <Condition>Updated("ParentID") Or Updated("ChildName")</Condition>
                    <Statements>
                        <Action Name="RunDataMacro">
                            <Argument Name="MacroName">Children.UpdateChildList</Argument>
                            <Parameters>
                                <Parameter Name="prmParentID" Value="[ParentID]"/>
                            </Parameters>
                        </Action>
                        <ConditionalBlock>
                            <If>
                                <Condition>Updated("ParentID")</Condition>
                                <Statements>
                                    <Action Name="RunDataMacro">
                                        <Argument Name="MacroName">Children.UpdateChildList</Argument>
                                        <Parameters>
                                            <Parameter Name="prmParentID" Value="[Old].[ParentID]"/>
                                        </Parameters>
                                    </Action>
                                </Statements>
                            </If>
                        </ConditionalBlock>
                    </Statements>
                </If>
            </ConditionalBlock>
        </Statements>
    </DataMacro>
</DataMacros>

[删除后]

<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
    <DataMacro Event="AfterDelete">
        <Statements>
            <Action Name="RunDataMacro">
                <Argument Name="MacroName">Children.UpdateChildList</Argument>
                <Parameters>
                    <Parameter Name="prmParentID" Value="[Old].[ParentID]"/>
                </Parameters>
            </Action>
        </Statements>
    </DataMacro>
</DataMacros>

结果

随着对 child table 的更改,parent table 中的列表将自动更新:

ParentID  ParentName     ChildList                        
--------  -------------  ---------------------------------
       1  Homer Simpson  Bart; Lisa; Maggie               
       2  Ned Flanders   Rod, the elder; Todd, the younger

备注

  1. [ChildList] 字段仅用于显示目的。编辑该字段中的值 不会 更改 child table.

  2. 中的值
  3. 列表用 ";" & Chr(160) 分隔,以区别于实际数据中可能存在的任何 ";" & Chr(32) 对。如果 non-breaking space (Chr(160)) 字符弄乱了列表的换行,那么我们可以在查询中使用 Replace() 函数将 ";" & Chr(160) 转换为 ";" & Chr(32)"," & Chr(32) 或任何最合适的。

  4. 要用现有的 child 数据填充列表,我们只需要 "update" 每个 parent 一个 child 记录,就像这样

UPDATE Children SET ChildName=ChildName 
WHERE ChildID IN (SELECT MIN(ChildID) AS m FROM Children GROUP BY ParentID)