Bulk insert from Excel to SQLite without TXT or CSV file - excel

I have a problem and I hope to get some input from any of you.
Basically, I need to write a code in VBA which enables me to take a table of data from excel to sqlite.
I have managed to do it using this code:
Sub Scrivi()
Dim objConnection As Object, objRecordset As Object
Dim strDatabase As String
Set objConnection = CreateObject("ADODB.Connection")
strDatabase = "DRIVER={SQLite3 ODBC Driver};Database=" & Application.ActiveWorkbook.Path & "\TestDB.db;"
Query = "drop table FUND; create table FUND (Fund_Code, Fund_Name, End_Date, PRIMARY KEY('Fund_Code'));"
riga = 3
colonna = 1
While Not (IsNull(ActiveSheet.Cells(row, column).Value) Or IsEmpty(ActiveSheet.Cells(row, column).Value))
Query = Query & "insert into FUND values('" & Cells(row, column) & "','" & Cells(row, column + 1) & "','" & Cells(row, column + 2) & "');"
row = row + 1
Wend
objConnection.Open strDatabase
objConnection.Execute Query
objConnection.Close
End Sub
Now I would like to change the iterative part in which I say row = row + 1 with a sort of bulk insert in which the program takes the whole table instead of doing it row by row.
I do not want a creation of csv/txt file if it is possible.
Thank you very much, any input would be appreciated.

A very basic level solution can be through first building the complete query for bulk insert and then execute the query in one go.
For Example -
insert into FUND values(val1, val2);
insert into FUND values(val3, val4);
can be replaced with -
insert into FUND values(val1, val2),(val3, val4)

you could append all data from worksheet.range into array(variant)
Dim arr as Variant
arr = Application.Transpose(range(your_range_of_data).values)
and then, using data in array, build SQL query string using this pattern
INSERT INTO table
(column1, column2, ... )
VALUES
(expression1, expression2, ... ),
(expression1, expression2, ... ),
...;
VBA Function (written 'on the knee', so it might require a little bit of tweaking, sorry)
Function buildQueryFromArray(arr as Variant, table as String, columns as String) as String
Dim strSqlQuery as String * 4096
Dim i as Integer
strSqlQuery = "INSERT INTO " & table & " (" & columns & ") VALUES"
For i=LBound(arr, 1) to UBound(arr, 1)
strSqlQuery = strSqlQuery & "(" & arr(i,1) & ", " & arr(i,2) & "etc" & "),"
Next i
buildQueryFromArray = Left(strSqlQuery, len(strSqlQuery)-1) & ";"
End Function
reason for using array instead of grabbing data directly from worksheet is: its much much faster. You can read more here: http://www.cpearson.com/excel/ArraysAndRanges.aspx
lemme know if that works for you : )
EDIT:
Ad1: you're right, we actually don't want to transpose it. however, I got used to write this way because of the reasons which are well described in post #7 in this topic: mrexcel.com/forum/ Why we use application.transpose?
Ad2: no, it doesn't have to be in function if this is your question, but it makes code cleaner and more reusable. Below you can find inline solution:
Sub Scrivi()
Dim objConnection As Object, objRecordset As Object
Dim strDatabase As String, strQuery as String
Set objConnection = CreateObject("ADODB.Connection")
strDatabase = "DRIVER={SQLite3 ODBC Driver};Database=" & Application.ActiveWorkbook.Path & "\TestDB.db;"
strQuery = "drop table FUND; create table FUND (Fund_Code, Fund_Name, End_Date, PRIMARY KEY('Fund_Code')); insert into FUND values"
'determine how many rows of data there is
Dim lastRow as Integer
lastRow = ActiveSheet.Range("A:A").Find("*", LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
'transfer all data from range to array
'range: from A3 to Cx, where x equals to lastRow
Dim arr as Variant
arr =
ActiveSheet.Range("A3:C" & lastRow).Value
'add all records from arr into single SQL query
'Lbound(xArray, xDimension)/Ubound(xArray, xDimension) - returns integer equal to first/last xArray item in xDimension. Everyday-use function. Google some more to have better insight of whats going on
Dim i as Integer
For i=Lbound(arr,1) to Ubound(arr,1)
strQuery = strQuery & "(" & arr(i,1) & "," & _
arr(i,2) & "," & _
arr(i,3) & "),"
Next i
'from the strQuery - swap last comma for semi-colon
strQuery = Left(strQuery, len(strQuery)-1) & ";"
objConnection.Open strDatabase
objConnection.Execute strQuery
objConnection.Close
Lemme know if everything is clear :)

Related

Sequencing a part number using User Form

I am completely new in VBA or programming. Right now I am developing a macro for a manufacturing site that inputs process data using Excel's User Forms. One of the things I want this macro to do is to automatically create run numbers for each process. The run number syntax we use is as follows:
V1.yy.mm.dd-1
V1.yy.mm.dd-2
V1.yy.mm.dd-3
Ex V1.20.04.29-1
The way I am trying to set up the run number creation is that when I select an item from a ComboBox the part number gets created into a TextBox to later be submitted into the corresponding database. I am not sure how to create a sequence after the Prefix = V1.yy.mm.dd-, I tried to use a CountIf application that would count the number of Prefixes with the same date in the spreadsheet for sequencing, but it seems the function does not work for partial matches. I tried to use the following but I can't get it to work. I am sure there are simpler ways to do this, can you give me a few suggestions? Thanks
This is the code I wrote so far:
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Dim Prefix As String
Dim mm, dd, yy As String
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
Dim s As Long
s = 1 + sh.Application.Count(Application.Match(Prefix, Range("B:B"), 0))
mm = Format(Date, "mm")
dd = Format(Date, "dd")
yy = Format(Date, "yy")
Prefix = "V1." & yy & "." & mm & "." & dd & "-"
v1 = "V1." & yy & "." & mm & "." & dd & "-" & "" & s
Me.TextBox6.Value = v1
End If
Maybe something like this ?
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
oDate = Format(Date, "yy.mm.dd")
oConst = "V1." & oDate & "-"
Range("B1:B10000").Copy Destination:=Range("zz1") 'copy all the item to helper column
Range("zz:zz").Replace What:=oConst, Replacement:="" 'get only the number from all the items with the current date
nextNum = Application.Max(Range("zz:zz")) + 1 'get the next number
MsgBox oConst & CStr(nextNum) 'this line only for checking
Range("zz:zz").ClearContents 'clear the helper column
Me.TextBox6.Value = oConst & CStr(nextNum)
End If
But this assuming that the item number in columns B is only at the same day.
If for example there is a forgotten data from any day before the current day, and this want to be inputted with that day and the next number, it need an input box or maybe a cell in sheet where the value is that day, then it will give the last number of that day.
Suppose the data in column B is something like below:
If the code is run today, it will show V1.20.04.30-4 as the next number. With the same data like above, if the code is run tomorrow, it will give V1.20.05.01-1.
To get the next number from yesterday (29 Apr 2020), the code need more line - which is to know on what day the code must get the next number.
Or this kind of line maybe is shorter:
oConst = "V1." & Format(Date, "yy.mm.dd") & "-"
nextNum = oConst & Application.WorksheetFunction.CountIf(Range("B:B"), "*" & oConst & "*") + 1
MsgBox nextNum
There are a few ways you could go about this but I'd say the easiest would be to put the incrementing run number in a separate cell somewhere on your worksheet (or another one if you want) to reference each time.
For example:
When the data is entered onto your 'database' sheet, write the run value to ThisWorkbook.Sheets("2- V1 Loading (2)").Range("AZ1").
Then in your code check that value like so:
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Dim Prefix As String
Dim mm, dd, yy As String
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
Dim s As Long
s = 1 + sh.Range("AZ1").Value
mm = Format(Date, "mm")
dd = Format(Date, "dd")
yy = Format(Date, "yy")
Prefix = "V1." & yy & "." & mm & "." & dd & "-"
v1 = "V1." & yy & "." & mm & "." & dd & "-" & s
Me.TextBox6.Value = v1
Presuming that the reference numbers are written to column B of the 2- V1 Loading (2) tab then the next number must always be the one found at the bottom of the column + 1. If there is no number for that date than the new sequential number should be 1. The code below implements that method
Function NextRef() As String
' 016
Dim Fun As String
Dim Counter As Integer
Dim Rng As Range
Dim Fnd As Range
Dim Sp() As String
Fun = Format(Date, """V1.""yy.mm.dd")
With ThisWorkbook.Worksheets("2- V1 Loading (2)")
' start in row 2 (row 1 holding column captions)
Set Rng = .Range(.Cells(2, "B"), .Cells(.Rows.Count, "B").End(xlUp))
End With
If Rng.Row > 1 Then ' skip, if the column is empty
' finds the first occurrence of Ref from the bottom
Set Fnd = Rng.Find(What:=Fun, _
After:=Rng.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchDirection:=xlPrevious)
If Not Fnd Is Nothing Then
Sp = Split(Fnd.Value, "-")
If UBound(Sp) Then Counter = Val(Sp(1))
End If
End If
NextRef = Fun & -(Counter + 1)
End Function
You can use the function simply like ComboBox1.Value = NextRef. However when and how to call that line of code is a bit unclear in your design as published. Especially, it's not clear why you would want it in a ComboBox at all, given that the box might also contain other information. Your idea to use the Change event may not work as intended because that event occurs with every letter the user types. I have tested this:-
Private Sub ComboBox1_GotFocus()
' 016
With ComboBox1
If .Value = "" Then .Value = NextRef
End With
End Sub
The next reference number is inserted as soon as you click on the ComboBox. It works but it doesn't make sense. I think now that you have the function that does the work you will find a way to deploy it. Good luck.

Easier way to use declared Strings in Query in VBA

I'm writing a macro which should run queries to transfer data from excel to a Access database, and everything is working fine, however, if I want to use a String in one of these queries, I've to type them like this:
'" & Lijn & "'
I know that the following code (which is written in VBA) is way easier to write in Javascript, by using question marks and setString:
VBA:
Dim ShiftsQ As String
ShiftsQ = "INSERT INTO Shifts(Lijn, Operator, Ploeg, Teamleider) VALUES ('" & Lijn & "', '" & Operator & "', '" & Ploeg & "', '" & Teamleider & "');"
Javascript:
var ShiftsQ = SQL.prepareStatement(INSERT INTO Shifts(Lijn, Operator, Ploeg, Teamleider) VALUES (?, ?, ?, ?);
ShiftsQ.setString(1, Lijn);
ShiftsQ.setString(2, Operator);
ShiftsQ.setString(3, Ploeg);
ShiftsQ.setString(4, Teamleider);
Is there anyway to write the VBA code like the Javascript one?
As far as I know, there is nothing like the .NET string.Format() method VBA. But you could write your own version of such a function that uses deputys and returns a formatted string.
Private Sub Main()
' Your actual query
' The deputys are indexed in curley brackets (inspired from the .NET syntax of the equivalent function, making your code easy to read for .NET programmers)
Dim qry As String
qry = "SELECT {0}, {1} FROM {2} WHERE {3}"
' The values to set for the deputys in your query
Dim parameters(3) As String
parameters(0) = "firstname"
parameters(1) = "lastname"
parameters(2) = "users"
parameters(3) = "userID = 'M463'"
' For demo purposes, this will display the query in a message box
' Instead of the MsgBox, you would use the formatted query to execute against the database
MsgBox FormatString(qry, parameters)
End Sub
' This is where the magic happens, the deputys in the given string will be replaced with the actual values from the provided array
Private Function FormatString(strInput As String, paramValues() As String)
' This will be our return value
Dim strOutput As String
strOutput = strInput
' Verify that the given count of parameters matches the given count of deputys in the input string
Dim maxParamIndex As Integer
maxParamIndex = UBound(paramValues)
Dim deputyCount As Integer
For i = 1 To Len(strOutput) + 1 Step 1
If Mid(strOutput, i, 3) = "{" & deputyCount & "}" Then
deputyCount = deputyCount + 1
End If
Next
' If there is a mismatch between the count of parameters and the count of deputys, display exception message and exit the function
' Adding +1 to maxParamIndex is neccessary, as maxParamIndex refers to the maximum index (starting at 0, not 1) of the given array and deputyCount refers to the actual count of deputys (starting at 1)
If maxParamIndex + 1 <> deputyCount Then
MsgBox "Number of deputys has to match number of parameters for the given string:" & vbCrLf & strInput, vbCritical, "Exception in Function FormatString"
FormatString = ""
End If
' Iterate through the array and replace the deputys with the given values
For i = 0 To maxParamIndex Step 1
strOutput = Replace(strOutput, "{" & i & "}", paramValues(i))
Next
' return the formatted string
FormatString = strOutput
End Function
Result of example:
If I face this problem I would simply solve it on my own (althoug there might be other "standard" solutions), by defining my own simple, global function (put in in any standard code module)
Public Function S_(str as String) as String
S_ = chr(39) & str & chr(39)
End Function
ShiftsQ = "INSERT INTO Shifts(Lijn, Operator, Ploeg, Teamleider) VALUES (" & S_(Lijn) & ", " & S_(Operator) & ", " & S_(Ploeg) & ", " & S_(Teamleider) & ");"
This way, I will follow a simple and systematic rule in all my project, that is call S_(param) on any parameter of text type in my queries...

Can I make my VBScript program ignore a field when reading from Excel?

I did a little bit of reading on what I am about to ask, but I couldn't find a specific answer.
I am writing a program in VBScript that will read an excel file and then update an Access Database.
It works great, but the problem I can foresee running into is thus:
What happens when the excel file has a blank on a specified field? I don't want the script file to update a "blank" to the database, I want it to be left unchanged in the database ONLY if there is a blank in the excel file. If not, proceed as normal.
Currently, it will read this as a blank and insert the blank into the row in my database.
Is this possible? For the script to basically ignore fields it reads (in Excel) that are blank while only updating (in the database) the fields that actually have data (in Excel) in them?
Currently, it says no in the field in the database. This is just a brief code example. In the Excel sheet, it says yes in the correct column.
Do Until objExcel.Cells(intRow,1).Value = ""
asset_Tag = objExcel.Cells(intRow, 1).Value
ebay = objExcel.Cells(intRow, 2).Value
intRow = intRow + 1
objRecordSet.Open "UPDATE Test SET Listed_Ebay = '" & ebay & "' WHERE Asset_Tag = '" & asset_Tag & "'", _
objConnection
Loop
If you are reluctant to add 20 If statements to your code, surely you could put Excel's native COUNTA functionality to make sure that there are 20 values before the update operation.
With objExcel
Do Until .Cells(intRow,1).Value = ""
If .Application.CountA(.Cells(intRow, 1).Resize(1, 20)) = 20 Then
asset_Tag = .Cells(intRow, 1).Value
ebay = .Cells(intRow, 2).Value
objRecordSet.Open "UPDATE Test SET Listed_Ebay = '" & ebay & "' WHERE Asset_Tag = '" & asset_Tag & "'", objConnection
End if
intRow = intRow + 1
Loop
End With
I had a guess a bit as to which 20 values were intended to be filled but I think you can get the idea.
EDIT NOTEĀ¹: reordered the code lines to maintain the intRow according to the loop.
EDIT NOTEĀ²: You may want to conditionally pass the original value back into the UPDATE query if the LEN of the new value is zero.
Dim qry as String
qry = "UPDATE Test SET " & _
"Listed_Ebay = Iif(CBool(Len('" & ebay & "')), '" & ebay & "', Listed_Ebay), " & _
"Listed_Amazon = Iif(CBool(Len('" & amzon& "')), '" & amzon & "', Listed_Amazon), " & _
"Listed_POS = Iif(CBool(Len('" & pos& "')), '" & pos & "', Listed_POS) " & _
"WHERE Asset_Tag = '" & asset_Tag & "'"
objRecordSet.Open qry, objConnection
I'm just making stuff up past the original one field example you provided but you should be able to transcribe this for your own 8 fields. I'm using LEN here on text values but comparing numbers to zero (or even their LEN would be appropriate.

sorting numbers in excel when number contains dash '-'

I have some number in a column in excel like this:
201
202
208-1
210
when I sort this column, sorted column is like below:
201
202
210
208-1
How do I sort this column? I want the sorted column becomes like this:
201
202
208-1
210
or
210
208-1
202
201
One option is a hidden column, say if your values listed above were in A2:A5, insert a column to the right and in B2 enter the formula below and copy this down to the other B cells:
=IFERROR(VALUE(LEFT(A2,FIND("-",A2)-1)),VALUE(A2))
or alternative suggested by #Gary'sStudent that handles values after the hyphen as well by converting to decimals:
=IFERROR(VALUE(SUBSTITUTE(A2,"-",".")),VALUE(A2))
This strips out the number up to the first hyphen. Select all of the values in the two columns, select sort and then sort by columnB. you can then right click on column B and select hide.
If you do not want to use hidden columns then I think your only option would be to write some VBA to do a custom sort procedure. You would then also need a way of triggering this such as a control in the spreadsheet or just a keyboard shortcut.
UPDATE
I have had a go at the VBA procedure, it was not as straight forward as I expected so it may be that there is an easier way to do this.
The basic steps I went through are to prompt the user for a cell range (you just have to select the cells when prompted), store the values to an array of strings, create an equivalent numeric array where the hyphens are replaced by decimal points, sort the numeric array and then loop through the initial range pasting in the values in order.
I was surprised to find out that VBA does not have a built in method for sorting an array, but found some code that could be used here. This creates a temp worksheet and uses the worksheet function, there is also code there for a pure VBA solution but it's pretty lengthy.
To create the VBA procedure you will need to open the VBA editor with alt F11 and create a new module then paste the code below into a module (create a new module - right click on modules on right and insert) then paste in the code below.
The procedure that you need to call is sort_with-hyphens.
You will need to create a control or create a keyboard short cut to trigger this. For either you will need to enable the developer ribbon tab through File>Options. For the control do developer>control>button and right click to assign a macro. For the keyboard short cut developer>Macros select the VBA procedure name from the list of macros and select options.
Sub sort_with_hyphens()
On Error GoTo sort_with_hyphens_err
Dim vRange As Range
Dim vCell As Variant
Dim vStrArray(), vNumArray()
Dim i As Long, vStart As Long, vEnd As Long
Dim vStep As String: vStep = "Initialising values"
' prompt user to specify range
Set vRange = Application.InputBox("Select a range to be sorted", _
"Obtain Range Object", _
Type:=8)
vStrArray = vRange.Value
vStart = LBound(vStrArray)
vEnd = UBound(vStrArray)
ReDim vNumArray(vStart To vEnd)
vStep = "Populating Numeric Array"
' loop through array copying strings with hyphen to decimal equivalent
For i = vStart To vEnd
vNumArray(i) = Val(Replace(vStrArray(i, 1), "-", "."))
Debug.Print i, vNumArray(i)
Next i
' sort numeric array
vStep = "Sorting Numeric Array"
SortViaWorksheet vNumArray
' write out sorted values
vStep = "Writing out Sorted Values"
For i = vStart To vEnd
' convert back to string and switch periods back to hyphens
vRange.Cells(i, 1).Value = Replace(CStr(vNumArray(i)), ".", "-")
Next
sort_with_hyphens_exit:
Exit Sub
sort_with_hyphens_err:
If vStep = "Writing out Sorted Values" Then
MsgBox ("An error has occurred, the original values will " & _
"be restored. Error in Step: " & vStep & vbCrLf & _
"Error Details:" & vbCrLf & err.Number & " - " & _
err.Description)
For i = vStart To vEnd
' replace with original value incase of error
vRange.Cells(i, 1).Value = vStrArray(i)
Next
Else
MsgBox ("An error has occurred in Step: " & vStep & vbCrLf & _
"Aborting sort procedure." & vbCrLf & _
"Error Details:" & vbCrLf & err.Number & " - " & _
err.Description)
End If
End Sub
Sub SortViaWorksheet(pArray)
Dim WS As Worksheet ' temporary worksheet
Dim R As Range
Dim N As Long
Application.ScreenUpdating = False
' create a new sheet
Set WS = ThisWorkbook.Worksheets.Add
' put the array values on the worksheet
Set R = WS.Range("A1").Resize(UBound(pArray) - LBound(pArray) + 1, 1)
R = Application.Transpose(pArray)
' sort the range
R.Sort key1:=R, order1:=xlAscending, MatchCase:=False
' load the worksheet values back into the array
For N = 1 To R.Rows.Count
pArray(N) = R(N, 1)
Next N
' delete the temporary sheet
Application.DisplayAlerts = False
WS.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
' test/debug/confirmation
Debug.Print vbCrLf & "Sorted Array:" & vbCrLf & "------------"
For N = LBound(pArray) To UBound(pArray)
Debug.Print N, pArray(N)
Next N
End Sub
Let me know if you have any questions.

Appending records from one table to another (with different columns)

I'm looking for a way to add the records from one table to another effectively. The tables are similar, but different. Let's say I'm trying to append all of the data from Employee table B into Employee table A.
Employee Table B
---------------
EmpID (int), fname (text), lname(text)
1 Bob Dole
I want this data appended to
Employee Table A
empid (int) fname(text) lname(text) DateOfBirth (DateTime)
as
1 Bob Dole null / blank
I have to do this for around 30+ tables and am looking for a quick way to do it.
Rather than writing a INSERT INTO table_a(empid, fname, lname) SELECT empid, fname,lname FROM table_b, I would like to use some of Access's importing features to save time. I tried dumping each table to excel and appending to the necessary tables, but I got a Subscript out of range error. I also tried copy pasting the records to no avail.
Is there a tool that Access provides that will save me the trouble of writing an append query for each table?
Provided the field names are the same in both tables (except for the ones missing) you could write some code to do it for you. Use the TableDefs (http://msdn.microsoft.com/en-us/library/office/bb220949(v=office.12).aspx) object to loop through tables and look for "_a" tables to append to and create the INSERT statement on-the-fly by querying the TableDef's .Fields collection.
For example something like this should work (un-tested, written by hand!):
Dim dbs As DAO.Database
Dim tdfLoop As TableDef
Dim strSql As String
Dim i as Integer
Dim strSourceTable as String
Dim strFieldList as String
Set dbs = CurrentDb
With dbs
For Each tdfLoop In .TableDefs
If Right(tdfLoop.Name, 2) = "_a" Then
strSourceTable = Mid(tdfLoop.Name, 1, Len(tdfLoop.Name)-2) & "_b"
strSql = "INSERT INTO " & tdfLoop.Name & "("
strFieldList = ""
For i = 0 To tdfLoop.Fields.Count - 1
strFieldList = strFieldList & tdfLoop.Fields(i).Name & ","
Next i
If strFieldList <> "" Then
strFieldList = Mid(strFieldList, 1, Len(strFieldList) - 2)
End If
strSql = strSql & strFieldList & ") SELECT " & strFieldList & " FROM " & strSourceTable
dbs.Execute(strSql)
End If
Next tdfLoop
End With
dbs.Close
Set dbs = Nothing
If the 'missing fields' do not have defaults defined in the table, then you could modify the above to return NULL values in their columns but I've assumed they have.

Resources