4

I have an MS Access Database which has a Field called Field1 that contains multiple values delimited by commas. i.e.,

Value1,Value 2, Value3, Value 4,Value5

I am trying to split the values not into separate fields, but by duplicating the record and storing each value in another field. This will be such that a record containing a cell with three values will be duplicated three times, with each record varying in the value contained in the new field. For example,

Before query/running module:

+-----------+------------------------+ | App Code | Field1 | +-----------+------------------------+ | AB23 | Value1, Value 2,Value3 | +------------------------------------+

After query/running module:

+-----------------------------------------------+ | App Code | Field1 | Field2 | +-----------+------------------------+----------+ | AB23 | Value1, Value 2,Value3 | Value1 | +-----------+------------------------|----------+ | AB23 | Value1, Value 2,Value3 | Value 2 | +-----------+------------------------+----------+ | AB23 | Value1, Value 2,Value3 | Value3 | +-----------+------------------------+----------+

So far, I have found several questions about splitting a field into two or even several different fields, but I have not found any solution for splitting the record vertically. Of these solutions, some use queries and others use modules but I am also uncertain of which is most efficient, so I decided to go with a VBA module.

And so, here is the VBA module that I have found to be the most useful so far:

Function CountCSWords (ByVal S) As Integer
      ' Counts the words in a string that are separated by commas.

      Dim WC As Integer, Pos As Integer
         If VarType(S) <> 8 Or Len(S) = 0 Then
           CountCSWords = 0
           Exit Function
         End If
         WC = 1
         Pos = InStr(S, ",")
         Do While Pos > 0
           WC = WC + 1
           Pos = InStr(Pos + 1, S, ",")
         Loop
         CountCSWords = WC
      End Function

      Function GetCSWord (ByVal S, Indx As Integer)
      ' Returns the nth word in a specific field.

      Dim WC As Integer, Count As Integer, SPos As Integer, EPos As Integer
         WC = CountCSWords(S)
         If Indx < 1 Or Indx > WC Then
           GetCSWord = Null
           Exit Function
         End If
         Count = 1
         SPos = 1
         For Count = 2 To Indx
           SPos = InStr(SPos, S, ",") + 1
         Next Count
         EPos = InStr(SPos, S, ",") - 1
         If EPos <= 0 Then EPos = Len(S)
         GetCSWord = Trim(Mid(S, SPos, EPos - SPos + 1))
      End Function

Yet how could I use this in an Access Query to achieve the aforementioned desired results? Otherwise, is there a better way to come to the same conclusion other than a Query (i.e. solely with a VBA module)?

EDIT

Note that the primary key in the Table is Application Code and not autonumber. This primary key is textual and distinct. In order for a record to be split, this will require the primary key to be duplicated, which is fine.

Paradox
  • 4,602
  • 12
  • 44
  • 88
  • You can do this in VBA with a much more simple loop using Split & Trim on Field1 - are you asking specifically about ONLY using a query? – dbmitch Jul 18 '16 at 15:22
  • I'll edit for clarity, thanks for pointing that out. I am not looking for a solution that only involves a query, only a solution that will efficiently get the job done - if that entails a suggestion involving VBA, then it is welcome! – Paradox Jul 18 '16 at 15:24
  • @HansUp: Yes, it does. The primary key is `App Code`. – Paradox Jul 18 '16 at 17:12
  • @HansUp: Out of curiosity, why would it be faster to parse `Field1` in that method? Also, thanks for the interesting solution - it would be practical since it can be consolidated with a query after. :) – Paradox Jul 18 '16 at 17:37
  • @HansUp - if you use two recordsets in parallel there isn't any conflict when you build the first recordset query to only process unadjusted record (no data in field2, comma in field1) and the second recordset is AppendOnly. See my proposal below. – dbmitch Jul 18 '16 at 18:04
  • Confused - where does AppCode come into play? The update works fine with any table structure as long as it has the two fields mentioned in it. It also splits and adds new records exactly as he wanted. What does order of values have to do with results? You don't need the primary key to do differentiate the recordsets. – dbmitch Jul 18 '16 at 18:24
  • Pardon my ignorance - but so what? My point is the table can have a primary key called anything - and it can have 300 other fields in it. The code will still work. Unless he's saying that AppCode primary key is something other than a Unique ID it doesn't matter. The ONLY exception would be if he's said the AppCode is somehow based on the Values being extracted - but he hasn't said that in his question - or the comments that I have seen. I can change my example to use AppCode for primary key instead of ID - but the point is - it doesn't matter. – dbmitch Jul 18 '16 at 18:33
  • Well if it is a Unique ID that is NOT autonumber - then that's a KEY point that should be included in the question. – dbmitch Jul 18 '16 at 18:34
  • @dbmitch: I will append that to my question right now. – Paradox Jul 18 '16 at 18:40
  • That's an important piece of information - the second important piece is to include an example of your primary key with your example data set - and to describe how the key is generated. As well is it text or numeric - and is based on Field1? – dbmitch Jul 18 '16 at 18:44

1 Answers1

2

Here's a sample piece of code using Field1, Field2 in your a Table1

Option Explicit

Public Sub ReformatTable()

    Dim db          As DAO.Database
    Dim rs          As DAO.Recordset
    Dim rsADD       As DAO.Recordset

    Dim strSQL      As String
    Dim strField1   As String
    Dim strField2   As String
    Dim varData     As Variant
    Dim i           As Integer

    Set db = CurrentDb

    ' Select all eligible fields (have a comma) and unprocessed (Field2 is Null)
    strSQL = "SELECT Field1, Field2 FROM Table1 WHERE ([Field1] Like ""*,*"") AND ([Field2] Is Null)"

    Set rsADD = db.OpenRecordset("Table1", dbOpenDynaset, dbAppendOnly)

    Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
    With rs
        While Not .EOF
            strField1 = !Field1
            varData = Split(strField1, ",") ' Get all comma delimited fields

            ' Update First Record
            .Edit
            !Field2 = Trim(varData(0)) ' remove spaces before writing new fields
            .Update

            ' Add records with same first field 
            ' and new fields for remaining data at end of string
            For i = 1 To UBound(varData)
                With rsADD
                    .AddNew
                    !Field1 = strField1
                    !Field2 = Trim(varData(i)) ' remove spaces before writing new fields
                    .Update
                End With
            Next
            .MoveNext
        Wend

        .Close
        rsADD.Close

    End With

    Set rsADD = Nothing
    Set rs = Nothing
    db.Close
    Set db = Nothing

End Sub

Example of Before

After running Program

EDIT

Updated example to generate a new primary key

If you have to generate a new AppCode based on previous Appcode (AND assumming AppCode is a text field), you can use this example to generate a unique primary key based on last appcode.

Option Explicit

Public Sub ReformatTable()

    Dim db          As DAO.Database
    Dim rs          As DAO.Recordset
    Dim rsADD       As DAO.Recordset

    Dim strSQL      As String
    Dim strField1   As String
    Dim strField2   As String
    Dim varData     As Variant
    Dim strAppCode  As String
    Dim i           As Integer

    Set db = CurrentDb

    ' Select all eligible fields (have a comma) and unprocessed (Field2 is Null)
    strSQL = "SELECT AppCode, Field1, Field2 FROM Table1 WHERE ([Field1] Like ""*,*"") AND ([Field2] Is Null)"

    ' This recordset is only used to Append New Records
    Set rsADD = db.OpenRecordset("Table1", dbOpenDynaset, dbAppendOnly)

    Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
    With rs
        While Not .EOF

            ' Do we need this for newly appended records?
            strAppCode = !AppCode

            strField1 = !Field1
            varData = Split(strField1, ",") ' Get all comma delimited fields

            ' Update First Field
            .Edit
            !Field2 = Trim(varData(0)) ' remove spaces before writing new fields
            .Update

            ' Add new fields for remaining data at end of string
            For i = 1 To UBound(varData)
                With rsADD

                    .AddNew

                    ' ***If you need a NEW Primary Key based on current AppCode
                    !AppCode = strAppCode & "-" & i

                    ' ***If you remove the Unique/PrimaryKey and just want the same code copied
                    !AppCode = strAppCode

                    ' Copy previous Field 1
                    !Field1 = strField1

                    ' Insert Field 2 based on extracted data from Field 1
                    !Field2 = Trim(varData(i)) ' remove spaces before writing new fields
                    .Update
                End With
            Next
            .MoveNext
        Wend

        .Close
        rsADD.Close

    End With

    Set rsADD = Nothing
    Set rs = Nothing
    db.Close
    Set db = Nothing

End Sub

Example of new AppCode key after running code

dbmitch
  • 5,361
  • 4
  • 24
  • 38
  • 2
    Nice. Comments in the loop should be `' Update First record` and `' Add new records` to avoid confusion. :) – Andre Jul 18 '16 at 16:24
  • Thank you! I'm just adapting it to my database and trying it out. I also made a small edit in the first table in my question to better reflect my situation, but I am coding that right now. :) – Paradox Jul 18 '16 at 17:34
  • What is the "small edit"? And how is your primary key defined? Is it a AutoNumber - or based on Field1? Make sure to update your question if you want to "better reflect your situation". We can only solve probems when we have the correct info. – dbmitch Jul 18 '16 at 18:41
  • That edit was removing the second field in the first table, since that field will not already exist in the database beforehand. I have updated my question to include the information you requested. – Paradox Jul 18 '16 at 18:56
  • I updated my example - but to clarify - you can't have a "unique primary key" and then say "this will require the primary key to be duplicated, which is fine." You'll have to remove the Primary Key - and Unqiue index before you run the program - my second example will work without doing that - but now I'm not sure at all what your goal is. – dbmitch Jul 18 '16 at 19:00
  • 1
    My primary key has no duplicates. If running the macro causes duplicate rows, and hence duplicates of the primary key, then that is not a problem for me. However, if the primary key must always be unique (I genuinely do not know if it must as I am new to VBA), then please let me know and I can change the primary key accordingly. If there is anything else that you would like to know, please tell me instead of making snarky comments. – Paradox Jul 18 '16 at 19:03
  • You still haven't shown us your AppCode field. I'm just guessing right now. The macro doesn't cause duplicate **rows**. But you'll get duplicate **AppCodes** - you can either 1. Change your primary key so it is on three fields (AppCode+Field1+Field2) - or use my example code to change the copied rows to be [AppCode]-# - which will keep it unique. Look at my second example and code. – dbmitch Jul 18 '16 at 19:09
  • I also noticed that there's an error on the following line due to a missing parameter: `Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)` – Paradox Jul 18 '16 at 19:26
  • Everything is optional after first parameter. The code works perfectly on my system - you'll have to copy/paste YOUR code for troubleshooting. The code above works as is. Does yours compile? – dbmitch Jul 18 '16 at 19:30
  • No mine does not; I get a runtime error `too few parameters. Expected 3.`. Perhaps a mistake on my end. Thank you for all of your help! – Paradox Jul 18 '16 at 19:31
  • What does your strSQL look like? Use a Debug.Print strSQL just before you call openrecordset – dbmitch Jul 18 '16 at 19:33
  • @dbmitch: I have re-entered your code and adapted it for my variables, and now I get the error `Run-time error '3061': Too few parameters. Expected 1.` my strSQL is `SELECT [App Code], [Field1], Field2 FROM Table1 WHERE ([Field1] Like "*,*") AND ([Field2] Is Null)` . The extra `[]` are because the actual name in my database has spaces. – Paradox Jul 19 '16 at 14:19
  • You have a field name spelled wrong - your example shows ID - not `App Code` - check your Table1 again – dbmitch Jul 19 '16 at 14:23
  • I will update OP to change ID to App Code, since that is what we went with in the updated example to generate a new primary key. – Paradox Jul 19 '16 at 14:37
  • You can change it here - the point is to make sure it is THE SAME as what is in your Table – dbmitch Jul 19 '16 at 14:42
  • @dbmitch: Found the problem - it is now fixed. It does not seem to be splitting properly though. Since this question is very populated, do you think it would be best to open up another? – Paradox Jul 19 '16 at 14:46
  • You're setting moving targets - if you define the problem properly with your actual data and goals the solution comes much faster. You can try a new question but it'll probably just be marked as duplicate. Update your question with ACTUAL data, CURRENT code and full description of what you expect with any error messages. The code given here works with what you described - so it DOES split properly with how you defined the problem – dbmitch Jul 19 '16 at 15:46