Split multi-value field into different rows using SQL in Microsoft Access - sql

I’ve got a simple table in Microsoft Access that looks like this:
Primary Key
Applications List
123
<Value>|<Value>,<Value>|<Value>
456
<Value>|<Value>,<Value>|<Value>
I need to break out the list of applications into separate rows using the “,” as a delimiter so the end result is a table that looks like this:
Primary Key
Applications List
123
<Value>|<Value>
123
<Value>|<Value>
456
<Value>|<Value>
456
<Value>|<Value>
I’ve tried using the Split function but can’t figure out how to split on the “,” and output the results to a different row like the second table above. I would greatly appreciate your help figuring this one out. Thanks so much!

If you can put a limit on the length of Application List you can build a number table
CREATE TABLE NumTable (ID integer)
up-front with as many rows as there are characters in the longest Application List. Assuming that the longest [Application List] is 1000 characters long, ID=1, ID=2, ..., ID=1000), and then use something like this:
select T.[Primary Key],
mid(T.AL,NT.ID+1
, iif(instr(NT.ID+1,t.AL,',')>0
, instr(NT.ID+1,t.AL,',')-1-nt.ID,1000)) as
from (select [Primary Key], ',' & [Application List] as AL from tblYourTable) as T
inner join
NumTable as NT
on mid(t.AL,NT.ID,1)=','
You can build the number table in EXCEL and paste it; or write a little VBA routine, or even create it dynamically (*).
I can't imagine it performing very well if the volume is high.
Let us know how you proceed!
(*)Generate numbers 1 to 1000000 in MS Access using SQL

If you setup a Table1 and a Table2 with the same field names, you can run this function to do it for you - it basically loops through Table1 records, splits the Applications List into multiple fields and then inserts new records for each field.
Public Sub SplitDataIntoNewTable()
Const DATA_SEPARATOR As String = ","
Dim qdf As DAO.QueryDef
Dim rsOld As DAO.Recordset
Dim rsNew As DAO.Recordset
Dim i As Integer
Dim lngNumAdded As Long
Dim lngKey As Long
Dim strList As String
Dim strNewList As String
Dim varLists As Variant
Set rsOld = CurrentDb.OpenRecordset("Table1", dbOpenDynaset, dbReadOnly)
Set rsNew = CurrentDb.OpenRecordset("Table2", dbOpenDynaset)
With rsOld
While Not .EOF
lngKey = ![Primary Key]
strList = ![Applications List]
varLists = Split(strList, DATA_SEPARATOR)
With rsNew
For i = LBound(varLists) To UBound(varLists)
' Add new record for every list split out of old data
.AddNew
' Note that this CANNOT actually be defined as a PRIMARY KEY - it will have duplicates
![Primary Key] = lngKey
![Applications List] = varLists(i)
lngNumAdded = lngNumAdded + 1
.Update
Next i
End With
.MoveNext
Wend
rsNew.Close
.Close
End With
MsgBox "Added " & lngNumAdded & " New Records"
Set rsOld = Nothing
Set rsNew = Nothing
End Sub
For example I had Table1 look like this:
And resulting Table2 ended up like this

Related

Setting listboxes in MS access form - row by row

I have a requirement in MS Access where a table is displayed as several rows in the form. I have created one form detail record(several fields) that will repeat for each row in the Table1. Lets say I have five columns in the Table1. Based on Column3 value, I would like to have a list of value for Column4 and Column5 during form_load. I have also created a separate Table2 to establish relationship between Column3, Column4 and Column5. I have set up Event procedure to populate the values using sub function. The challenge I have is, not being able to set up different listbox 'value list' for different rows. Any tips on populationg form fields IMRecomExIns and AmendReasonExIns by processing each row in Table1 would be a great help.
Private Sub IMRecomExIns_Click()
Dim CoverType As String
Dim ListRecomm As String
Dim ListAmend As String
Dim db As DAO.Database
Dim tablevar As Recordset
Set db = CurrentDb
Set tablevar = db.OpenRecordset("Table2")
CoverType = "*" & Me.CoverTypeExIns.Value & "*"
ListRecomm = ""
ListAmend = ""
If tablevar.EOF = False And tablevar.BOF = False Then
tablevar.MoveFirst
Do Until tablevar.EOF
If tablevar!CoverType Like CoverType Then
ListRecomm = tablevar!Recommendation
ListAmend = tablevar!AmendReason
tablevar.MoveLast
End If
tablevar.MoveNext
Loop
End If
Me.IMRecomExIns.RowSourceType = "Value list"
Me.IMRecomExIns.RowSource = ListRecomm
Me.AmendReasonExIns.RowSourceType = "Value list"
Me.AmendReasonExIns.RowSource = ListAmend
End Sub
1) I have stored all the value list in a single cell. For example tablevar!Recommendation will have all the values for Me.IMRecomExIns.RowSource, which means the output is will look like "Rec1";"Rec2";"Rec3";etc... Same applies for tablevar!AmendReason "AR1";"AR2';"AR3";ETC... Understand this is not the normalized form of storing data. I want to POC to work before building a full solution with normalized tables.
2) Answered earlier.. the rowsource will be set with all the possible values at the first match, so no point in going all the way to the end of the table
3) CoverTypeExIns is a string, Table 2 have many different possibilities such as "Mortgage Income" and "Family Income", however the Recommendation and Amendreason are same for all "Income" category that comes from Table1. Thats why the wildcard search.
My problem is not with setting the RowSource for a single row, but setting up RowSource for multiple occurrence in of the same IMRecommmendation and AmendReason in MS Access screen.
Here is the design view of the form. This form is linked to MS Access table. For multiple rows the Detail record will repeat itself as many times.
An example of two rows displayed in the screen.
I'm not sure exactly what you are asking/trying to do here.
I can see at several problems with the code that you have:
You are using tablevar.MoveLast in the loop, whic would automatically take you to the end of the recordset.
Also, you are not concatenating (joining together) ListRecomm/ListAmend, you are just setting them equal to a value, so each loop that matches will overwrite any previous value.
Finally, I am not sure what you are doing with trying to find CoverTypeExIns - you are using LIKE, which would indicate that it is text, but not wrapping the value in single quotes. If it is a numeric value, then you should be using "=".
However, rather than opening a recordset, looping it and checking for a match to build up a list of values, it is better to just set the RowSource of listboxes equal to a SQL string (effectively a query).
Something like (assuming CoverType is numeric):
Private Sub IMRecomExIns_Click()
Dim strSQL As String
strSQL = "SELECT Recommendation FROM Table2 WHERE CoverType=" & Me!CoverTypeExIns
Me!AmendReasonExIns.RowSource = strSQL
End Sub
I prefer to declare a string to hold the SQL statement rather than setting the .RowSource directly, as it makes troubleshooting easier.
Regards,
Based on the new information given, below is some VBA code that opens up a recordset based on the information entered in "Cover", and then sets the .RowSource property of the two combo boxes to be the value lists. In my example, don't bother setting the .RowSourceType, as this should be done at design time:
Private Sub IMRecomExIns_Click()
Dim db As DAO.Database
Dim rsData As DAO.Recordset
Dim strSQL As String
Set db = DBEngine(0)(0)
strSQL = "SELECT Recommendation, AmendReason FROM Table2 WHERE CoverType LIKE '*" & Me!cboCover & "*';"
Set rsData = db.OpenRecordset(strSQL)
If Not (rsData.BOF And rsData.EOF) Then
Me!IMRecomExIns.RowSource = rsData!Recommendation
Me!AmendReasonExIns.RowSource = rsData!AmendReason
End If
rsData.Close
Set rsData = Nothing
Set db = Nothing
End Sub
As I have previously stated, you should really normalize the design of your database now, rather than getting in so far that it requires a major re-write.
Regards,

How to return unknown column names based on a condition in an Access 2016 query

I have a table with historical values, and some have columns where all the values are null. I'm trying to delete all the columns where all the values are null. In this dataset, the null values are represented by ".." and there are 733 records.
My problem is not knowing how to refer to an unknown column name. The query below is my latest attempt to identify if I can even select the correct information before I start dropping columns.
SELECT *, COUNT(*)
FROM table
WHERE * = ".."
Anyone have any ideas?
As Olivier wrote, SQL alone won't do it. The easiest way is to loop the fields collection and use DCount on each field, with a criterium like [field name] <> '..' to find all columns that only have '..'.
Sub AnalyzeTable()
Dim db As DAO.Database
Dim td As DAO.TableDef
Dim fld As DAO.Field
Dim sCrit As String
Dim n As Long
Set db = CurrentDb
Set td = db.TableDefs("tHistorical")
For Each fld In td.Fields
' only check text columns
If fld.Type = dbText Or fld.Type = dbMemo Then
' For each field, count the records *not* containing '..'
sCrit = "[" & fld.Name & "] <> '..'"
n = DCount("*", "tHistorical", sCrit)
' if that number is 0, the column only contains '..' and can go
If n = 0 Then
' this will go into the Immediate window, use Ctrl+G to open it
Debug.Print fld.Name
End If
End If
Next fld
End Sub
You cannot accomplish this task with SQL. You could open a recordset and test all the columns for each record by looping through the Fields collection.
But if you have only 733 records, the easiest way is to open the table in access, select all the columns and apply Sort Ascending. You will then see the empty rows being displayed first. Delete them manually.
Yea... you can't just have it return records where a single column is nothing but '..' values. You'll need to query the columns.
Something like
Select '
count(*),
sum(iif(f1='..',1,0)),
sum(iif(f2='..',1,0)),
...
FROM table
And see if any of the columns match the total, then you have a column to work with.

loop through records and add record or create if none exists to new table depending on multiple criteria

I'm new to access/vba and trying to set up a project database. I have a table ("Updates") that is generated when changes are made to certain fields on a form (used for project updates by the end user). It has the primary key UpdateID, foreign key ProjectID as well as UTimeStamp, OldValue, NewValue, History. I use the history key to identify which type of update was made (for example for Status, History=1). I want to then count the number of projects for each status at the end of each month, keeping historical data to allow users to track the changes from month to month (or even compare data from months apart). I'm trying to write a code (in VBA for access) that would take into account that there are sometimes multiple status updates in each month and I don't want them to get counted twice, also some months no updates are made but I still want them included in the count (using the last updated status before that month as the status).
I was thinking of using a combination of looping through the records and checking to see if a value exists for that specific ProjectID and month and inserting the last value (most recent) into a new table "StatusTracking" and if no record exists then using the INSERT INTO function to add a new record. "StatusTracking" will have the fields ID, ValueMonth, ValueYear, (since ideally I want to track over the course of more than a year) Status, ProjectID. However, I am very new to this and am having trouble getting started as I'm not sure the best way to loop through both the months and ProjectID.
Public Function getStatus()
Dim varMonth As Integer
Dim ReportStatus As String
Dim RS As DAO.Recordset
Dim db As DAO.Database
Dim sqlStr As String
sqlStr = "SELECT Updates.ProjectID, Format(Month([UTimeStamp])) AS UpdateMonth, ProjectList.Status, Updates.NewValue, Updates.UTimeStamp" & _
"FROM Updates RIGHT JOIN ProjectList ON Updates.ProjectID = ProjectList.ProjectID" & _
"WHERE (((Updates.History) = 1))" & _
"GROUP BY Updates.ProjectID, Format(Month([UTimeStamp])), ProjectList.Status, Updates.NewValue, Updates.UTimeStamp"
Set db = CurrentDb
Set RS = db.OpenRecordset(sqlStr)
With RS
.MoveLast
.MoveFirst
While (Not .EOF)
'Cycle through each month
For varMonth = 1 To 12 Step 1
ReportStatus = DLast("NewValue", RS, "UpdateMonth = " & varMonth)
RS.Fields ("Status") <> RS.Fields("NewValue")
End Function
Any help is appreciated!
DCount is useful for counting unique occurrences in a specified field. The link I provided should put you on the right track there. Note that that only returns the count itself, and not the records. If you need to populate a recordset, you can use SELECT DISTINCT in your query to return only the records that have, well, distinct values in your criteria.
I recently worked on a project that involved building a history table for tracking purposes. Since it was based around forms, I opted for using .addnew and .update rather than INSERT INTO. Of course, use what's best for the situation at hand; I used .addnew and .update for the main reason that I had a lot of controls in my form and it was simpler in my mind to do it that way. There's lots of ways to do it, this worked best for me. I've also provided a snippet of the code I wrote for that project as another example.
Hope this helps!
'Example
'Assuming recordset and database variables are already declared
'rec = recordset, db = currentdb
set rec = db.openrecordset(<source table, name of existing query, or SQL query>)
if <condition is met> then
'populate table with values in form controls
rec.addnew
rec("Destination Table Field") = Me.Controls("Name of Form Control").Value
.
.
.
rec.update
set rec = nothing
set db = nothing
'clearing rec and db after done using
end if
Code from my project:
Set db = CurrentDb
Set rec = db.OpenRecordset("Select * from tbl_maintenanceOrders")
Set recHist = db.OpenRecordset("Select * from tbl_umoHistory")
msgConfirm = MsgBox("Correct values confirmed?", vbYesNo, "Continue")
'enter record in maintenance order table
If msgConfirm = vbYes Then
rec.AddNew
recHist.AddNew
rec("openTimestamp") = Now()
rec("openedBy") = Me.Controls("cbo_originator").Value
rec("assetID") = Me.Controls("cbo_asset").Value
rec("assetDesc") = Me.Controls("txt_assetDesc").Value
rec("priority") = Me.Controls("cbo_priority").Value
rec("umoProblemDesc") = Me.Controls("txt_issueDesc").Value
rec("umoSpecifics") = Me.Controls("txt_issueDetails").Value
rec("umoState") = "open"
rec("umoStatus") = "new"
rec.Update
'add UMO history entry
recHist("umoID") = rec("orderID")
recHist("activity") = "opened"
recHist("umoState") = "open"
recHist("umoStatus") = "new"
recHist("activityDesc") = "UMO Requested"
recHist("initiatorID") = Me.Controls("cbo_originator").Value
recHist("timeStamp") = Now()
recHist("updater") = Me.Controls("cbo_originator").Value
recHist.Update
End If
'cleanup
Set rec = Nothing
Set recHist = Nothing
Set db = Nothing

Connection to two (or more) sharepoint lists with ADODB

I can connect to a SharePoint list with ADODB this way :
Dim objCon As New ADODB.Connection
objCon.Open "Provider=Microsoft.ACE.OLEDB.12.0;WSS;IMEX=2;RetrieveIds=Yes;DATABASE=mysite/documents;LIST={xxxx-guid-xxxx};"
Dim rst As Recordset
Set rst = objCon.Execute("Select * from list1)
and it's working :).
Now, I would like to connect two list at the same time to do an inner join :
Set rst = objCon.Execute("Select * from list1 inner join list2)
but I don't find the syntax to put the second list GUID on the connection string. How can I do ?
I know this is an older question but I was trying to attempt the same effort a while back and when I was looking for examples I generally saw the same answer of "No its not possible".
As Thomas G points out, it is possible as a 'disconnected recordset' (I typically refer to those as sub-queries) and then to join the two Lists together.
My goal is / was to avoid the need to first import the data into Excel just to run the queries with the Lists joined since I'm actually using it to bulk import several files per week into several Lists in SharePoint. I just don't want to deal with the data maintenance / clean up in Excel if I can avoid it in the first place. (The several files are all using data from two different lists during the import)
In line with the original question, I've simplified the code to just retrieve the results of a Select query against two SharePoint Lists. The code is written for Excel VBA but can be used in any VBA instance if the Excel parts are removed.
Sub SQL_Two_SP_Lists()
Dim sp_sdbPath As String, sp_sConnect As String
Dim SP_List_1 As String, SP_List_2 As String
Dim c As Long
Dim cnSP As New ADODB.Connection
Dim rsSP As New ADODB.Recordset
sp_sdbPath = "https://your_SharePoint_URL_Here/"
sp_sConnect = "Provider=Microsoft.ACE.OLEDB.12.0;WSS;IMEX=2;RetrieveIds=Yes;DATABASE=" & sp_sdbPath & ";"
SP_List_1 = "LIST={List 1 GUID Here}" 'i.e. xxxx-guid-xxxx
SP_List_2 = "LIST={List 2 GUID Here}" 'Go to List settings > Information Management Policy Settings > GUID is in the URL
'Establish a connection to the first List
cnSP = sp_sConnect + SP_List_1
cnSP.Open
'Write the SQL & Establish a connection to the second List as a sub-query using IN
sSQL = "SELECT A.*, B.* " + _
"FROM List A " + _
"INNER JOIN (Select * From LIST IN 'DATABASE=" & sp_sdbPath & ";" & SP_List_2 & "' 'WSS;RetrieveIds=Yes;') B On A.Cust_ID = B.Cust_ID;"
rsSP.Open sSQL, cnSP, adOpenStatic, adLockReadOnly 'Change cursor & lock type if inserting, updating or deleting
'The rest is to drop the results into an empty worksheet named 'Test'
For c = 0 To rsSP.Fields.Count - 1
ThisWorkbook.Sheets("Test").Cells(1, c + 1) = rsSP.Fields(c).Name
Next
ThisWorkbook.Sheets("Test").Cells(2, 1).CopyFromRecordset rsSP
rsSP.Close
cnSP.Close
End Sub
From Microsoft http://msdn.microsoft.com/en-us/library/ee633650.aspx
Selecting fields from multiple lists is not supported. You can create a dataset for each list and select fields from each dataset.
I don't know your environement but to me the easiest way to accomplish this would be to create 2 Linked tables pointing on your SharePoint lists and then you can manipulate their data the way you want.

Query creating an identifier for each packet

Sorry the title is not very descriptive but it is a tricky problem to word.
I have some data, about 200 or more rows of it, and each row has a PacketID, so several rows belong in the same packet. What I need to do, is convert all the PacketIDs from (Example - BDFD-2) to just a number (Example - 1) so all the entries with a packet identifier x need to have a packet identifier of say 3. Is there an SQL query that can do this? Or do I just have to go through manually.
You asked about a query. I wrote a quick VBA procedure instead just because it was so easy. But I'm unsure whether it is appropriate for your situation.
I created tblPackets with a numeric column for new_PacketID. I hoped that will make it clearer to see what's going on. If you truly need to replace PacketID with the new number, you can alter the procedure to store CStr(lngPacketID) to that text field. So this is the sample data I started with:
PacketID new_PacketID packet_data
BDFD-2 a
R2D2-22 aa
BDFD-2 b
R2D2-22 bb
EMC2-0 aaa
EMC2-0 bbb
And this is the table after running the procedure.
PacketID new_PacketID packet_data
BDFD-2 1 a
R2D2-22 3 aa
BDFD-2 1 b
R2D2-22 3 bb
EMC2-0 2 aaa
EMC2-0 2 bbb
And the code ...
Public Sub RenumberPacketIDs()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim lngPacketID As Long
Dim strLastPacketID As String
Dim strSql As String
strSql = "SELECT PacketID, new_PacketID" & vbCrLf & _
"FROM tblPackets" & vbCrLf & _
"ORDER BY PacketID;"
Set db = CurrentDb
Set rs = db.OpenRecordset(strSql)
With rs
Do While Not .EOF
If !PacketID <> strLastPacketID Then
lngPacketID = lngPacketID + 1
strLastPacketID = !PacketID
End If
.Edit
!new_PacketID = lngPacketID
.Update
.MoveNext
Loop
.Close
End With
Set rs = Nothing
Set db = Nothing
End Sub
I think an approach like that could be fine for a one-time conversion. However if this is an operation you need to perform repeatedly, it could be more complicated ... especially if you need each PacketID replaced with the same number from one run to the next ... eg. BDFD-2 was replaced by 1 the first time, so must be replaced by 1 every time you run the procedure.
If you only have a few packet IDs, you can just use update:
UPDATE table_name
SET PacketID =
(
CASE PacketID
WHEN 'BDFD-2' THEN 3
WHEN 'ABCD-1' THEN 5
ELSE 2
END
)
The ELSE is optional.
I am not sure why you even want to convert the packet ids to a number, they seem perfectly fine as they are. You could create a table of packets as follows
SELECT DISTINCT TableOfRows.Packet_id AS PacketId INTO Packets FROM TableOfRows;
You can then use this to select the packet you are interested in and display the corresponding rows