I've been re-thinking my suggestions. You have a "sparse data" problem. You
could have any of these conditions
Year 1 data exists, Year 2 data exists
Year 1 data exists, Year 2 data NULL
Year 1 data NULL, Year 2 data exists
Year 1 data NULL, Year 2 data NULL
You want to see the values for all combinations of CodeID, DataSourceID,
ProvID for the year pairs.
While from an academic standpoint it can probably be done in pure SQL with
various subqueries, joins of various types, and UNION queries as in my
previous posting, on second thought I think you'd be better off building a
temporary table. The following should be a lot easier to follow and
maintain. It uses a temporary table and some VBA code to populate it.
Not having all of your table definitions, I've made some assumptions:
a) The table that you collect your data points is
table

ataTable
ID, autonumber, PK
CodeID, number/long, not null
YearID, number, long, not null
DataSourceID, number, long, not null
ProvID, number, long, not null
AMount, Currency, not null
b) you have lookup tables for the codes...
table:lookup_CODE
CodeID, number (autonumber?) long, PK
Code, text
table: lookup_DataSource
DataSourceID, number (autonumber?)long, PK
DataSource, text
table: lookup_Province
ProvID, (autonumber?)long, PK
Province, text
For the lookup_xxx tables, I will assume that there may be gaps in the
sequence of keys. This is handled in the code below by way of loading some
arrays with only existing values.
c) Create this table which will contain your temporary working results
table:temp_MATRIX
link_CodeID, number, long, not null, PK, FK to lookup_CodeID
link_DataSourceID, number, long, not null, PK, FK to lookup_DataSource
link_Province, number, long, not null, PK, FK to lookup_Province
Year_1, number, int, null allowed
Year_2, number, int, null allowed
Amount_1, currency, null allowed
Amount_2, currency, null allowed
This table will contain your de-normalized data, and expand the missing data
to NULLs associated with your key values as needed. It will even contain
rows for the tripliets (CodeID, DataSourceID, ProvID) where you had no
amounts for either year.
d) Then create this module "mod_Matrix". You should be able to
cut-and-paste. Change table names/field names as needed. Make sure that you
have a check in TOOLS/REFERENCES for the Microsoft DAO library.
Option Compare Database
Option Explicit
' be sure that TOOLS/REFERNCES has a check
' opposite Microsoft DAO 3.6 (or similar)
'
Dim longarray_CodeID() As Long
Dim longarray_DataSource() As Long
Dim longarray_ProvID() As Long
Dim int_N_CodeID As Integer
Dim int_N_DataSourceID As Integer
Dim int_N_ProvID As Integer
Private Function load_CodeID_array() As Integer
Dim RS As DAO.Recordset
Dim I As Integer
Dim N As Integer
I = 0
N = 0
Set RS = CurrentDb.OpenRecordset("lookup_CodeID")
If Not RS.EOF Then
RS.MoveLast
N = RS.RecordCount
If N > 0 Then ReDim ongarray_CodeID(1 To N)
RS.MoveFirst
End If
While Not RS.EOF
I = I + 1
longarray_CodeID(I) = RS("CodeID")
RS.MoveNext
Wend
RS.Close
Set RS = Nothing
load_CodeID_array = I
End Function
Private Function load_DataSourceID_array() As Integer
Dim RS As DAO.Recordset
Dim I As Integer
Dim N As Integer
I = 0
N = 0
Set RS = CurrentDb.OpenRecordset("lookup_DataSourceID")
If Not RS.EOF Then
RS.MoveLast
N = RS.RecordCount
If N > 0 Then ReDim longarray_DataSourceID(1 To N)
RS.MoveFirst
End If
While Not RS.EOF
I = I + 1
longarray_DataSourceID(I) = RS("DataSourceID")
RS.MoveNext
Wend
RS.Close
Set RS = Nothing
load_DataSourceID_array = I
End Function
Private Function load_ProvID_array() As Integer
Dim RS As DAO.Recordset
Dim I As Integer
Dim N As Integer
I = 0
N = 0
Set RS = CurrentDb.OpenRecordset("lookup_ProvID")
If Not RS.EOF Then
RS.MoveLast
N = RS.RecordCount
If N > 0 Then ReDim longarray_ProvID(1 To N)
RS.MoveFirst
End If
While Not RS.EOF
I = I + 1
longarray_ProvID(I) = RS("ProvID")
RS.MoveNext
Wend
RS.Close
Set RS = Nothing
load_ProvID_array = I
End Function
Private Sub Clear_temp_Matrix()
Dim SQL As String
SQL = "DELETE * FROM temp_MATRIX"
CurrentDb.Execute SQL
End Sub
Private Function load_ID_arrays() As Integer
load_ID_arrays = 0 ' until otherwise
int_N_CodeID = load_CodeID_array()
If (int_N_CodeID = 0) Then
MsgBox "CodeID table is empty.", vbOKOnly, "Error"
load_ID_arrays = -1
End If
int_N_DataSourceID = load_DataSourceID_array()
If (int_N_DataSourceID = 0) Then
MsgBox "DataSource table is empty.", vbOKOnly, "Error"
load_ID_arrays = -1
End If
int_N_ProvID = load_ProvID_array()
If (int_N_ProvID = 0) Then
MsgBox "ProvID table is empty.", vbOKOnly, "Error"
load_ID_arrays = -1
End If
End Function
Public Function build_temp_Matrix() As Long
Dim RS As DAO.Recordset
Dim fld_RS(0 To 6) As DAO.Field
Dim err As Integer
Dim i_Code As Integer
Dim i_DataSource As Integer
Dim i_Prov As Integer
Dim l_Records As Long
Dim I As Integer
err = load_ID_arrays()
If err Then
build_temp_Matrix = 0
Exit Function
End If
DoCmd.Hourglass True
DoEvents
Clear_temp_Matrix ' erase previous data
l_Records = 0
Set RS = CurrentDb.OpenRecordset("temp_Matrix")
' Since we will be in a tight loop, use field variables so
' VBA doesn't have to work as hard
Set fld_RS(0) = RS("link_CodeID")
Set fld_RS(1) = RS("link_DataSourceID")
Set fld_RS(2) = RS("link_ProvID")
Set fld_RS(3) = RS("Year_1")
Set fld_RS(4) = RS("Year_2")
Set fld_RS(5) = RS("Amount_1")
Set fld_RS(6) = RS("Amount_2")
For i_Code = 1 To int_N_CodeID
For i_DataSource = 1 To int_N_DataSourceID
For i_Prov = 1 To int_N_ProvID
RS.AddNew
fld_RS(0) = longarray_CodeID(i_Code)
fld_RS(1) = longarray_DataSource(i_DataSource)
fld_RS(2) = longarray_ProvID(i_Prov)
fld_RS(3) = Null ' Year 1
fld_RS(4) = Null ' Year 2
fld_RS(5) = Null ' Amount 1
fld_RS(6) = Null ' Amount 2
RS.Update
DoEvents
l_Records = l_Records + 1
Next i_Prov
Next i_DataSource
Next i_Code
For I = 1 to 6
Set fld_RS(I) = Nothing
Next I
RS.Close
Set RS = Nothing
build_temp_Matrix = l_Records
DoCmd.Hourglass False
DoEvents
End Function
Public Function Populate_temp_Matrix(int_Year_1 As Integer, _
int_Year_2
As Integer) _
As Long
Dim N_Records As Long
Dim SQL As String
Dim Y(2) As Integer
Dim I As Integer
Y(1) = int_Year_1
Y(2) = int_Year_2
Debug.Print "Populate_temp_Matrix(" & _
Str(int_Year_1) & "," & _
Str(int_Year_2) & ")"
N_Records = build_temp_Matrix
If N_Records = 0 Then
Populate_temp_Matrix = -1
Exit Function
End If
DoCmd.Hourglass True
DoEvents
For I = 1 To 2
SQL = "UPDATE DataTable INNER JOIN " & vbCrLf & _
" temp_MATRIX ON " & vbCrLf & _
" (DataTable.ProvID = temp_MATRIX.link_ProvinceID) " &
vbCrLf & _
" AND (DataTable.DataSourceID = temp_MATRIX.link_DataSourceID) "
& vbCrLf & _
" AND (DataTable.CodeID = temp_MATRIX.link_CodeID) " & vbCrLf & _
"SET temp_MATRIX.Year_" & Trim(str(I) & _
" = " & Str(Y(I)) & ", " & vbCrLf & _
" temp_MATRIX.Amount_" & Trim(Str(I)) &
" = [amount]" & vbCrLf & _
"WHERE ( DataTable.YearID = " & Str(Y(I)) & " ) "
Debug.Print SQL
CurrentDb.Execute SQL
Next I
Populate_temp_Matrix = N_Records
DoCmd.Hourglass False
DoEvents
Debug.Print "=" & Str(N_Records)
End Function
' ------------------ end mod_Matrix
e) Create a form with
two text boxes - txt_Year_1 and txt_Year_2
command button with this OnClick event
Dim N_Records As Long
N_Records = Populate_temp_Matrix(Me.txt_Year_1, Me.txt_Year_2)
If N_Records comes back > 0 you have a loaded table to work with.