Access Query or VBA - Split data into line level - sql

How do I get the following
Project | Code 1 | Code 2 | Code 3
1 | a | b | c
2 | a | d
translate into getting code on line level by project?
Project | Code
1 | a
1 | b
1 | c
2 | a
2 | d
I am currently doing this manually but the problem is I have 30 codes. My current process is do a select query and then do a union for all 30 codes. Can someone help me out with this? I know a vba script will do the job but weren't sure how to do this. Thanks!

You can support any number of project codes by creating a new table, let's call it ProjectCode.
So you'd have one table for Projects, that doesn't have anything to do with codes.
If codes have extra data attached, you can create a Code table and add that information.
ProjectCode helps the database to see how they are related by using a JOIN. the structure would be
ProjectID | CodeID
What I've done for cases like this, is to use the main form for projects, and have a continuous form in datasheet view as a subform, linking to the ProjectCode-like table, and link the ID from Projects to the ProjectID of the subform. (Link Master Fields & Link Child Fields properties of the subform object), note you'll want to hide ProjectID on the subform.
To get a list of codes, you can just query ProjectCode with the project ID. if you need additional information from projects, use a join:
SELECT * FROM Projects INNER JOIN ProjectCode ON ProjectCode . ProjectID = Projects.ID
WHERE ID = 'some id'
Note that the information from Projects will be repeated for each code.

With this function you can read data from a table with N codes fields named (code1, code2, ..., codeN) and write them to a table with only two fields:
Function Verticalize(numberofcodes As Integer)
Dim sql As String
Dim rst As New ADBDb.Recordset
sql = "SELECT * FROM tname"
rst.Open sql, CurrentProject.AccessConnection
While Not rst.EOF
For i = 1 To numberofcodes
DoCmd.RunSQL "insert into tdest values (" & rst(0) & "," & rst("code") & Str(i) & ")"
Next i
rst.MoveNext
Wend
End Function

Related

How to filter an Access datasheet form based on fields of datasheet and subdatasheet

I have an Access DB, along with tables and forms.
In one table I have customers and in a second table as a subdatasheet on the first table I have tags.
I have created a form which shows the customer list along with the subdatasheet which displays all the tags that a customer has.
I would like to filter based on a field of the main datasheet, and on the subdatasheet for a tag.
E.g. where customer_name = "Peter" and tag ="Neighbor"
I saw this code but when trying to use it fails.
Filter on Subdatasheet
When trying Set mainDS = Me.Controls(dataSheetName).Form where do I find the datasheetName?
I tried entering Form Name, Table Name, I searched in form properties but didn't manage to find a solution.
Based on your question it sounds like you are new to Access and you should review table normalization.
Everything starts with a normalized table structure. Unfortunately you are starting with the relatively difficult Many to Many Relationship. Each Customer can have many tags which is a One to Many Relationship, but there wouldn't be much point to comparing customers unless they could have the same tags, which means each tag can have many customers as well. For example, create your tables with the corresponding primary and foreign keys and tell access about the relationhip by hitting the ribbon and selecting the relationships tool:
The raw data in the CustomersTags table is what you are interested in, but it is not userfriendly
----------------------------------------------------------------
| CustomerTagID | CustomerID | TagID |
----------------------------------------------------------------
| 1 | 1 | 1 |
----------------------------------------------------------------
| 2 | 3 | 1 |
----------------------------------------------------------------
| 3 | 2 | 3 |
----------------------------------------------------------------
| 4 | 2 | 4 |
----------------------------------------------------------------
| 5 | 2 | 5 |
----------------------------------------------------------------
| 6 | 3 | 1 |
----------------------------------------------------------------
| 7 | 2 | 1 |
----------------------------------------------------------------
| 8 | 3 | 5 |
----------------------------------------------------------------
| 9 | 2 | 2 |
This is why we use forms for entering and viewing the data. For speeds sake select CustomersTags and hit create table:
This is not user friendly so we don't show primary keys and replace (right-click and select change to) all the i'ds with user friendly combo-boxes. I also change the forms format to Data sheet.
I continue making the form prettier and add two unbound combo boxes to the header to filter the form with. You can do this with listboxes or checkboxes, or etc as well. For instance a multi-select list box so you can select multiple tags. In all cases you just set the forms filter
Option Compare Database
Option Explicit
'select the combobox afterupdate property to get these events
'you can use macro's if you want
'the exact code is very dependent on things like whether you have default values. I just show the most necessary case
Private Sub cmbCustomer_AfterUpdate()
If (IsNull(Me.cmbCustomer) Or IsNull(Me.cmbTag)) Then
'do nothing
Else
'filter the forms record source
Me.filter = "(CustomerID = " & Me.cmbCustomer & ") AND TagID = " & Me.cmbTag
Me.FilterOn = True
End If
End Sub
Private Sub cmbTag_AfterUpdate()
If (IsNull(Me.cmbCustomer) Or IsNull(Me.cmbTag)) Then
'do nothing
Else
'the filter is just the WHERE PART of an sql statement without the where
Me.filter = "(CustomerID = " & Me.cmbCustomer & ") AND TagID = " & Me.cmbTag
Me.FilterOn = True
End If
End Sub
Next, I switch the forms format to continuous forms and continue prettyifying because to my surprise the datasheet format was blocking setting the filter string.
Here is some guidance for making comboboxes, comboboxes are used for displaying userfriendly values instead of IDs. For instance instead of displaying the raw CustomerID we display the Customer_name but the value in the combobox is the CustomerID. The unbound combobox's value is the CustomerID corresponding to the user friendly Customer_Name the user selects. Whether the combobox is bound or unbound you set them up in the same way. Select a bunch of columns from some table or query then declare how many columns you are showing and their width. To hide a column set it's width to 0. The order of the columns determines the order of the numbers. I bound to the tables and both tables go ID then user friendly description so column-widths is 0,1
This example allows selecting impossible combinations of Customer and tag which will cause an error. One way to avoid this would be to set the rowsource of the alternate combobox when you choose a value on the first. For instance, when you pick a customer you restrict the tags which can be selected in the next combobox. This is cascading comboboxes:
Option Compare Database
Option Explicit
Private Sub cmbCustomer_AfterUpdate()
'it helps to use the query designer to get the sql right
Me.cmbTag.RowSource = "SELECT Tags.TagID, Tags.FriendlyDescription" & _
" FROM Tags INNER JOIN (Customers INNER JOIN CustomersTags ON Customers.CustomerID = CustomersTags.CustomerID)" & _
" ON Tags.TagID = CustomersTags.TagID WHERE (((CustomersTags.CustomerID)= " & Me.cmbCustomer & "))"
Me.cmbTag.Visible = True
End Sub
Private Sub cmbTag_AfterUpdate()
Me.filter = "(CustomerID = " & Me.cmbCustomer & ") AND TagID = " & Me.cmbTag
Me.FilterOn = True
Me.cmbCustomer.SetFocus
Me.cmbTag.Visible = False 'quick and dirty reset 'can't invis a focused control
Me.Refresh
End Sub

Lookup value in Access table without a unique identifier using VBA

I have a table like this:
+-----------+------+
| TableName | Flag |
+-----------+------+
| TableG | 0 |
| TableE | 0 |
| TableR | 1 |
| TableL | 1 |
| TableN | 0 |
What I'm trying to do is have a function that looks up every TableName where Flag=1 so that I can assign each table where Flag=1 to a button in a form (e.g. assign the first instance to Button1, etc.). The tricky part is that this table cannot be edited, so no unique ID/count can be assigned to easily grab items.
Here's what I've tried so far:
DLookup does not handle having multiple lookup matches (e.g. DLookup("TableName", "MyTable", "Flag=1") would not allow me to access all the values where Flag=1), so I don't think I can use that to store them in an array
I've tried using a RecordSet (see below), but I could not figure out how to have the SQL string automatically add a count so that each table name could be referenced (e.g. TableNameRS(1) to pull the name of the first table with Flag=1)
Public Function TableNameRS(TableID As Integer)
Dim RS As DAO.Recordset
Dim SQL As String
Dim Name As Variant
SQL = "select TableName, COUNT(TableName) from MyTable where Flag=1"
Set RS = CurrentDb.OpenRecordset(SQL)
Name = RS("TableName")
TableNameRS = Name
RS.Close
End Function
I am out of ideas - what are my options here?
If the number of records and buttons are fixed, say 10, and buttons have names like btn1, btn2, etc., consider a procedure like:
Public Sub TableNameRS(TableID As Integer)
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT TableName FROM MyTable WHERE Flag=1 ORDER BY TableName")
For x = 1 to 10
Me("btn" & x).Caption = RS("TableName")
RS.MoveNext
Next
RS.Close
End Function
Procedure can be called in form Open event. Then code in button Click event can pass its caption as an argument to whatever other procedure needs to take action with TableName value.
Calculating a sequence identifier in Access query is a common topic MS Access Restart Number Sequence.
Assuming TableName values are unique:
SELECT TableName, DCount("*", "table", "TableName<'" & [TableName] & "' AND Flag=1")+1 AS Seq,
FROM table
WHERE Flag=1
ORDER BY TableName;
Can use that query as RecordSource for form or report or execute DLookup() on it.
Be aware any change in filter will impact sequence calculation.

Compare and synchronize DataTables

I need to Compare Two dataTables.
dataTable A contains current set of Data on clients machine.
dataTable B contains future updates to dataTable A.
dataTable A structure
ID | firstname | lastName
1 | "test" | "last"
2 | "whatever" | "someone"
3 | "hi" | "hello
dataTable B Structure
ID | firstname | lastName
1 | "updated" | "yes"
2 ->deleted
3 | "hi" | hello" ->unchanged
4 | "new" |record " ->row added
When I go dataTableA.merge(datatableB)
I basically just get dataTableA with dataTableB added rows
so for example
ID | firstname | lastName
1 | "test" | "last"
2 | "whatever" | "someone"
3 | "hi" | "hello
1 | "updated" | "yes"
3 | "hi" | hello" ->unchanged
4 | "new" |record " ->row added
It doesn't match on the IDs and get updated or deleted. I just want to compare two tables, update table A that should look exactly like table B. I'm not quite sure how to accomplish this properly.
Basically there is a SQL table in the clients machine that needs to get completed updated and sync exact to a datatable B that is being passed in. In theory i just want to take table B and basically update table A. So after I need to update the SQL table. I tried something like this.
Dim adapter = New SqlDataAdapter("select * from test_table2", connection)
Using (New SqlCommandBuilder(adapter))
adapter.Fill(dTable)
connection.Open()
adapter.Update(dTable)
End Using
Doesn't seem to work.
If the datatables are supposed to end up identical, instead of using
dataTableA.merge(datatableB)
Try
dataTableA=datatableB.Copy
Got this info from here
https://msdn.microsoft.com/en-us/library/system.data.datatable.copy(v=vs.110).aspx
Your both datatables are being created separately, independent of each other. In this context you can use following DataTable and LINQ operations such as
Intersect to find matching rows in both DataTables,
Except to find unique rows only in one table
ImportRow to copy specific rows of one DataTable to another.
Copy to copy an entire datatable into another
It could be like this
Dim dtA As DataTable 'Your original Table A
Dim dtB As DataTable 'Your modified Table B
Dim dtC As DataTable 'Unchanged Rows
Dim dtD As DataTable 'Final synchronized Table
Dim dtE As DataTable 'Deleted Rows
'Populate your Table A and Table B into dtA and dtB
'get unchanged rows into dtC (all rows - changed - deleted)
dtC = dtA.AsEnumerable.Intersect(dtB.AsEnumerable, DataRowComparer.Default).CopyToDataTable
'Copy all unchanged rows to final table
dtD = dtC.Copy
'Copy the structure to blank datatable
dtE = dtC.Clone
'process modified rows (changed + deleted) i.e. (all rows - unchanged rows)
For Each cdRow As DataRow In dtA.AsEnumerable.Except(dtC.AsEnumerable, DataRowComparer.Default).CopyToDataTable.Rows
'check if this row exists in modified rows
If dtB.Select("cid = " & cdRow.Item("cid").ToString).Count > 0 Then
'This is a modified row copy it to the final table or process it to database
dtD.ImportRow(dtB.Select("cid = " & cdRow.Item("cid").ToString).CopyToDataTable.Rows(0))
Else
'This is a deleted row copy it to the deleted records table or process it directly to the database
dtE.ImportRow(cdRow)
End If
Next
'Now dtE contains your deleted rows
'Finally dtD is your synchronized datatable
Assumptions and other details:
All DataTables should have same table structure
This code assumes that ID column is unique to identify new and old rows.
You will still have to process deleted rows (dtE) from original database.
DataTable dtD, dtE can be avoided, but just for the purpose of simplicity and better understanding of the concept these are included.
Instead of later processing all records in dtD and dtE in a separate loop you can directly update them to the database as mentioned in code above.
There may be memory, resources, processing/timing related issues in case you are processing large DataTables

Create Access report that lists information from one table, which is matched on ID from another table

I'm updating this as the previous answer turned out to not be what I really needed.
I have two tables in my Access database. One is called Billings, one is called Bookings. Each table has a column called Booking Number, which is how they are related. The Billings table also has a field called Container. What I want to do is:
Create a one page report for each Booking Number (which can be expanded to multiple pages depending on the number of containers)
In the body of the report I would like to list each Container (along with other information about each container) on separate rows
Here are my tables:
Bookings
----------------
ID | Booking Number
1 | '1234'
2 | '1235'
Billings
----------------
ID | Booking Number | Container
1 | '1234' | '12'
2 | '1234' | '16'
3 | '1235' | '18'
Based on these two tables, there should be a report for Booking #1234, and Booking #1235. Booking #1234's report should list Container's 12 and 16, while Booking #1235's report would only list 18. I'm a PHP/MySQL developer, so I understand SQL queries. However, the way I would write the query for MySQL obviously does not work for Access. At the moment I am using a Module similar to the answer below, but it does not do what I need it to do. This is my current query:
SELECT
b.*,
ListContainers(b.[Booking Number]) AS Containers
FROM
Bookings AS b
WHERE
((b.[Booking Number]) Is Not Null);
This will create a comma separated list of the containers associated with each Booking Number, but I want to create a separate row for each Container, which will also include other information from the Billings table.
Has anyone had any experience with a similar situation, or know any steps I could take to accomplish what I'm looking to do?
For your new requirements you'll want to use a "subreport" to list the containers (and related information). For details, see my recent answer to a similar question here.
[This is my answer to the original question, before the "spec" changed.]
Create a new Module in Access and paste in the following code
Public Function ListContainers(Booking_Number As String) As String
Dim cdb As DAO.Database, rst As DAO.Recordset, rtn As String
Const Separator = ", "
Set cdb = CurrentDb
rtn = ""
Set rst = cdb.OpenRecordset("SELECT Container FROM Billings WHERE [Booking Number]=""" & Booking_Number & """ ORDER BY Container", dbOpenSnapshot)
Do While Not rst.EOF
rtn = rtn & rst!Container & Separator
rst.MoveNext
Loop
rst.Close
Set rst = Nothing
Set cdb = Nothing
If Len(rtn) > 0 Then
rtn = Left(rtn, Len(rtn) - Len(Separator)) '' trim trailing separator
End If
ListContainers = rtn
End Function
You can then use that Function in a query, like this
SELECT [Booking Number], ListContainers([Booking Number]) AS Containers
FROM Billings
That will return
Booking Number Containers
-------------- ----------
1234 12, 16
1235 18

Returning SQL rows with data concated per row in access

In access I have a table that is like
ID| ROOMS | NOTES|
78| 234| |
3 | 231| key |
78| 195| |
3 | 164| |
I want to a sql query that will take the ID and combine them into one row each so it is like
78 -> 234,195
3->231, 164 -> key
i just want to combine the rows only in the query no into a new table
Unfortunately you'll have to build a function to do this, but thankfully access supports the use of VBA functions inside of your SQL query.
For example function to concatenate the rim's together based on a given ID would be as follows:
Public Function MyRooms(lngID As Variant) As Variant
Dim rstRooms As DAO.Recordset
If IsNull(lngID) Then Exit Function
Set rstRooms = "select Rooms from tblBookings where id = " & lngID
Do While rstRooms.EOF
If MyRooms <> "" Then MyRooms = MyRooms & "->"
MyRooms = MyRooms & rstRooms!Rooms
rstRooms.MoveNext
Loop
rstRooms.Close
End Function
Now your query can look like this:
Select id, MyRooms([id]) as Rooms, Notes from some table.
You can also make an identical function much the same for the notes column, and again simply place that in the above query.