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
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 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 have a table that was created by a SQL query where each of the results were combined into one cell. I would like to get each value associated correctly on separate rows.
The data is currently set up like this:
+-------+---------+-------------------------------------------+---------------+
| ID | Desc | Users | Functions |
+-------+---------+-------------------------------------------+---------------+
| a | a desc | First Last [uname3], First Last [uname45] | abc, def, xyz |
+-------+---------+-------------------------------------------+---------------+
| b | b desc | First Last [uname8], First Last [uname72] | lmn, def, xyz |
+-------+---------+-------------------------------------------+---------------+
I would like for it to be presented as:
+-------+---------+----------------------+---------------+
| ID | Desc | Users | Functions |
+-------+---------+----------------------+---------------+
| a | a desc | First Last[uname3] | abc, def, xyz |
+-------+---------+----------------------+---------------+
| a | a desc | First Last [uname45] | abc, def, xyz |
+-------+---------+----------------------+---------------+
| b | b desc | First Last[uname8] | lmn, def, xyz |
+-------+---------+----------------------+---------------+
| b | b desc | First Last [uname72] | lmn, def, xyz |
+-------+---------+----------------------+---------------+
I would just do this manually but there are ~75 rows with as many as 125 users listed in the same cell.
Thanks for any help!
Let's say your data are stored in A to D columns in the sheet named TheNameOfSheet:
Dim i As Integer, j As Integer
Dim users() As String
Dim wsh As Worksheet
Set wsh = ThisWorkbook.Worksheets("TheNameOfSheet")
i = 1 'starting row
Do
'get the list of users and split it by comma
users = Split(wsh.Range("C" & i), ", ")
'go through the list of users
For j = LBound(users()) To UBound(users())
'insert new row
If j>0 Then wsh.Range("A" & i).EntireRow.Insert(Shift:=xlShiftDown)
'copy original data
wsh.Range("A" & i) = wsh.Range("A" & i)
wsh.Range("B" & i) = wsh.Range("B" & i)
'insert single name
wsh.Range("C" & i) = users(j)
wsh.Range("D" & i) = wsh.Range("D" & i)
'increase counter
i = i + 1
Next j
i = i +1
Loop
For further information, please see:
MS Excel: How to use the SPLIT Function (VBA)
Range.Insert Method (Excel)
Note: I didn't tested this code! I've written it directly from my head ;)
if you are not interested in writing an excel macro.
then do this.
copy paste the sorted results twice in our excel.
Select the entire column of users, choose text to columns with delimiter as Coma,
Sort all the columns (inc/desc, your wish) based on users column
then insert one more row beside between functions and users.
paste the below formula
=IF(D3=D2,E3,D3) on the first cell of newly added column and drag till bottom.
the output will look like the below image.
remvoe the columns D and E. there you are with the end result as you wanted it
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.