Looping to find related records - sql

Looking for assistance/direction in setting up a loop? function to find related records in a table.
The table (tblTransactions) holds information about various transactions we are tracking. I am also using this table to reference a predecessor transaction. Now I am seeking a way to loop through the table to find related records.
The table has the following fields:
TransID - primary key
Grantor - name field
Grantee - name field
PTrans - number field that references TransID
Some sample data:
+---------+---------+---------+--------+
| TransID | Grantor | Grantee | PTrans |
+---------+---------+---------+--------+
| 1 | Bob | Sally | 0 |
| 2 | Jane | Emily | 0 |
| 3 | Sally | Beth | 1 |
| 4 | Beth | Sam | 3 |
+---------+---------+---------+--------+
Ideally I'd like to be able to start with TransID 4 and show all the transaction data, on separate rows, for the selected transaction (4) and it's predecessors.
Results would be:
+---+-------+-------+
| 4 | Beth | Sam |
| 3 | Sally | Beth |
| 1 | Bob | Sally |
+---+-------+-------+

Your question concerning querying self-referential data is very similar to this question in which the user has a table of employees, each of which may have a supervisor whose employeee record is also present in the same table, thus forming a hierarchy.
A relatively easy way to solve this would be using a DLookup expression within a loop or within a recursive call until the expression returned Null. For example, here is a recursive variant:
Function TransLookup(lngtrn As Long)
Dim lngptr
lngptr = DLookup("ptrans", "tbltransactions", "transid = " & lngtrn)
If Not IsNull(lngptr) Then
Debug.Print lngtrn ' Do something with the data
TransLookup (lngptr)
End If
End Function
Evaluated with your data this would yield:
?TransLookup(4)
4
3
1
This is of course only printing the transaction ID, but the function could alternatively populate a separate table with the data for each transaction if required.
However, returning the results record-by-record or populating a temporary table seems inelegant if we can construct a single SQL query to return all of the results in one go.
However,since MS Access does not support recursive SQL queries, the difficulty when querying such hierarchical data is not knowing how many levels to code ahead of time.
As such, you could use a VBA function to construct the SQL query itself, and thus always incorporating as many levels as is necessary to return the full dataset.
Indeed, this is the approach I put forward in my answer to the related question linked above - the function provided in that answer could equally be adapted to suit this situation, for example:
Function BuildQuerySQL(lngtrn As Long) As String
Dim intlvl As Integer
Dim strsel As String: strsel = selsql(intlvl)
Dim strfrm As String: strfrm = "tbltransactions as t0 "
Dim strwhr As String: strwhr = "where t0.transid = " & lngtrn
While HasRecordsP(strsel & strfrm & strwhr)
intlvl = intlvl + 1
BuildQuerySQL = BuildQuerySQL & " union " & strsel & strfrm & strwhr
strsel = selsql(intlvl)
If intlvl > 1 Then
strfrm = "(" & strfrm & ")" & frmsql(intlvl)
Else
strfrm = strfrm & frmsql(intlvl)
End If
Wend
BuildQuerySQL = Mid(BuildQuerySQL, 8)
End Function
Function HasRecordsP(strSQL As String) As Boolean
Dim dbs As DAO.Database
Set dbs = CurrentDb
With dbs.OpenRecordset(strSQL)
HasRecordsP = Not .EOF
.Close
End With
Set dbs = Nothing
End Function
Function selsql(intlvl As Integer) As String
selsql = "select t" & intlvl & ".* from "
End Function
Function frmsql(intlvl As Integer) As String
frmsql = " inner join tbltransactions as t" & intlvl & " on t" & intlvl - 1 & ".ptrans = t" & intlvl & ".transid "
End Function
Now, evaluating the BuildQuerySQL function with Transaction ID 4 yields the following SQL UNION query, with each level of nesting unioned with the previous query:
select
t0.*
from
tbltransactions as t0
where
t0.transid = 4
union
select
t1.*
from
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
where
t0.transid = 4
union
select
t2.*
from
(
tbltransactions as t0 inner join tbltransactions as t1
on t0.ptrans = t1.transid
)
inner join tbltransactions as t2
on t1.ptrans = t2.transid
where
t0.transid = 4
Such function may therefore be evaluated to construct a saved query, e.g. for Transaction ID = 4, the following would create a query called TransactionList:
Sub test()
CurrentDb.CreateQueryDef "TransactionList", BuildQuerySQL(4)
End Sub
Or alternatively, the SQL may be evaluated to open a RecordSet of the results, depending on the requirements of your application.
When evaluated with your sample data, the above SQL query will yield the following results:
+---------+---------+---------+--------+
| TransID | Grantor | Grantee | PTrans |
+---------+---------+---------+--------+
| 1 | Bob | Sally | 0 |
| 3 | Sally | Beth | 1 |
| 4 | Beth | Sam | 3 |
+---------+---------+---------+--------+

Related

Count number of table columns that are not null - MS Access SQL

I have a table which I have shown a simplified example of below:
ID | Item1 | Item2 | Item3 | Item4 | Item5
------------------------------------------
A | NULL | NULL | YES | YES | NULL
B | NULL | NULL | NULL | YES | NULL
C | NULL | NULL | NULL | NULL | NULL
I want to return the following data set:
ID | Count
------------
A | 2
B | 1
C | 0
I.e. I want a count of how many of the columns are NOT NULL for that ID
One potential solution would be
SELECT
ID,
SUM(
IIf(Item1 is NULL,0,1)
+
IIf(Item2 is NULL,0,1)
+
IIf(Item3 is NULL,0,1)
+
IIf(Item4 is NULL,0,1)
+
IIf(Item5 is NULL,0,1)
) 'Count'
FROM
tableName
GROUP BY
ID
However in practice the real table I am using has over a hundred columns and I would prefer to avoid having to write out the names of each column. Is there a simpler way to do this?
You can use VBA to loop through every record and field:
Function CountFields()
Set db = CurrentDb()
db.Execute ("delete * from ItemCounts")
Set RS = db.OpenRecordset("select * from [DataTable]")
RS.MoveFirst
Do While Not RS.EOF
Id = RS.Fields("ID").Value
Count = 0
For Each Item In RS.Fields
If (Item.Name <> "ID" And RS.Fields(Item.Name).Value <> "") Then Count = Count + 1
Next Item
db.Execute ("insert into ItemCounts (ID,[count]) select " & Id & "," & Count)
RS.MoveNext
Loop
MsgBox ("done")
End Function
This puts the counts in a table called ItemCounts, which needs to be set up before the VBA is executed. The fields in that table are ID and Count.
And, if you can reformat the source data, I agree with Minty - but I know that's not always feasible.
Your data is not normalised and therefore you are having to perform gymnastics in your code to work around the problem.
Your data should be stored vertically not horizontally;
ID | ItemNo | Value
---------------------
A | 2 | 1
A | 3 | 1
B | 4 | 1
This would make your query a simple total query, and allow for any number of items. You are also only storing data when you have some not for every case.
Edit: This will loop through the fields
Dim Rst As Recordset
Dim f As Field
Set Rst = CurrentDb.OpenRecordset(TableName)
For Each f In Rst.Fields
Debug.Print (f.name)
Next
Rst.Close
You can reduce it a little:
SELECT
ID,
ABS(SUM((Item1 is Not NULL)+(Item2 is Not NULL)+(Item3 is Not NULL)+(Item4 is Not NULL)+(Item5 is Not NULL))) As [Count]
FROM
tableName
GROUP BY
ID

I want to transpose rows into columns in ms-access

I need to transpose rows into columns in MS Access database, VBA, SQL both the codes are welcome.
Table
| Name | Ticker | ID | Innovation | Quality | Year |
| XYZ | PQR | 11 | 1 | 1 | 2009 |
| XYZ | PQR | 11 | 0 | 1 | 2010 |
| XYZ | PQR | 11 | 1 | 0 | 2011 |
| XYZ | PQR | 11 | 1 | 1 | 2012 |
Desired Table
| Year | 2009 | 2010 | 2011 | 2012 |
| Name | XYZ | XYZ | XYZ | XYZ |
| Ticker | PQR | PQR | PQR | PQR |
| ID | 11 | 11 | 11 | 11 |
| Innovation | 1 | 0 | 1 | 1 |
| Quality | 1 | 1 | 0 | 1 |
As you can see from the desired table, I am trying to have the Year row as Column and list all the columns apart from Year as my rows.
I have tried using Tranform and Pivot function in MS Access but it only Pivots one variable. Let me know your thoughts on it.
The below code failed in transposing all the variables.
TRANSFORM Max([Quality])
SELECT Ticker
FROM Table
Where Ticker = "XYZ"
GROUP BY Ticker
PIVOT Year;
Also, if possible I want to publish it as PDF document.
Thanks in advance,
RVG
Access TRANSFORM is not really intuitive and easy to use and I do not think you can use it that way. Each result row is supposed to be an aggregate of your table. I know of no way to get the previous field names into a new column.
See a working example:
TRANSFORM and PIVOT in Access 2013 SQL
What you really seem to want is just a new presentation to the existing data.
Excel might help.
I have never exported pdf from Access but from Excel is easy. Here is an example:
Sub ExportPdf(path As String, Optional openAfter As Boolean)
''calculate best range for print area
Dim lastCol As Integer
Dim firstRow As Integer
Dim lastRow As Integer
lastCol = pt.TableRange2.Columns(pt.TableRange2.Columns.Count).Column
firstRow = pt.TableRange2.Rows(1).Row
lastRow = ms.Cells(pt.TableRange2.Rows.Count * 3, 1).End(xlUp).Row
Worksheets(ContextSheet).PageSetup.PrintArea = Range(Cells(firstRow, 1), Cells(lastRow, lastCol)).Address
Worksheets(ContextSheet).ExportAsFixedFormat Type:=xlTypePDF, Filename:= _
path & "\Area " & getPivotTablePageFilters(getPivotTable()) & ".pdf", Quality:= _
xlQualityStandard, IncludeDocProperties:=True, IgnorePrintAreas:=False, _
OpenAfterPublish:=openAfter
End Sub
This is a vb script that takes the data from TableSource and transposes it into TableTranspose. The 2nd table has to be set up with a column named FName to take the field names, and columns for each year in the source table.
Function Transpose()
Set db = CurrentDb()
db.Execute ("delete * from TableTranspose")
Set RS = db.OpenRecordset("TableSource")
Set YrList = db.OpenRecordset("select distinct [Yr] from [TableSource] Group by [Yr]")
For Each F In RS.Fields
FN = F.Name
INS = "Insert Into TableTranspose (FName"
SQL = "Select '" & FN & "'"
YrList.MoveFirst
Do While Not YrList.EOF
YR = YrList.Fields("YR").Value
INS = INS & ",[" & YR & "]"
SQL = SQL & ",max(iif(YR=" & YR & ",[" & FN & "])) AS [" & YR & "]"
YrList.MoveNext
Loop
SQL = SQL & " From TableSource"
db.Execute (INS & ") " & SQL)
Next F
MsgBox ("Done")
End Function
This works by processing one field at a time to match the layout of the desired output, and looping through each year of TableSource to find the data to make up the row in TableTranspose. It shouldn't matter how many fields there are or what they are named.
It will create a row in the output for the Year, which will be redundant - you can delete it, or add logic to skip that field if necessary.
This seems to work fine with the 4 years of data in your sample, and should extend OK to more years. It's possible that you will hit a limit on SQL command length if there are too many years in the data, but I think not.
If you are filtering the records from TableSource, you can add the WHERE clause on the line just from the db.execute near the bottom.
Is the data always the same set of field names and years? If so, you might be able to use a UNION query, something like:
Select "Name" as [FName], max(iif(year="2009",name))as [2009], max(iif(year="2010"
,name)) as [2010], max(iif(year="2011",name)) as [2011], max(iif(year="2012", name)) as [2012] from Table group by FName
Union all Select "Ticker", max(iif(year="2009",ticker)), max(iif(year="2010"
,ticker)), max(iif(year="2011",ticker)), max(iif(year=-"2012",ticker)) from Table group by FName
Union all Select "ID", max(iif(year="2009",id)), max(iif(year="2010"
,id)), max(iif(year="2011",is)), max(iif(year="2012",id)) from Table group by FName
Union all Select "Innovation", max(iif(year="2009",innovation)), max(iif(year="2010"
,innovation)), max(iif(year="2011",innovation)), max(iif(year=-"2012",innovation)) from Table group by FName
Union all Select "Quality", max(iif(year="2009",quality)), max(iif(year="2010"
,quality)), max(iif(year="2011",quality)), max(iif(year=-"2012",quality)) from Table group by FName

How to get correct row count from data view based on distinct values in Two Columns

I have data view in which I need to get row count based on the Combined list of distinct values of two columns(say ColumnA and ColumnB ). I can get the String list of distinct values from these two columns. The part I am struggling with is how to get exact row count (into a List of Integer) from the dataview based on each item in the String list. I think the trouble is some values of ColumnA may not be present in ColumnB and vice versa.If I apply a row filter based on ColumnA , it is possible to miss out on some of the values ColumnB Which is why I did something like below. But I end up getting extra count in the end in my Integer list. If I dont do this then I miss out on some of the values resulting in getting lower combined total than the no. of rows.
Please advise. I need to use the final List of String and Int values to create a basic table and then create a chart in excel.
For x As Integer = 0 To DistinctColValueList.Count - 1
'' OR Condition Below Gives more count
'Dv.RowFilter = "ColumnA = '" & DistinctColValueList.ElementAt(x) & "' OR ColumnB = '" & DistinctColValueList.ElementAt(x) & "'"
Dv.RowFilter = "ColumnA = '" & DistinctColValueList.ElementAt(x) & "'"
DistinctIntValueList.Add(Dv.Count)
Next
Dim ClonedList As New List(Of Integer)
For Each item In DistinctIntValueList
ClonedList.Add(item)
Next
Dv.RowFilter = ""
DistinctIntValueList.Clear()
For y As Integer = 0 To DistinctColValueList.Count - 1
Dv.RowFilter = "ColumnB = '" & DistinctColValueList.ElementAt(y) & "'"
Dim Abs_Diff = Math.Abs(ClonedList.ElementAt(y) - Dv.Count)
DistinctIntValueList.Add(ClonedList.ElementAt(y) + Abs_Diff)
Next
Update: added example table.
+-----------------------------+
|Item |Column_A |Column B |
| | | |
+-----------------------------+
| XYZ1 | Loc1 | Loc5 |
| XYZ2 | Loc13 | Loc6 |
| XYZ3 | Loc4 | Loc4 |
| XYZ4 | Loc6 | Loc10 |
| XYZ5 | Loc5 | Loc2 |
| XYZ6 | Loc5 | Loc6 |
| XYZ7 | Loc6 | Loc9 |
| XYZ8 | Loc4 | Loc10 |
| XYZ9 | Loc6 | Loc13 |
| XYZ10 | Loc2 | Loc10 |
+--------+----------+---------+
One solution to your problem is concat the values in Column A and Column B where A<>B and then Concat where they are the same and do the counts.
Using the DataTable provided I wrote the following. It also has the advantage of getting the distinct values and the counts at once
Dim dt As New DataTable("Test")
dt.Columns.Add("Item", "".[GetType]())
dt.Columns.Add("Column_A", "".[GetType]())
dt.Columns.Add("Column_B", "".[GetType]())
dt.Rows.Add("XYZ1", "Loc1", "Loc5")
dt.Rows.Add("XYZ2", "Loc13", "Loc6")
dt.Rows.Add("XYZ3", "Loc4", "Loc4")
dt.Rows.Add("XYZ4", "Loc6", "Loc10")
dt.Rows.Add("XYZ5", "Loc5", "Loc2")
dt.Rows.Add("XYZ6", "Loc5", "Loc6")
dt.Rows.Add("XYZ7", "Loc6", "Loc9")
dt.Rows.Add("XYZ8", "Loc4", "Loc10")
dt.Rows.Add("XYZ9", "Loc6", "Loc13")
dt.Rows.Add("XYZ10", "Loc2", "Loc10")
'get a collection where A <> B
Dim diff = dt.AsEnumerable().Where(Function(x) x.Field(Of String)("Column_A") <> x.Field(Of String)("Column_B"))
'get a concat of A and B where A<>B and concat where A=B
Dim concat = diff.[Select](Function(x) x.Field(Of String)("Column_A")) _
.Concat(diff.[Select](Function(x) x.Field(Of String)("Column_B"))) _
.Concat(dt.AsEnumerable() _
.Where(Function(x) x.Field(Of String)("Column_A") = x.Field(Of String)("Column_B")) _
.[Select](Function(x) x.Field(Of String)("Column_A")))
'Group by to get the counts
Dim result = concat.GroupBy(Function(value) value).[Select](Function(group) New With { _
Key .Value = group.Key, _
Key .Count = group.Count() _
})
Console.WriteLine("Value | Count")
For Each x In result
Console.WriteLine("{0} | {1}", x.Value, x.Count)
Next
Results
Value | Count
Loc1 | 1
Loc13 | 2
Loc6 | 5
Loc5 | 3
Loc4 | 2
Loc2 | 2
Loc10 | 3
Loc9 | 1

Date continuity in a database (find missing date gaps)

I have a database of users who pay monthly payment. I need to check if there is continuity in these payments.
For example in the table below:
+---------+------------+
| user_id | date |
+---------+------------+
| 1 | 2015-02-01 |
| 2 | 2015-02-01 |
| 3 | 2015-02-01 |
| 1 | 2015-03-01 |
| 2 | 2015-03-01 |
| 3 | 2015-03-01 |
| 4 | 2015-03-01 |
| 1 | 2015-04-01 |
| 2 | 2015-04-01 |
| 3 | 2015-04-01 |
| 4 | 2015-04-01 |
| 5 | 2015-04-01 |
| 1 | 2015-05-01 |
| 2 | 2015-05-01 |
| 3 | 2015-05-01 |
| 4 | 2015-05-01 |
| 5 | 2015-05-01 |
| 1 | 2015-06-01 |
| 2 | 2015-06-01 |
| 3 | 2015-06-01 |
| 5 | 2015-06-01 |
| 3 | 2015-07-01 |
| 4 | 2015-07-01 |
| 5 | 2015-07-01 |
+---------+------------+
Until May everything was ok, but in June user 4 didn't pay although he paid in the next month (July).
In July users 1 and 2 didn't pay, but this is ok, because they could resign from the service.
So in this case I need to have information "User 4 didn't pay in June".
Is it possible to do that using SQL?
I use MS Access if it's necessary information.
From my experience, you cannot just work with paid in table to fill the gaps. If in case all of your user does not pay a specific month, it is possible that your query leaves that entire month out of equation.
This means you need to list all dates from Jan to Dec and check against each user if they have paid or not. Which again requires a table with your requested date to compare.
Dedicated RDBMS provide temporary tables, SP, Functions which allows you to create higher level/complex queries. on the other hand ACE/JET engine provides less possibilities but there is a way around to get this done. (VBA)
In any case, you need to give the database specific date period in which you are looking for gaps. Either you can say current year or between yearX and yearY.
here how it could work:
create a temporary table called tbl_date
create a vba function to generate your requested date range
create a query (all_dates_all_users) where you select the requested dates & user id's (without a join) this will give you all dates x all users combination
create another query where you left join all_dates_all_users query with your user_payments query. (This will produce all dates with all users and join to your user_payments table)
perform your check whether user_payments is null. (if its null user x hasn't paid for that month)
Here is an example:
[Tables]
tbl_date : id primary (auto number), date_field (date/Time)
tbl_user_payments: pay_id (auto number, primary), user_id (number), pay_Date (Date/Time) this is your table modify it as per your requirements. I'm not sure if you have a dedicated user table so i use this payments table to get the user_id too.
[Queries]
qry_user_payments_all_month_all_user:
SELECT Year([date_field]) AS mYear, Month([date_field]) AS mMonth, qry_user_payments_user_group.user_id
FROM qry_user_payments_user_group, tbl_date
ORDER BY Year([date_field]), Month([date_field]), qry_user_payments_user_group.user_id;
qry_user_payments_paid_or_not_paid
SELECT qry_user_payments_all_month_all_user.mYear,
qry_user_payments_all_month_all_user.mMonth,
qry_user_payments_all_month_all_user.user_id,
IIf(IsNull([tbl_user_payments].[user_id]),"Not paid","Paid") AS [Paid?]
FROM qry_user_payments_all_month_all_user
LEFT JOIN tbl_user_payments ON (qry_user_payments_all_month_all_user.user_id = tbl_user_payments.user_id)
AND ((qry_user_payments_all_month_all_user.mMonth = month(tbl_user_payments.[pay_date]) AND (qry_user_payments_all_month_all_user.mYear = year(tbl_user_payments.[pay_date]) )) )
ORDER BY qry_user_payments_all_month_all_user.mYear, qry_user_payments_all_month_all_user.mMonth, qry_user_payments_all_month_all_user.user_id;
[Function]
Public Function FN_CRETAE_DATE_TABLE(iDate_From As Date, Optional iDate_To As Date)
'---------------------------------------------------------------------------------------
' Procedure : FN_CRETAE_DATE_TABLE
' Author : KRISH KM
' Date : 22/09/2015
' Purpose : will generate date period and check whether payments are received. A query will be opened with results
' CopyRights: You are more than welcome to edit and reuse this code. i'll be happy to receive courtesy reference:
' Contact : krishkm#outlook.com
'---------------------------------------------------------------------------------------
'
Dim From_month, To_Month As Integer
Dim From_Year, To_Year As Long
Dim I, J As Integer
Dim SQL_SET As String
Dim strDoc As String
strDoc = "tbl_date"
DoCmd.SetWarnings (False)
SQL_SET = "DELETE * FROM " & strDoc
DoCmd.RunSQL SQL_SET
If (IsMissing(iDate_To)) Or (iDate_To <= iDate_From) Then
'just current year
From_month = VBA.Month(iDate_From)
From_Year = VBA.Year(iDate_From)
For I = From_month To 12
SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & From_Year & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
DoCmd.RunSQL SQL_SET
Next I
Else
From_month = VBA.Month(iDate_From)
To_Month = VBA.Month(iDate_To)
From_Year = VBA.Year(iDate_From)
To_Year = VBA.Year(iDate_To)
For J = From_Year To To_Year
For I = From_month To To_Month
SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & J & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
DoCmd.RunSQL SQL_SET
Next I
Next J
End If
DoCmd.SetWarnings (True)
On Error Resume Next
strDoc = "qry_user_payments_paid_or_not_paid"
DoCmd.Close acQuery, strDoc
DoCmd.OpenQuery strDoc, acViewNormal
End Function
you can call this public function from button or form or debug window:
?FN_CRETAE_DATE_TABLE("2015-01-01","2015-10-01")
this will generate from jan to oct and check whether you received payments or not.
[Screen]:
Something like this, look for user where next month is not paid for, but the month after that is paid for:
select user_id, month(date) + 1
from tablename t1
where not exists (select 1 from tablename t2
where t2.user_id = t1.user_id
and month(t2.date) = month(t1.date) + 1)
and exists (select 1 from tablename t3
where t3.user_id = t1.user_id
and month(t3.date) > month(t1.date) + 2)
Note 1: No dbms is specified, so month() function is just pseudo code. ANSI SQL has extract(month from column_name).
Note 2: in ANSI SQL date is a reserved word and needs to be delimited as "date".
Note 3: Just realized this answer will only return the first month even if several (consecutive) are missing...
I have written a simple query for this but I realize that it's not the best solution. Other solutions are still welcome.
SELECT user_id,
MIN(date) AS min_date,
MAX(date) AS max_date,
COUNT(*) AS no_of_records,
round((MAX(date)-MIN(date))/30.4+1,0) AS months,
(months-no_of_records) AS diff
FROM test
GROUP BY user_id
HAVING (round((MAX(date)-MIN(date))/30.4+1,0)-COUNT(*)) > 0
ORDER BY 6 DESC;
Now we can take a look at columns "no_of_records" and "months". If they are not equal, there was a gap for this user.

Concatenate in Access 2000

I have searched 'concatenate' topics and have been unable to find the answer I need. This posting came close access sql query to concatenate rows but my attempts to make it work for my purpose failed.
What I have is a table like this
Lic# | Permit | Year
------------------------
1 | NS1 | 2003
1 | NS1 | 2004
1 | NS2 | 2004
2 | TR | 2012
2 | NS2 | 2012
3 | OR | 2008
2 | OR | 2011
2 | NS1 | 2011
2 | TR | 2011
....And so forth. This table has many unique license numbers with permit type and year (from 2003-2012) listed for each.
What I would like is to create a table that would display the information like this
Lic# | Permit | Year
-----------------------------
1 |NS1 | 2003
1 | NS1, NS2 | 2004
2 | TR, NS2 | 2012
3 | OR | 2008
2 | OR, NS1, TR | 2011
As I posted in my comment, this is easy using the group_concat() function in MySQL, but if you want to do it in MS Access, I think you have to deal with this using VBA.
I propose you this function:
public function concatenatePermits(licNo as integer, year as integer)
dim db as DAO.database, rec as DAO.recordset, strSQL as string
dim ans as string
set db = currentdb()
strSQL = "select permit from [your table] " & _
"where [lic#]=" & licNo & " and year=" & year & ";"
set rec = db.openrecordset(strSQL, dbOpenDynaset, dbReadOnly)
ans = ""
with rec
.moveFirst
do
if ans = "" then
ans = !permit
else
ans = ans & "," & !permit
end if
loop until .EOF
end with
rec.close
db.close
concatenatePermits = ans
end function
This function can be used in any query. Downside: If your table is really big, the execution of a query that uses this function can be really slow. I think the better approach would be to create an empty table and then fill it row by row using VBA.
Hope this helps you.
Adding rows using VBA
In your comment you ask how to add rows to a table with VBA. Assuming the table exists and you have the data you want to feed into this table, I suggest you something like this:
public sub addData()
dim db as dao.database, recOut as dao.recordset
' Declare all the variables you need for your code'
set db = currentdb()
' recOut will be the table where you want to store your data '
set recIn = db.openRecordset("tblYourOutTable",dbOpenDynaset,dbEditAdd)
' At some point in your code you will need to store data in your table: '
with recOut
.addNew
![A_Field] = value1
![Another_field] = value2
![Yet_another_field] = value3
.update
end with
' Close the recordset and the database objects '
rec.close
db.close
end sub