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
Related
I have an Access query that contains order quantities and reorder frequencies, e.g:
+-------+---------------------------+---------------------------+
|Product| Order Qty (pallets) | Order Interval (wks) |
+-------+---------------------------+---------------------------+
| 1234 | 2.5 | 7 |
+-------+---------------------------+---------------------------+
| 1235 | 3.4 | 10 |
+-------+---------------------------+---------------------------+
I want to generate a time phased table of orders, like this:
+-------+--------+--------+--------+--------+--------+--------+--------+
|Product| Wk1 | Wk2 | Wk3 | Wk4 | Wk5 | Wk6 | Wk7 |
+-------+--------+--------+--------+--------+--------+--------+--------+
| 1234 | 2.5 | | | | | | 2.5 |
+-------+--------+--------+--------+--------+--------+--------+--------+
I'm familiar with MySQL but it seems that I will need to create a VBA subroutine to do this in Access. I'd very much appreciate if someone could point me in the right direction.
You are already in the 'right' direction - need VBA and a 'temp' table. Build temp table with enough WkX fields to accommodate the highest possible interval (max 254). Most likely code will involve opening recordset object, looping records to read values and save records with appropriate data. Maybe this will get you started:
Dim db As DAO.Database, rs As DAO.Recordset
Set db = CurrentDb
Set rs = CurrentDb.OpenRecordset("SELECT * FROM table")
CurrentDb.Execute "DELETE FROM PhaseTable"
Do While Not rs.EOF
db.Execute "INSERT INTO PhaseTable(Product, Wk1, Wk" & rs!Interval) " & _
"VALUES('" & rs!Product & "," & rs!Qty & "," & rs!Qty & ")"
rs.MoveNext
Loop
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 |
+---------+---------+---------+--------+
I am importing a CSV from a network drive, which has lots of columns containing text SG_. I am currently renaming these columns via code by running a 1st query to create a recordset with SELECT TOP 1* FROM FILENAME.EXT rows. Then i am looping the recordset.Fields and removing the prefixed text e.g. nnn_ using MID Function and using the remaining text to use them as Aliases for these SG columns. So my 1st VBA created SQL string looks like this:
StrSQL = "SELECT [Food],[Bev],[Meds],[Average 1],[Midpoint],[Average 2],[SG_ABC],[SG_DEF],[SG_GHI]
From this, i want to pick up the columns containing SG_.
Sample VBA Code:
Set oCon = CreateObject("ADODB.Connection")
Set oRs = CreateObject("ADODB.Recordset")
strCon = "Driver=Microsoft Access Text Driver (*.txt, *.csv);Dbq=" & sFullDirectory & ";Extensions=asc,csv,tab,txt;HDR=Yes;"
strSQL = "SELECT TOP 1 * FROM " & FILE1
oCon.Open strCon
Set oRs = oCon.Execute(strSQL)
i = 1
strSQL = "SELECT "
For Each Fld In oRs.Fields
Select Case True
Case Is = InStr(1, Fld.Name,"SG_") > 0
TempSG=Trim(Mid(Fld.Name,InStr(1,Fld.Name,"SG_"),Len(Fld.Name)))
strSQL = strSQL & " CDbl([" & Fld.Name & "]) AS [" & TempSG & "], "
....more Cases...
End Select
Next Fld
oRs.Close
....more code.
Then i am joining this strSQL query to other tables for selecting other columns and loading the result to an ado recordset. I am currently using Microsoft Access Text Driver in Excel using VBA.
strSQL1 = "SELECT G.[lbl], A.[tval], Q.*"
strSQL1 = strSQL1 & " FROM "
strSQL1 = strSQL1 & " (SELECT G.[pos], A.[pos], G.[lbl], A.[tval] FROM " & FILE2 & " G," & FILE3 & " A WHERE G.[ID] = A.[ID]) T, (" & strSQL & ") Q "
strSQL1 = strSQL1 & " WHERE (CLng(T.[G].[pos]) = CLng(Q.[gval])) AND (CLng(T.[A].[pos]) = CLng(Q.[pos]))"
strSQL1 = strSQL1 & " ORDER BY CLng(Q.[gval]), CDbl(Q.[Aggregate 1]) DESC, G.[lbl];"
Set oRs = oCon.Execute(strSQL1)
In the above code, Q.* represents the below table from which i want to only select the SG_ columns. Hope this makes things clear. e.g.
SELECT G.[lbl], A.[tval], Q.* LIKE 'SG_'
CSV Table:
Food | Bev | Meds | Average | Midpoint | Average | 434_SG_ABC | 236_SG_DEF | 121_SG_GHI |
--------------------------------------------------------------------------------------------------
cheese | Rum | cold | 1.22 | 4.98 | 0.24 | 23.43 | 54.67 | 89.33 |
Butter | Wine | heat | 3.56 | 2.40 | 0.98 | 12.12 | 90.23 | 33.43 |
Olive | Beer | rain | 4.33 | 7.11 | 1.45 | 11.55 | 10.31 | 87.22 |
Rice | Gin | hail | 2.02 | 7.86 | 3.36 | 25.82 | 29.44 | 65.70 |
Is there a way to select these Columns?
There is no straight forward way to achieve something like that.
You will either have to modify the VBA code to generate the string with the required columns.
Or if changing the VBA is not possible, insert the data in a staging table and then generate a Dynamic query for selecting the required data
DECLARE #query NVARCHAR(MAX) = ''
SELECT #query = #query + ',' + name
FROM sys.columns
WHERE object_name(object_id) = '<Your Staging table name>'
AND name like 'sg%'
SET #query = 'SELECT ' + RIGHT(#query, LEN(#query)-1) + '
INTO <whichever permanent or temp table you need>
FROM <Your Staging table name>'
EXEC(#query)
This will give you the Select statement required for your details.
I have an MDB table with 2 fields:
+-------------+------------------+
| salesperson | Transaction_Date |
+-------------+------------------+
| John | 12/11/2018 |
| John | 13/11/2018 |
| John | 18/11/2018 |
| Steeve | 23/12/2018 |
| Steeve | 29/12/2018 |
+-------------+------------------+
In VBA (in Excel) I want through ADO to create the following format:
+-------------+----------------------------------+
| salesperson | Transaction_Date_Concatenated |
+-------------+----------------------------------+
| John | 12/11/2018-13/11/2018-18/11/2018 |
| Steeve | 23/12/2018-29/12/2018 |
+-------------+----------------------------------+
The number of Transaction_Dates for each salesperson may vary from 1 to 30.
The following SQL creates a record for each salesperson but it doesn't, of course, produces the concatenated field although it looks as in the direction of what I want
SQL = " SELECT SalesPerson, max(Date) as value1 FROM 0TargetTemplate GROUP BY SalesPerson"
To my knowledge, this cannot be achieved using SQL alone. You'll need to use a VBA function to iterate over the records and construct the delimited string.
Allen Browne has created an existing example which may be found here.
For your particular task, you might call the function in the following manner:
select
[0TargetTemplate].SalesPerson,
ConcatRelated
(
"[0TargetTemplate].Transaction_Date",
"[0TargetTemplate]",
"[0TargetTemplate].SalesPerson = '" & [0TargetTemplate].SalesPerson & "'",
"[0TargetTemplate].Transaction_Date",
"-"
) as Transaction_Date_Concatenated
from
[0TargetTemplate];
Using the suggested code and function from the link I wrote a procedure in VBA Access but get an error "Too few parameters" in the Set rs= statement. I've tried in VBA EXCEL with proper connections but get an error about "ConcatRelated" function not recognized, although properly referenced in a VBA module
Sub test()
SQL = "SELECT [tblOrders].CompanyName, ConcatRelated ('[tblOrders].Transaction_Date',[tblOrders], [tblOrders].SalesPerson =' [tblOrders].SalesPerson', [tblOrders].Transaction_Date) AS Transaction_Date_Concatenated FROM tblOrders;"
Set rs = CurrentDb.OpenRecordset(SQL)
On Error GoTo resultsetError
dbValue = rs!Variable
MsgBox dbValue, vbOKOnly, "RS VALUE"
resultsetError:
MsgBox "Error Retrieving value from database", vbOKOnly, "Database Error"
End Sub
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