Import EXCEL data to SQL Server without data conversion - sql-server

I am trying to insert excel data into sql server tables. Each column should be imported with the exact same format that the user wrote in the source Excel.
I am using following query to fetch data
SELECT * FROM OPENROWSET( 'Microsoft.ACE.OLEDB.12.0', 'Excel 12.0 Xml;HDR=YES;IMEX=1;Database=H:\Loadloandata\Test\K3.xlsx',
'SELECT * FROM [Sheet1$]')
But now in the date column of excel we are receiving some float values( format issues from the users) as shown below
Because of the invalid data, the OLE provider convert the all other dates to float values in corresponding SQL table colums (float values corresponding to each date).If a date column is automatically cast to float I won't be able to know the original format of the data in the excel file, so all columns should be imported as varchar.
How can i prevent this datatype conversion? Based on google search i have used IMEX=1 in connection string to retrieve data for mixed data columns.
But it is not working !!

I think that you should get the data types from the SQL server table first to create the recordset, rather than letting Excel decide the datatypes from the Sheet. I believe Excel decides the data types by the first row, so in your case, it assumed Funded data was an integer, then casts any following strings into that data type.
Here is a full function that I use
This code is modified by me from the original source, which is mentioned in the comment. I made changes to deal with errors better.
Function ExportRangeToSQL(ByVal sourcerange As Range, _
ByVal conString As String, ByVal table As String, _
Optional ByVal beforeSQL = "", Optional ByVal afterSQL As String) As String
'https://www.excel-sql-server.com/excel-sql-server-import-export-using-vba.htm
' Object type and CreateObject function are used instead of ADODB.Connection,
' ADODB.Command for late binding without reference to
' Microsoft ActiveX Data Objects 2.x Library
' ADO API Reference
' https://msdn.microsoft.com/en-us/library/ms678086(v=VS.85).aspx
' Dim con As ADODB.Connection
On Error GoTo Finalise ' throw friendly user connection error
Dim con As Object
Set con = CreateObject("ADODB.Connection")
con.ConnectionString = conString
con.Open
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
' BeginTrans, CommitTrans, and RollbackTrans Methods (ADO)
' http://msdn.microsoft.com/en-us/library/ms680895(v=vs.85).aspx
Dim level As Long
level = con.BeginTrans
cmd.CommandType = 1 ' adCmdText
If beforeSQL > "" Then
cmd.CommandText = beforeSQL
cmd.ActiveConnection = con
cmd.Execute
End If
' Dim rst As ADODB.Recordset
Dim rst As Object
Set rst = CreateObject("ADODB.Recordset")
With rst
Set .ActiveConnection = con
.Source = "SELECT * FROM " & table
.CursorLocation = 3 ' adUseClient
.LockType = 4 ' adLockBatchOptimistic
.CursorType = 0 ' adOpenForwardOnly
.Open
' Column mappings
Dim tableFields(100) As Integer
Dim rangeFields(100) As Integer
Dim exportFieldsCount As Integer
exportFieldsCount = 0
Dim col As Integer
Dim index As Variant
For col = 0 To .Fields.Count - 1
index = 0
index = Application.Match(.Fields(col).Name, sourcerange.Rows(1), 0)
If Not IsError(index) Then
If index > 0 Then
exportFieldsCount = exportFieldsCount + 1
tableFields(exportFieldsCount) = col
rangeFields(exportFieldsCount) = index
End If
End If
Next
If exportFieldsCount = 0 Then
Err.Raise 513, , "Column mapping mismatch between source and destination tables"
End If
' Fast read of Excel range values to an array
' for further fast work with the array
Dim arr As Variant
arr = sourcerange.Value
' The range data transfer to the Recordset
Dim row As Long
Dim rowCount As Long
rowCount = UBound(arr, 1)
Dim val As Variant
For row = 2 To rowCount
.AddNew
For col = 1 To exportFieldsCount
val = arr(row, rangeFields(col))
If IsEmpty(val) Then
Else
.Fields(tableFields(col)) = val
End If
Next
Next
.UpdateBatch
End With
rst.Close
Set rst = Nothing
If afterSQL > "" Then
cmd.CommandText = afterSQL
cmd.ActiveConnection = con
cmd.Execute
End If
Finalise:
If con.State <> 0 Then
con.CommitTrans
con.Close
End If
Set cmd = Nothing
Set con = Nothing
' Raise appropriate custom errors
Select Case Err.Number
Case -2147217843
Err.Raise 513, , "Issue connecting to SQL server database - please check login credentials"
Case -2147467259
If InStr(1, Err.Description, "Server does not exist") <> 0 Then
Err.Raise 513, , "Could not connect to SQL server, please check you are connected to the local network (in the office or on VPN)"
Else
Err.Raise 513, , "Issue connecting to SQL server database" & vbNewLine & Err.Description
End If
Case -2147217900
If InStr(1, Err.Description, "'PK_XL_Eng_Projects_QuoteRef'") <> 0 Then
Err.Raise 513, , "Quote already uploaded for this QuoteRef and Upload Time, please wait a minute before trying again" & vbNewLine & vbNewLine & Err.Description
Else
Err.Raise Err.Number, , Err.Description
End If
Case 0
' do nothing no error
Case Else
' re raise standard error
Err.Raise Err.Number, , Err.Description
End Select
End Function

Is there a reason why you using SSIS? I think that is best suited for the job.
Anyways, back to your issue. IMEX=1 is not enough. What you need is to check the registry entries
You need set TypeGuessRows and ImportMixedTypes within this registry path (this is for 32-bit office!):
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Office\12.0\Access Connectivity Engine\Engines\Excel
TypeGuessRows = 0 (the default is 8)
ImportMixedTypes = Text
What does TypeGuessRows do?
It tries to guess data type based on the number of rows defined. The default values is 8. That means it will check 8 rows to see what data type should be used. If you want to the engine to scan all the rows put 0 there. There is a catch, however, if your spreadsheet large, you could pay heavy performance penalty for such setting.
What does ImportMixedTypes do?
This is where your IMEX setting comes into the game. There are 3 possible values for IMEX setting 0, 1, 2:
0 is Export mode
1 is Import mode
2 is Linked mode (full update capabilities)
Only when setting IMEX=1 the registry value is honored. - with the default setting being ImportMixedTypes=Text. In any other value (0, 2) the value in registry is checked, if it is valid, but it does not influence the outcome. (you will get an error if invalid)
There are two valid values for ImportMixedTypes:
ImportMixedTypes=Text
ImportMixedTypes=Majority Type
The Majority Type is rarely used. What it does it counts type of each column and the majority type is then used for the whole column. The Text type will limit the row size to 255 characters, if you want to use more characters then Majority Type must be used and the majority must use more than 256 characters.

Related

Getting data from SQL Server to Excel VBA Listview

im trying to get the data in my database and show it in my listview (userform). I ran into an error saying the "Type Mismatch" im quite confuse on what part is the mismatch, already search what this error means but i've declare all my variables in correct data types. But since I have blanks or with null value it displays a error. On this line If not IsNull(Records(j, i)) Then li.ListSubItems.Add , , Records(j, i) Im trying to display null or empty on listview the mismatch error was gone but if column1 is blank and column2 have is not blank, the values on column2 will be pasted on column1.
Private Sub ListView_Data()
On Error GoTo ERR_Handler
Dim sql As String
Dim con As ADODB.Connection
Dim rst As New ADODB.Recordset
Dim Records As Variant
Dim i As Long, j As Long
Dim RecCount As Long
Set con = New ADODB.Connection
con.Open "Provider=SQLOLEDB;Data Source=10.206.88.119\BIWFO;" & _
"Initial Catalog=TESTDB;" & _
"Uid=user; Pwd=pass;"
sql = "select * from [TESTDB].[dbo].[tbl_MN_Omega_Raw]"
rst.Open sql, con, adOpenKeyset, adLockOptimistic
'~~> Get the records in an array
Records = rst.GetRows
'~~> Get the records count
RecCount = rst.RecordCount
'~~> Populate the headers
For i = 0 To rst.Fields.count - 1
ListView1.ColumnHeaders.Add , , rst.Fields(i).Name, 200
Next i
rst.Close
con.Close
With ListView1
.View = lvwReport
.CheckBoxes = False
.FullRowSelect = True
.Gridlines = True
.LabelWrap = True
.LabelEdit = lvwManual
Dim li As ListItem
'~~> Populate the records
For i = 0 To RecCount - 1
Set li = .ListItems.Add(, , Records(0, i))
For j = 1 To UBound(Records)
If not IsNull(Records(j, i)) Then li.ListSubItems.Add , , Records(j, i)
'End If
Next j
Next i
End With
ERR_Handler:
Select Case Err.Number
Case 0
Case Else
MsgBox Err.Description, vbExclamation + vbOKOnly, Err.Number
End Select
End Sub
Your type mismatch has nothing to do with null-values. It comes from a statement that looks rather unsuspicious: rst.RecordCount returns a Vartype LongLong, and assigning it to a Long throws a type mismatch.
You have 3 ways to fix this problem:
(1) Declare RecCount as Variant
(2) Convert rst.RecordCount into a Long: RecCount = CLng(rst.RecordCount)
(3) Don't use RecCount at all and instead use UBound(records, 2) (I prefer this method).
To find such errors, disable your error handler (comment the On Error Goto ERR_Handler). This will show the line that throws the error.
There is, however, a small issue when you have fields that contain null: You don't execute a li.ListSubItems.Add for those values, and therefore all values for the remaining fields of that row will be "shifted" to the left. You will need to run ListSubItems.Add also for null-Values, eg with
Dim fValue As Variant
fValue = Records(j, i)
If IsNull(fValue) Then fValue = "" ' or maybe "(null)"
li.ListSubItems.Add , , fValue

Excel VBA User Defined Function That Reads and Returns Data from a Database Doesn't Always Work. Why?

I have written a user defined function (udf) to run a select statement in access, and return the recordset as an array to an Excel spreadsheet. The function takes as its arguments two strings: a connection string for the Access database (e.g. "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Database1.accdb;Persist Security Info=False;"), and a SQL string.
Sometimes it works, sometimes it doesn't. When it doesn't work I get a #VALUE! error in my spreadsheet. I don't know what causes it to fail when it's unsuccessful. Please help me to solve this issue.
Here is my code:
Function udfREADDB(connection As String, sql As String) As Variant
Dim db As ADODB.connection, rs As ADODB.Recordset, varArray() As Variant
Dim i As Integer, j As Integer ' rows & cols
Set db = New ADODB.connection
With db
.ConnectionString = connection
.ConnectionTimeout = 10
.Open
End With
' Query database
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open sql, db
ReDim varArray(0 To rs.RecordCount, 0 To rs.Fields.Count - 1)
' Read headings to array
For j = 0 To rs.Fields.Count - 1
varArray(0, j) = rs.Fields(j).Name
Next j
' Read data to array
For i = 1 To rs.RecordCount
For j = 0 To rs.Fields.Count - 1
varArray(i, j) = rs.Fields(j)
Next j
rs.MoveNext
Next i
rs.Close
db.Close
Set rs = Nothing
Set db = Nothing
udfREADDB = varArray
End Function
I have tried putting a breakpoint into the code. Then, when I call the udf from a cell in the spreadsheet, the code pauses as expected. I then go through each element of the code (each variable) and nothing indicates an error. I can even use the immediate pane to return elements of my array, varArray(), and it's fine. The same goes for UBound(varArray, 1) or UBound(varArray, 2).
I wondered if the error was caused by too long a path name, but wouldn't this show up as an error in VBA as well as the spreadsheet?
I created a test database containing a table with 100 rows and five fields (an integer, a double, a string, a Boolean and a datetime). The spreadsheet is populated with the data as expected: I use the formula "{=udfREADDB(A1,A2)}" with Shift+Ctrl+Enter in the cells A11:E101.
When I pasted my test database in a location with a long path I did manage to recreate the error. However, when I took a copy of a database that is stored in a location with a long path, and pasted it in C:\, my udf still returned a #VALUE! error, as it did to begin with.
I have used a Sub() in VBA to call my function and I use Debug.Print to see the size of the array that is created, but this doesn't help because no error is generated.
I'm running out of ideas as to how to troubleshoot this issue :(
The error appears in the spreadsheet because it cannot handle Nulls. The solution, then, is to catch the Nulls and replace them with something else; either vbNullString, 0, or "NULL". My preference is vbNullString. This is achieved by using the IsNull() function. VBA can handle Nulls, but Excel can't.
Function udfREADDB(connection As String, sql As String) As Variant
Dim db As ADODB.connection, rs As ADODB.Recordset, varArray() As Variant
Dim i As Integer, j As Integer ' rows & cols
Set db = New ADODB.connection
With db
.ConnectionString = connection
.ConnectionTimeout = 10
.Open
End With
' Query database
Set rs = New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open sql, db
ReDim varArray(0 To rs.RecordCount, 0 To rs.Fields.Count - 1)
' Read headings to array
For j = 0 To rs.Fields.Count - 1
varArray(0, j) = rs.Fields(j).Name
Next j
' Read data to array
For i = 1 To rs.RecordCount
For j = 0 To rs.Fields.Count - 1
If IsNull(rs.Fields(j)) Then
varArray(i, j) = vbNullString
Else
varArray(i, j) = rs.Fields(j)
End If
Next j
rs.MoveNext
Next i
rs.Close
db.Close
Set rs = Nothing
Set db = Nothing
udfREADDB = varArray
End Function

Excel VBA ADODB RecordSet changes Field types from SQL Server

I am running a simple query on a SQL Server table:
SELECT * FROM MyTable
The table contains three columns, with types int, bit, and datetime2(0). I am using code along the lines of:
Dim cnn As ADODB.Connection
Dim rst As ADODB RecordSet
Dim rngDest As Range
:
: ' etc
:
rst.Open "SELECT * FROM MyTable", cnn
rngDest.CopyFromRecordset rst
This works well, except that it seems to be getting the field types wrong: it says the field types are 3, 11, 202, which are, according to the documentation, Integer (correct), Boolean (correct, I guess), and NVarChar2 -- incorrect. The third field should be a date, surely ("Date" type is 135).
As background: I migrated the data from Access to SQL Server using SSMA. This table now has the three column types I mentioned. Also, I understand that it is a well-known problem that CopyFromRecordset can get the field types wrong, and I initially thought that was why the third column (the date) was appearing in Excel as a string, but upon closer inspection I can see that when the VBA has read the Recordset it already believes the field is text, before the CopyFromRecordset line. I feel if I could get the VBA to somehow recognise that the third field should be a date then I could convert it internally. I would rather not create a solution for this table specifically because I have many, many Tables and Views that have to be handled, so I would prefer to find an approach that works for all of them.
If it's important, the connection string I am using is:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Data Source=[REDACTED];Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Use Encryption for Data=False;Tag with column collation when possible=False;Initial Catalog=[REDACTED];
Can anyone help please?
The backwards compatible SQLOLEDB driver that ships with Windows has no notion SQL Server data types introduced over the last 20 years, like date. Try the latest SQL Server OLEDB driver, MSOLEDBSQL.
I ran a quick ADO test using the VbScript below and a date column returned ADO type 133 (adDBDate).
Set connection = CreateObject("ADODB.Connection")
connection.Open "Provider=MSOLEDBSQL;Data Source=.;Integrated Security=SSPI"
Set recordset = connection.Execute("SELECT CAST(SYSDATETIME() AS date);")
MsgBox recordset.Fields(0).Type
connection.Close
Here is the solution I have found to work (while I'm waiting for the support guys to update the driver):
[TL;DR: Read the Recordset into a variant varOrig using rst.GetRows; transpose, then plonk into an Excel sheet; using .Value2 = causes Excel to convert it to a proper date. [We have to transpose because .GetRows, despite its name, returns columns of data.]]
Hope this is useful for anyone else in the future having a similar problem.
Public Sub RecordSetToRange(ByRef rst As ADODB.Recordset, ByRef lobDst As ListObject)
Const strROUTINE As String = "RecordSetToRange"
Dim iRow As Long
Dim iCol As Long
Dim varOrig As Variant
Dim varTxp() As Variant
' -- Error Handling, Validation, Initialization.
On Error GoTo ErrHandler
' -- Procedure.
' Headers first.
With lobDst.HeaderRowRange.Cells(1)
For iCol = 0 To rst.Fields.Count - 1
.Offset(0, iCol).Value2 = rst.Fields(iCol).Name
Next iCol
End With
' Then data. Note that, despite the name of the function, GetRows returns columns of data, so we have
' to transpose it. Don't do anything if there is no data.
If rst.RecordCount > 0 Then
varOrig = rst.GetRows
ReDim varTxp(UBound(varOrig, 2), UBound(varOrig, 1))
For iRow = 0 To UBound(varOrig, 2)
For iCol = 0 To UBound(varOrig, 1)
varTxp(iRow, iCol) = varOrig(iCol, iRow)
Next iCol
Next iRow
lobDst.DataBodyRange.Resize(UBound(varOrig, 2) + 1, UBound(varOrig, 1) + 1).Value2 = varTxp
End If
'
ErrHandler: ' -- Error handling and Routine termination.
If Err.Number <> 0 Then If DspErr(mstrMODULE, strROUTINE) Then Stop: Resume Else End
End Sub
Your date field may be string. so you convert the date. The following example converts a character date to date data. You will need to specify differently depending on what your character type is.
20130604060133 -> 2013/06/04 06:01:33
select
convert(datetime,SUBSTRING( '20130604060133',1,8),112) +
convert(datetime,
( substring( '20130604060133',9,2) + ':' +
substring( '20130604060133',11,2) + ':' +
substring( '20130604060133',13,2) )
,120) as myDate
The problem seems to be somewhere else, but it's not clear. But there is a way to change the data. See below.
Dim cnn As ADODB.Connection
Dim rst As ADODB.Recordset
Dim rngDest As Range
:
: ' etc
:
rst.Open "SELECT * FROM MyTable", cnn
rngDest.CopyFromRecordset rst
Dim vDB
Dim rngDB As Range
Set rngDB = rngDest.CurrentRegion
vDB = rngDB
rngDB.NumberFormat = "General"
rngDB = vDB

EXCEL VBA to SQL Index and Seek

I am exporting data in Excel table to SQL Server Database, If exists UPDATE else INSERT.
The following VBA code works well for exporting to ACCESS Database,
BUT NOT TO SQL SERVER DATABASE TABLE.
Error Message appear :Invalid Use of Property for .Index and .Seek.
Please Help !!! Toh
Sub ExcelDataToSql ()
Dim cn As ADODB.Connection, rs As ADODB.Recordset, r As Long
Dim lastrow As Long, o As Long
Set cn = New ADODB.Connection
Set rs = New ADODB.Recordset
cn.Open "Provider=SQLNCLI11;Server=***;Database=****;Trusted_Connection=yes;"
rs.CursorLocation = adUseServer
rs.Open "InventorySQL", cn, 1, 3, adCmdTableDirect
' Get Lastrow
Worksheets("InventoryEXCEL").Select
lastrow = Worksheets("InventoryEXCEL").Cells(rows.Count, 1).End(xlUp).Row
r = 2 ' the start row in the worksheet
For o = 2 To lastrow
'Check For Duplicate In Database SQL
With rs
.Index = "PrimaryKey"
.Seek Range("A" & r).Value
If .EOF Then
.AddNew
'If No Duplicate insert New Record
rs.Fields("oPartno") = Range("A" & r).Value
rs.Fields("oDesc") = Range("B" & r).Value
rs.Fields("oCost") = Range("C" & r).Value
.update
Else
' If Duplicate Found Update Existing Record
rs.Fields("oDesc") = Range("B" & r).Value
rs.Fields("oCost") = Range("C & r).Value
.Update
End If
End With
Next o
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
MsgBox "Posting Completed"
End Sub
. Index = "PrimaryKey" --- Sysntax Error : Invalid Use of Property
.Seek Range ("A" & r).Value Sysntax Error :
Reference:Seek Method and Index Property Example (VB)
The MSDN example passes an Array as the first parameter.
rstEmployees.Seek Array(strID), adSeekFirstEQ
The first parameter's name os KeyValues which also implies an array
I would try this first
.Seek Array(Range("A" & r).Value)
It might also be beneficial to use one of the SeekEnum value
Update: TOH the OP found that this was the relavent code snippet
MSDN also suggest checking if the Provider supports .Index and .Seek
If rstEmployees.Supports(adIndex) And rstEmployees.Supports(adSeek) Then
My Problem is resolved by work around.
Many resources indicated that Sql Providers do not support the index and seek function. So I avoid Index and Seek
I work around by importing the excel worksheet into Sql server as source table... thereafter Merge the target table with source table... if match UPDATE, if Not Match INSERT.
select * from InventoryTableSQL
select * from InventoryTableFromExcel
Merge InventoryTableSQL as T
using InventoryTableFromExcel as S
on t.oPartno = s.oPartno
when matched then
update set t.oPartno = s.oPartno,
t.oDesc = s.oDesc,
t.oCost = s.oCost
when not matched by target then
insert(oPartno, oDesc, oCost) values(s.oPartno, s.oDesc, s.oCost));

Excel VBA Array not receiving all values from stored procedure recordset - different result than running stored procedure outside of Excel

I have quite a conundrum which I have been trying to troubleshoot. I have a stored procedure in a MySql database, which I call through an Excel VBA application. The VBA application passes the recordset into an Array, and then I use a For Loop to place each of the items in the Array onto a worksheet.
Here's the problem: two of the values in the recordset keep coming back blank in Excel. Oddly, the two are in the middle of the Array, not the beginning or end. However, if I call the stored procedure through another query program such as HeidiSql, I receive ALL values back. I'm at a loss as to why I'm not receiving all of the values through Excel... or why the Array isn't receiving them all, at any rate.
Thanks in advance for your help.
Here is my code:
Sub StartHereFlexFunderCust()
On Error GoTo ErrorHandler
Dim Password As String
Dim SQLStr As String
'OMIT Dim Cn statement. Cn stands for Database Connection
Dim Server_Name As String
Dim User_ID As String
Dim Database_Name As String
Dim custID As String
Dim myArray()
'OMIT Dim rs statement. rs stands for Database Recordset and is the Recordset of what is returned
Set RS = CreateObject("ADODB.Recordset")
Server_Name = Range("O10").Value
Database_Name = Range("O11").Value ' Name of database
'id user or username. We need to write code to insert the current user into this variable (Application.Username) if possible. But they may not be consistent across all machines.
'For example mine is "Ryan Willging" and we would have to shorten it to rwillging but others may be rwillging.
'This is important because if we do not do this all queries will come from the same person and that is not good for debugging.
User_ID = Range("O12").Value
Password = Range("O13").Value
custID = Range("C4").Value 'Deal Number from Start here that we are passing into the stored procedure
'This is the storedprocedure call and it passes in the value of the DealId to the Stored Procedure
SQLStr = "call flexFundByCustomer(" + custID + ")"
Set cn = CreateObject("ADODB.Connection") 'NEW STATEMENT
'This statement takes the variables from the checklist and passes them into a connection string
cn.Open "Driver={MySQL ODBC 5.1 Driver};Server=" & _
Server_Name & ";Database=" & Database_Name & _
";Uid=" & User_ID & ";Pwd=" & Password & ";"
'This statement queries the database using the SQL string and the connection string.
'The adOpenStatic variable returns a static copy of a set of records that you can use to find data or generate reports. There are other variables that
'could be used but I think this one will suffice.
RS.Open SQLStr, cn, adOpenForwardOnly
Debug.Print msg 'or MsgBox msg
'Take all of the info from the queries and put them into the spreadsheet
myArray = RS.getrows()
Dim Fld_Name As String
Dim Val_of_Field As String
Dim starthere As Worksheet
Fld_Name = UBound(myArray, 1)
Val_of_Field = UBound(myArray, 2)
Set starthere = ThisWorkbook.Sheets("Start Here")
MsgBox "No error yet defined Start Here!"
'This little loop works well to dump the recordset into excel. We can then map the correct fields 'k inputs the headers and R inputs the rows returned in the Recordset
For K = 0 To Fld_Name ' By using a For loop the data is inputed into excel one row at a time
starthere.Range("U4").Offset(0, K).Value = RS.fields(K).Name
For R = 0 To Val_of_Field
starthere.Range("U4").Offset(R + 1, K).Value = myArray(K, R)
Next
Next
RS.Close
Set RS = Nothing
cn.Close
Set cn = Nothing
ErrorHandler:
MsgBox "There's been an error!"
Exit Sub
End Sub
Consider using Range.CopyFromRecordset method to avoid any use of arrays. Or if memory does not allow, use a Do While Loop across Recordset columns:
' COLUMN HEADERS
For i = 1 To RS.Fields.Count
starthere.("Results").Range("U4").Offset(0, i) = RS.Fields(i - 1).Name
Next i
' DATA ROWS
' COPYFROMRECORDSET APPROACH
starthere.Range("U5").CopyFromRecordset RS
' DO WHILE LOOP APPROACH
starthere.Activate
starthere.Range("U5").Activate
row = 5
Do While Not RS.EOF
For i = 0 To RS.Fields.Count - 1
ActiveCell.Offset(0, i) = RS.Fields(i)
Next i
row = row + 1
ActiveCell.Offset(row, 21)
RS.MoveNext
Loop
As for the values returning empty that may be a MySQL and Excel incompatibility of data types. For instance, you may have a table field set to MySQL's maximum decimal (65, 30) which denotes max digits of 65 and max 30 decimal points which cannot be reflected on a spreadsheet. Current precision limit of a cell value is 15 decimal points.
Alternatively, you may have a VARCHAR(65535) which is the 65,535 byte limit or the open-ended TEXT column of no limit that also cannot be displayed on spreadsheet. Current limit of characters in one cell is 32,767.
Try modifiying column to a smaller type:
ALTER TABLE `tableName` MODIFY COLUMN `largenumberfield` DECIMAL(10,7);
ALTER TABLE `tableName` MODIFY COLUMN `largetextfield` VARCHAR(255);
Why the other programs such as HeidiSQL retrieve values? It might be due to their internal conversion features forcing data values into a specific format (i.e., removing whitespaces, truncating values) which then renders adequately in Excel.

Resources