Stuart McLachlan
stuart at lexacorp.com.pg
Fri May 30 00:36:01 CDT 2008
Interesting article in SQL Server Central about passing an array to a stored procedure and
using a Number/Tally Table to split the array:
http://www.sqlservercentral.com/articles/T-SQL/63003/
I've recently needed to do exactly this to create a procedure to create a parent and set of
child records in one step, then return the parent PK.
Playing around with it for a while gave me an few ideas for on-the fly normalising of those
horrible comma separated fields we sometimes come across in poorly designed Access
databases. Following is a complete demo.
1. Create a new MDB.
2. Create a new module.
3. Go into References and enable DAO.
4. Paste the three functions below into the module.
5. Run the MakeNumberTable function to create your Number/Tally table
I've only filled it with 255 values because that's the max length of a text field
you can make the table much larger if you are working with memo fields like JC :-)
6. Run the MakeDataTable function to create a set of test data.
7. Now open the two tables just to see what you have got.
8. Run the Normalise function to create and then open the query which makes
a normalised version of tblData!
9. Take a look at qryNormalise and see if you can understand how it works :-)
Cheers,
Stuart
'----code follows----
Option Compare Database
Option Explicit
Function MakeNumberTable() As Long
'Create a "Numbers Table" or "Tally Table" and poulate it with series of integers
Dim tdfNew As DAO.TableDef
Dim idxNew As Index
Dim l As Long
Dim rs As Recordset
'Delete table if it exists
On Error Resume Next
DoCmd.DeleteObject acTable, "tblNumbers"
On Error GoTo 0
'Create table
Set tdfNew = CurrentDb.CreateTableDef("tblNumbers")
tdfNew.Fields.Append tdfNew.CreateField("Num", dbLong)
Set idxNew = tdfNew.CreateIndex("PrimaryKey")
idxNew.Fields.Append idxNew.CreateField("Num")
idxNew.Primary = True
idxNew.Unique = True
tdfNew.Indexes.Append idxNew
CurrentDb.TableDefs.Append tdfNew
'Fill table with numbers 1 to 255
Set rs = CurrentDb.OpenRecordset("tblNumbers")
For l = 1 To 255
rs.AddNew
rs!Num = l
rs.Update
Next
End Function
Function MakeDataTable() As Long
'Create a sample demormalised table with 1000 records
'each one containing a PK and up to 80 comma separated values
Dim tdfNew As DAO.TableDef
Dim idxNew As Index
Dim fldNew As Field
Dim rs As Recordset
Dim l1 As Long
Dim l2 As Long
Dim l3 As Long
'Delete the data table if it exists
On Error Resume Next
DoCmd.DeleteObject acTable, "tblData"
On Error GoTo 0
'create a new data table
Set tdfNew = CurrentDb.CreateTableDef("tblData")
Set fldNew = tdfNew.CreateField("ID", dbLong)
fldNew.Attributes = dbAutoIncrField + dbFixedField
tdfNew.Fields.Append fldNew
Set fldNew = tdfNew.CreateField("Data", dbText, 255)
tdfNew.Fields.Append fldNew
Set idxNew = tdfNew.CreateIndex("PrimaryKey")
idxNew.Fields.Append idxNew.CreateField("ID")
idxNew.Primary = True
idxNew.Unique = True
tdfNew.Indexes.Append idxNew
CurrentDb.TableDefs.Append tdfNew
'fill the data table with non-normalised data
Set rs = CurrentDb.OpenRecordset("tblData")
Randomize Timer
For l1 = 1 To 1000
strData = ""
For l2 = 1 To Int((80) * Rnd + 1)
strData = strData & "," & Int((100) * Rnd + 1)
Next
strData = Mid$(strData, 2)
rs.AddNew
rs!Data = strData
rs.Update
Next
End Function
Function Normalise() As Long
'create a query to normalise the data in tblData using the Numbers Table
'joining it to the string data at character level
Dim qdfNew As QueryDef
Dim strSQL As String
strSQL = "SELECT tblData.ID, Mid$(',' & [data],[Num]+1,InStr([Num]+1,',' & [Data] & ',',',')-
[num]-1) AS DataValue " _
& "FROM tblData, tblNumbers " _
& "WHERE Num <= Len(Data) And Mid$(',' & Data,Num,1) = ',' " _
& "ORDER BY ID, Num;"
Set qdfNew = CurrentDb.CreateQueryDef("qryNormalise", strSQL)
DoCmd.OpenQuery "qryNormalise"
End Function
'----end code ----