Paradox Paradox - 4 months ago 31
SQL Question

Split Field Into Multiple Records in Access DB

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:

+------+------------------------+
| ID | Field1 |
+------+------------------------+
| AB23 | Value1, Value 2,Value3 |
+-------------------------------+


After query/running module:

+------------------------------------------+
| ID | 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.


Answer

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