在不使用 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] & ";" & Chr(160)</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
<Action Collapsed="true" Name="SetLocalVar">
<Argument Name="Name">newList</Argument>
<Argument Name="Value">[newList] & [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
备注
[ChildList] 字段仅用于显示目的。编辑该字段中的值 不会 更改 child table.
中的值
列表用 ";" & Chr(160)
分隔,以区别于实际数据中可能存在的任何 ";" & Chr(32)
对。如果 non-breaking space (Chr(160)
) 字符弄乱了列表的换行,那么我们可以在查询中使用 Replace()
函数将 ";" & Chr(160)
转换为 ";" & Chr(32)
或 "," & Chr(32)
或任何最合适的。
要用现有的 child 数据填充列表,我们只需要 "update" 每个 parent 一个 child 记录,就像这样
UPDATE Children SET ChildName=ChildName
WHERE ChildID IN (SELECT MIN(ChildID) AS m FROM Children GROUP BY ParentID)
我有一个 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] & ";" & Chr(160)</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
<Action Collapsed="true" Name="SetLocalVar">
<Argument Name="Name">newList</Argument>
<Argument Name="Value">[newList] & [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
备注
[ChildList] 字段仅用于显示目的。编辑该字段中的值 不会 更改 child table.
中的值
列表用
";" & Chr(160)
分隔,以区别于实际数据中可能存在的任何";" & Chr(32)
对。如果 non-breaking space (Chr(160)
) 字符弄乱了列表的换行,那么我们可以在查询中使用Replace()
函数将";" & Chr(160)
转换为";" & Chr(32)
或"," & Chr(32)
或任何最合适的。要用现有的 child 数据填充列表,我们只需要 "update" 每个 parent 一个 child 记录,就像这样
UPDATE Children SET ChildName=ChildName
WHERE ChildID IN (SELECT MIN(ChildID) AS m FROM Children GROUP BY ParentID)