Instead of the using the auto number in Access (sometimes produces duplicates) I've decided to generate my own numbers.
I am using the data macro Before Change but I'm not sure on how to run the query SELECT MAX(ID)+1 FROM MyTable and insert it into the ID field on each Insert.
I've messed around with the SetField, SetLocalVar, LookUpRecord actions but no luck so far.
EDIT: I've tried using DMAX in the expression as per example: https://www.599cd.com/tips/access/incrementing-your-own-counter/. This works when I add a row manually. However, I add rows from Excel VBA at which point this method stops working, generating the error, the function is not valid for expressions used in data macros
You can only use very limited SQL statements in data macros. You can use queries, though.
Create a query (called QueryA), and enter SELECT MAX(ID)+1 As Expr1 FROM MyTable as the SQL
Then, you can use a data macro with the following structure:
If [IsInsert] Then
Look Up A Record In QueryA
SetLocalVar
Name = NewID
Expression = [QueryA].[Expr1]
SetField
Name = ID
Value = NewID
The AXL is the following:
<?xml version="1.0" encoding="UTF-8"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
<DataMacro Event="BeforeChange">
<Statements>
<ConditionalBlock>
<If>
<Condition>[IsInsert]</Condition>
<Statements>
<LookUpRecord>
<Data>
<Reference>QueryA</Reference>
</Data>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">NewID</Argument>
<Argument Name="Value">[QueryA].[Expr1]</Argument>
</Action>
</Statements>
</LookUpRecord>
<Action Name="SetField">
<Argument Name="Field">Field1</Argument>
<Argument Name="Value">[NewID]</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
</Statements>
</DataMacro>
</DataMacros>
You shouldn't use VBA functions or domain aggregates such as DMax in data macros, nor in the queries data macros are dependent upon. If you do, it can only be triggered from a running Access application, because these are only valid from within Access.
Alternatively, you can rewrite your SQL statement to be valid for data macros. This means: no aggregates, no calculations! But you can use ordering to get the maximum value:
If [IsInsert] Then
Look Up A Record In SELECT [MyTable].[ID] As [Expr1] FROM [MyTable] ORDER BY [MyTable].[ID] DESC
Alias A
SetLocalVar
Name = NewID
Expression = [A].[Expr1] + 1
SetField
Name = ID
Value = NewID
The AXL is the following (which makes it easier to understand the limited SQL):
<?xml version="1.0" encoding="UTF-8"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
<DataMacro Event="BeforeChange">
<Statements>
<ConditionalBlock>
<If>
<Condition>[IsInsert]</Condition>
<Statements>
<LookUpRecord>
<Data Alias="A">
<Query>
<References>
<Reference Source="MyTable" />
</References>
<Results>
<Property Source="MyTable" Name="ID" Alias="Expr1" />
</Results>
<Ordering>
<Order Direction="Descending" Source="MyTable" Name="ID" />
</Ordering>
</Query>
</Data>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">NewID</Argument>
<Argument Name="Value">[A].[Expr1]+1</Argument>
</Action>
<Action Name="SetField">
<Argument Name="Field">Field1</Argument>
<Argument Name="Value">[NewID]</Argument>
</Action>
</Statements>
</LookUpRecord>
</Statements>
</If>
</ConditionalBlock>
</Statements>
</DataMacro>
</DataMacros>
Replaces Access Autonumber LongInt Data Type Using simple Data Macro and one simple VBA function (see images x2)
Data Macro
VBA Code
Related
I am working on an Access 2013 database that will have different utility poles entered into the database and linked with other attributes. Each pole will have a unique global ID, and to simplify working I would like to add another unique ID that is more simple. I would like this field auto populated when a new pole in imported into the database. The ID would go as follows:
SAC(year)-(Escalating number, cannot be a duplicate)
ex. SAC16-20 (This would be the 20th pole entered into the database in 2016)
ex. SAC15-2536 (Would be the 2536th pole entered in 2015)
If anyone could help me generate some code to make this auto populate ID field work I would greatly appreciate it.
With Access versions 2010 and later you can use an event-driven data macro to generate the sequential ID. For example, say you have a table named [poledata]. Open it in Design View and add two fields:
alternate_id_seq – Numeric (Long Integer)
alternate_id – Text(20)
Save the changes to your table and then switch to Datasheet View.
In the Access ribbon, switch to the "Table Tools > Table" tab and click "Before Change"
then enter the following macro ...
... or paste the following XML into the macro editor window
<?xml version="1.0" encoding="utf-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
<DataMacro Event="BeforeChange">
<Statements>
<ConditionalBlock>
<If>
<Condition>[IsInsert]</Condition>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">1</Argument>
</Action>
<Action Name="SetLocalVar">
<Argument Name="Name">prefix</Argument>
<Argument Name="Value">"SAC" & Year(Date()) Mod 100 & "-"</Argument>
</Action>
<LookUpRecord>
<Data Alias="pd">
<Query>
<References>
<Reference Source="poledata" Alias="pd" />
</References>
<Results>
<Property Source="pd" Name="alternate_id_seq" />
</Results>
<Ordering>
<Order Direction="Descending" Source="pd" Name="alternate_id_seq" />
</Ordering>
</Query>
<WhereCondition>[pd].[alternate_id] Like [prefix] & "*"</WhereCondition>
</Data>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">[pd].[alternate_id_seq]+1</Argument>
</Action>
</Statements>
</LookUpRecord>
<Action Name="SetField">
<Argument Name="Field">alternate_id_seq</Argument>
<Argument Name="Value">[next_seq]</Argument>
</Action>
<Action Name="SetField">
<Argument Name="Field">alternate_id</Argument>
<Argument Name="Value">[prefix] & [next_seq]</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
</Statements>
</DataMacro>
</DataMacros>
Now when new rows are added to the table the [alternate_id_seq] and [alternate_id] columns will be populated automatically.
I am working on an Access 2013 database that will have different utility poles entered into the database and linked with other attributes. Each pole will have a unique global ID, and to simplify working I would like to add another unique ID that is more simple. I would like this field auto populated when a new pole in imported into the database. The ID would go as follows:
SAC(year)-(Escalating number, cannot be a duplicate)
ex. SAC16-20 (This would be the 20th pole entered into the database in 2016)
ex. SAC15-2536 (Would be the 2536th pole entered in 2015)
If anyone could help me generate some code to make this auto populate ID field work I would greatly appreciate it.
With Access versions 2010 and later you can use an event-driven data macro to generate the sequential ID. For example, say you have a table named [poledata]. Open it in Design View and add two fields:
alternate_id_seq – Numeric (Long Integer)
alternate_id – Text(20)
Save the changes to your table and then switch to Datasheet View.
In the Access ribbon, switch to the "Table Tools > Table" tab and click "Before Change"
then enter the following macro ...
... or paste the following XML into the macro editor window
<?xml version="1.0" encoding="utf-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
<DataMacro Event="BeforeChange">
<Statements>
<ConditionalBlock>
<If>
<Condition>[IsInsert]</Condition>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">1</Argument>
</Action>
<Action Name="SetLocalVar">
<Argument Name="Name">prefix</Argument>
<Argument Name="Value">"SAC" & Year(Date()) Mod 100 & "-"</Argument>
</Action>
<LookUpRecord>
<Data Alias="pd">
<Query>
<References>
<Reference Source="poledata" Alias="pd" />
</References>
<Results>
<Property Source="pd" Name="alternate_id_seq" />
</Results>
<Ordering>
<Order Direction="Descending" Source="pd" Name="alternate_id_seq" />
</Ordering>
</Query>
<WhereCondition>[pd].[alternate_id] Like [prefix] & "*"</WhereCondition>
</Data>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">[pd].[alternate_id_seq]+1</Argument>
</Action>
</Statements>
</LookUpRecord>
<Action Name="SetField">
<Argument Name="Field">alternate_id_seq</Argument>
<Argument Name="Value">[next_seq]</Argument>
</Action>
<Action Name="SetField">
<Argument Name="Field">alternate_id</Argument>
<Argument Name="Value">[prefix] & [next_seq]</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
</Statements>
</DataMacro>
</DataMacros>
Now when new rows are added to the table the [alternate_id_seq] and [alternate_id] columns will be populated automatically.
I am defining a Derived Column transformation in BIML but I am having trouble referencing the output from the previous Excel Source in my Derived Column transformation.
I receive the error upon opening the package after successfully generating the SSIS package and it suggests that it the Derived Transformation cannot find the output from the Excel Source.
Error 2 Error loading AFR_ShareTableBIML.dtsx: The object
"/DTS:Executable/DTS:Executables/DTS:Executable/DTS:ObjectData/pipeline/components/component/inputs/input/inputColumns/inputColumn/properties/property"
references ID "#{Package\Data Flow {Import Share Table CSV}\Source
{Flat File Share Table}.Outputs[Output].Columns[Div c per share]}",
but no object in the package has this ID.
Here is a code snippet:
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<FileFormats>
<FlatFileFormat Name="FFF_AFRShareTable" ColumnNamesInFirstDataRow="true"
FlatFileType="Delimited" IsUnicode="false" TextQualifer="None" HeaderRowsToSkip="6">
<Columns>
<Column Name="Quote Buy" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter=","></Column>
<Column Name="Quote Sell" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter=","></Column>
<Column Name="Div c per share" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter=","></Column>
</Columns>
</FlatFileFormat>
</FileFormats>
<Connections>
<FlatFileConnection Name="FF_AFRShareTable" FileFormat="FFF_AFRShareTable"
FilePath="C:\Temp\Stocks.csv"></FlatFileConnection>
<OleDbConnection Name="CMD DB"
ConnectionString="Data Source=Localhost;Initial Catalog=DB;Provider=SQLNCLI11.1;Integrated Security=SSPI;" CreateInProject="true">
</OleDbConnection>
</Connections>
<Packages>
<Package Name="AFR_ShareTableBIML" ConstraintMode="Linear" ProtectionLevel="DontSaveSensitive">
<Tasks>
<ExecuteSQL Name="SQLTask {OLE_DB} Truncate Security Share Table" ConnectionName="CMD DB">
<DirectInput>truncate table Staging.SecurityShareTable</DirectInput>
</ExecuteSQL>
<Dataflow Name="Data Flow {Import Share Table CSV}">
<Transformations>
<FlatFileSource Name="Source {Flat File Share Table}" ConnectionName="FF_AFRShareTable"></FlatFileSource>
<DerivedColumns Name="DER_NullifyColumns">
<Columns>
<Column Name ="DER_DPS" DataType = "Decimal" Precision="4">
[Div c per share] == "-" ? NULL(DT_DECIMAL, 4) : (DT_DECIMAL, 4)[Div c per share]
</Column>
</Columns>
</DerivedColumns>
</Transformations>
</Dataflow>
</Tasks>
</Package>
</Packages>
I have already defined the column name via the FlatFileFormat and I have confirmed that the expression in the DER_DPS column is is syntactically correct. I found that through replacing the square brackets "[" and "]" with double apostrophes, the SSIS package can be opened. For example:
"Div c per share" == "-" ? NULL(DT_DECIMAL, 4) : (DT_DECIMAL, 4) "Div c per share"
However there are derived column transformation errors on incorrect syntax. Are square brackets special characters in BIML that I need to escape?
That was ... interesting.
It appears that your use of curly braces in your component names causes the Biml expansion to go haywire.
<Biml xmlns="http://schemas.varigence.com/biml.xsd">
<FileFormats>
<FlatFileFormat Name="FFF_AFRShareTable" ColumnNamesInFirstDataRow="true"
FlatFileType="Delimited" IsUnicode="false" TextQualifer="None" HeaderRowsToSkip="6">
<Columns>
<Column Name="Quote Buy" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter=","></Column>
<Column Name="Quote Sell" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter=","></Column>
<!-- Change -->
<Column Name="Div c per share" ColumnType="Delimited" DataType="AnsiString" Length ="50" Delimiter="CRLF"></Column>
</Columns>
</FlatFileFormat>
</FileFormats>
<Connections>
<FlatFileConnection Name="FF_AFRShareTable" FileFormat="FFF_AFRShareTable"
FilePath="C:\ssisdata\so\input\Stocks.csv"></FlatFileConnection>
<OleDbConnection Name="CMD DB"
ConnectionString="Data Source=Localhost\dev2014;Initial Catalog=tempdb;Provider=SQLNCLI11.1;Integrated Security=SSPI;"
CreateInProject="false">
</OleDbConnection>
</Connections>
<Packages>
<Package Name="so_37641290_AFR_ShareTableBIML" ConstraintMode="Linear" ProtectionLevel="DontSaveSensitive">
<Tasks>
<ExecuteSQL Name="SQLTask OLE_DB Truncate Security Share Table" ConnectionName="CMD DB">
<DirectInput>truncate table Staging.SecurityShareTable</DirectInput>
</ExecuteSQL>
<Dataflow Name="Data Flow Import Share Table CSV">
<Transformations>
<FlatFileSource Name="Source Flat File Share Table" ConnectionName="FF_AFRShareTable"></FlatFileSource>
<DerivedColumns Name="DER_NullifyColumns">
<Columns>
<Column Name="DER_DPS" DataType="Decimal" Precision="4"><![CDATA[[Div c per share] == "-" ? NULL(DT_DECIMAL, 4) : (DT_DECIMAL, 4)[Div c per share]]]></Column>
</Columns>
</DerivedColumns>
</Transformations>
</Dataflow>
</Tasks>
</Package>
</Packages>
</Biml>
The above biml works for me. Changes I made:
removed { and } from the tasks and component names
updated the last Column definition within your FlatFileFormat Columns collection to have a delimiter of CRLF instead of ,
I used the CDATA tag for the expression. Not needed here but if you had a > or < in there, then you'd need to escape them as either < or the CDATA approach as I used.
I also cleaned up the Derived Column's entity assignments. There were spaces around the equals and I don't believe those are supposed to be there.
Path updates for flat file + OLE DB to work with my setup.
Source data
0
1
2
3
4
5
Quote Buy,Quote Sell,Div c per share
1,1,1
2,2,2
3,3,-
Results
I am working on an Access 2013 database that will have different utility poles entered into the database and linked with other attributes. Each pole will have a unique global ID, and to simplify working I would like to add another unique ID that is more simple. I would like this field auto populated when a new pole in imported into the database. The ID would go as follows:
SAC(year)-(Escalating number, cannot be a duplicate)
ex. SAC16-20 (This would be the 20th pole entered into the database in 2016)
ex. SAC15-2536 (Would be the 2536th pole entered in 2015)
If anyone could help me generate some code to make this auto populate ID field work I would greatly appreciate it.
With Access versions 2010 and later you can use an event-driven data macro to generate the sequential ID. For example, say you have a table named [poledata]. Open it in Design View and add two fields:
alternate_id_seq – Numeric (Long Integer)
alternate_id – Text(20)
Save the changes to your table and then switch to Datasheet View.
In the Access ribbon, switch to the "Table Tools > Table" tab and click "Before Change"
then enter the following macro ...
... or paste the following XML into the macro editor window
<?xml version="1.0" encoding="utf-16" standalone="no"?>
<DataMacros xmlns="http://schemas.microsoft.com/office/accessservices/2009/11/application">
<DataMacro Event="BeforeChange">
<Statements>
<ConditionalBlock>
<If>
<Condition>[IsInsert]</Condition>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">1</Argument>
</Action>
<Action Name="SetLocalVar">
<Argument Name="Name">prefix</Argument>
<Argument Name="Value">"SAC" & Year(Date()) Mod 100 & "-"</Argument>
</Action>
<LookUpRecord>
<Data Alias="pd">
<Query>
<References>
<Reference Source="poledata" Alias="pd" />
</References>
<Results>
<Property Source="pd" Name="alternate_id_seq" />
</Results>
<Ordering>
<Order Direction="Descending" Source="pd" Name="alternate_id_seq" />
</Ordering>
</Query>
<WhereCondition>[pd].[alternate_id] Like [prefix] & "*"</WhereCondition>
</Data>
<Statements>
<Action Name="SetLocalVar">
<Argument Name="Name">next_seq</Argument>
<Argument Name="Value">[pd].[alternate_id_seq]+1</Argument>
</Action>
</Statements>
</LookUpRecord>
<Action Name="SetField">
<Argument Name="Field">alternate_id_seq</Argument>
<Argument Name="Value">[next_seq]</Argument>
</Action>
<Action Name="SetField">
<Argument Name="Field">alternate_id</Argument>
<Argument Name="Value">[prefix] & [next_seq]</Argument>
</Action>
</Statements>
</If>
</ConditionalBlock>
</Statements>
</DataMacro>
</DataMacros>
Now when new rows are added to the table the [alternate_id_seq] and [alternate_id] columns will be populated automatically.
I have an Access 2010 database with a relationship between parent and child tables. I would like to be able to query the database from an external application and show values from the child table as a concatenated list of values in a single column, similar to what MySQL can produce with its GROUP_CONCAT() function.
This has been asked here many times before, e.g., here:
Combine values from related rows into a single concatenated string value
but those solutions rely on a custom VBA function that is not available to external queries.
Is there a way to make such a concatenated list available to external queries without having to manually build the list in the other application?
Issue
Historically, the Access solution for a GROUP_CONCAT()-type query has been to use a VBA function like Allen Browne's ConcatRelated() (ref: here). However, custom VBA functions are only available to queries run from within Microsoft Access itself, so this is not a viable solution for queries against an Access database from some other application (e.g., a .NET application using OLEDB or ODBC).
Solution
With an Access 2010 (or newer) database we can emulate the behaviour of a MySQL GROUP_CONCAT() query by adding a Long Text ("Memo") field to the parent table and using data macros on the child table to maintain the concatenated list.
For example, for tables [Parents] ...
ParentID ParentName
-------- -------------
1 Homer Simpson
2 Ned Flanders
... and [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
... we can add a new Memo/Long Text field named [ChildList] to the [Parents] table and then add the following data macros to the [Children] table:
[Named Macro: 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>
[After Insert]
<?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>
[After Update]
<?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>
[After Delete]
<?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>
Results
As changes are made to the child table the list in the parent table will automatically be updated:
ParentID ParentName ChildList
-------- ------------- ---------------------------------
1 Homer Simpson Bart; Lisa; Maggie
2 Ned Flanders Rod, the elder; Todd, the younger
Notes
The [ChildList] field is for display purposes only. Editing the values in that field will not change the values in the child table.
The list is separated with ";" & Chr(160) to differentiate it from any ";" & Chr(32) pairs that may be in the actual data. If the non-breaking space (Chr(160)) characters mess up wrapping of the list then we could use the Replace() function in our query to convert ";" & Chr(160) to ";" & Chr(32) or "," & Chr(32) or whatever would be most appropriate.
To populate the lists with existing child data we simply need to "update" one child record for each parent, like so
UPDATE Children SET ChildName=ChildName
WHERE ChildID IN (SELECT MIN(ChildID) AS m FROM Children GROUP BY ParentID)