Transposing CopyFromRecordset Excel VBA - vba

I have the following code in my Excel VBA that copies data from a table in SQL into Excel. This data is being inserted horizontally starting on cell C2, but I want it to be inserted vertically on column C.
Sheets("Control").Range("C2").CopyFromRecorset rsPubs
Where rsPubs is my ADO connection.
Basically, I just want this data transposed. What's an efficient way of doing this?
This is how rsPubs is created (the connection works fine as I'm actually getting the data):
' Create a recordset object.
Dim rsPubs As ADODB.Recordset
Set rsPubs = New ADODB.Recordset
With rsPubs
' Assign the Connection object.
.ActiveConnection = cnPubs
' Extract the required records.
.Open "SELECT * FROM Analytics.dbo.XBodoffFinalAllocation"
' Copy the records into cell B3 on Sheet1.
Sheets("Control").Range("C2").CopyFromRecordset rsPubs
' Tidy up
.Close
End With
cnPubs.Close
Set rsPubs = Nothing
Set cnPubs = Nothing

I cannot test this currently, but you could:
Sheets("Control").Range("C2").CopyFromRecorset rsPubs 'copy your data
Sheets("Control").Range("C2").Copy 'copy the data into clipboard
Sheets("Control").Range("C2").PasteSpecial xlPasteValues, xlPasteSpecialOperationNone, True, True
Also you could use the Transpose Worksheetfunction - however, I don't quite see a way right now to do this directly, expect your input data is transposed already.
Here is a nice official example and further informations on this topic: How to transfer data from an ADO Recordset to Excel with automation
Especially the "using GetRows" section.
This should do:
Dim resultset As Variant
Dim result As Variant
resultset = rsPubs.GetRows
result = Application.WorksheetFunction.Transpose(resultset)
Sheets("Control").Range("C2").Resize(UBound(result, 1), UBound(result, 2)) = result
http://www.teachexcel.com/excel-help/excel-how-to.php?i=147811

Edit 2 in the accepted answer doesn't work for me, but the following does (see http://www.mrexcel.com/forum/excel-questions/513845-copyfromrecordset-transpose.html for my source):
Public Sub PlaceTransposedResults(oResults As ADODB.Recordset, rTarget As Range)
Dim vTransposed As Variant
If Not oResults.EOF Then
vTransposed = oResults.GetRows
rTarget.Resize(UBound(vTransposed, 1) + 1, UBound(vTransposed, 2) + 1) = vTransposed
End If
End Sub
(this assummes that you haven't changed the array base with the OPTION BASE and that your version of Excel has Range.Resize and that oResults is never nothing)
One tweak on this is to make this a function and return the correctly sized range - useful if you want to resize a named range to cover the result set.
Another likely tweak is that you may want to optionally allow the user to ask for the field names to be added as the in the first column. I have found nothing better than:
Dim ix As Integer
For ix = 0 To oResults.Fields.Count - 1
rTarget.Offset(ix, 0) = oResults.Fields(ix).Name
Next ix
(of course you then have to offset your main results by 1 column in this case).

Untested:
Sub CopyTransposed(rng As Range, rs As ADODB.Recordset)
Dim x As Long, y As Long
x = 0
Do While Not rs.EOF
For y = 0 To rs.Fields.Count - 1
rng.Offset(y, x).Value = rs.Fields(y).Value
Next y
x = x + 1
rs.MoveNext
Loop
End Sub

This is really simple. It uses Excel's built-in transpose. Can be transposed in a single statement. No looping is needed. Tested and validated.
Dim vRecs, vRecsX
vRecs = rsPubs.GetRows
vRecsX = Application.Transpose(vRecs)
Sheets("Control").Range("C2").Resize(rsPubs.RecordCount, rsPubs.Fields.Count) = vRecsX
This code is copied directly from a working example.
Note, since this is a worksheet function, expect it to work with arrays of size only up to the number of columns on a worksheet. I believe columns-per-sheet varies between different versions of Excel, so it's a safe bet to determine your transpose limit based on your version of Excel.
"In the modern versions of Excel this is column XFD, which is 16,384 columns. In older versions of Excel (2003 and prior) the last column was IV which is 256 columns."
https://officemastery.com/_how-many-rows-and-columns-in-excel/

My suggestion is not to use VBA, at all. Microsoft already provided you the facility to import data from a database.
Data -> Import External Data
It will, then, create a QueryTable inside the sheet where you can just right-click and refresh in a regular basis. Another bonus is that you don't get the nasty macro warning. The QueryTable can be a table, query or stored procedure.
Try it!

Related

Using VBA and SQL, can I refer to an excel column header in a query?

I am writing a SQL query that checks excel values against a database. While running my Excel macro, I create worksheet 2 (ws2) and need to run a query which checks if each of the values in column F = table.number.
I know I can use Cells to get a single value and wrap it in a for loop but that takes up too much processing and requires too many SQL extracts. The column in ws2 is called "REFERENCE" and has all the data below it. Ideally, I would like to write the SQL query like:
select * from table where ws2.REFERENCE = table.number
Is there a way to do this?
I suppose you can do the comparison in either Excel or in SQL Server, right. I think one viable solution is to import data from SQ Server, including field names, and do compare this to what you already have in Excel.
Sub Conn2SQL()
Dim cnn1 As New ADODB.Connection
Dim mrs As New ADODB.Recordset
Dim iCols As Integer
Set cnn1 = New ADODB.Connection
cnn1.ConnectionString = "driver={SQL Server};server=MyDBServer;uid=MyuserID;pwd=mypassword;database=MyDB"
cnn1.ConnectionTimeout = 30
cnn1.Open
SQry = "use MyDB select * from TableName"
mrs.Open SQry, cnn1
For iCols = 0 To mrs.Fields.Count - 1
Worksheets("Sheet2").Cells(1, iCols + 1).Value = mrs.Fields(iCols).Name
Next
Sheet2.Range("A2").CopyFromRecordset mrs
mrs.Close
cnn1.Close
End Sub

vba store recordset as integer variable

First time poster, I finally had a question that I couldn't find an answer to here.
I have an MS Access query that returns 1 result (which is a number) that I want to store as an integer variable (x) so I can use it later for a loop. The issue is that because I'm using it as a recordset and the variable is an integer, I'm receiving a "Type Mismatch" error.
Right now I'm just storing the result to a cell and setting the variable equal to the cell:
Ws.Range("A1") = Db.OpenRecordset("SELECT COUNT(Asset_Name) FROM Assets WHERE Active = True").GetRows(1)
x = Ws.Range("A1")
Ws.Range("A1").Delete
And then later I just have a loop that runs x times:
For i = 0 To x
Basically, I just want to have some code that looks like this:
x = Db.OpenRecordset("SELECT COUNT(Asset_Name) FROM Assets WHERE Active = True").GetRows(1)
Any help here would be huge. Thank you!
The following should give you the correct result:
Dim x As Integer
Dim db As DAO.Recordset
db.MoveFirst
If IsNumeric(db.OpenRecordset("SELECT COUNT(Asset_Name) FROM Assets WHERE Active = True").Fields(0).Value) Then
x = CInt(db.OpenRecordset("SELECT COUNT(Asset_Name) FROM Assets WHERE Active = True").Fields(0).Value)
Else
MsgBox "The query did not return a number." & Chr(10) & "Aborting..."
End If
Note, that you are using DAO and not ADO as your original tags on the post indicated. Still, they both behave rather similar and the cursor is normally on the first row (when the data is returned). So, MoveFirst should not be necessary. Still, Microsoft themselves keep using it in its own sample code all the time. The first column if for DAO and ADO alike .Fields(0).

Insert 10000+ rows in an Excel sheet

I am creating an Excel macro while pulls data from an Excel sheet (containing more than 10 thousand rows) and populates another Excel sheet.
I have to insert data row-wise since for every row I need to fetch data from 2-3 different sheets based on a column value (say, EMP_ID).
e.g. Excel file - 1 has
Emp_ID | Emp_Name | Age
Now based on each employee ID I need to fetch employee related data from 3 other Excel sheets. So I have to loop through 10k records.
When I execute the code the Excel application just hangs. I think this is because I try to insert data row-wise.
Can someone suggest a faster way to insert/update large number of rows.
I have already tried using Variant/Array to store data and then populate the sheets. But it still doesn't seem to work.
NOTE: I am reading records from the Source file into a RecordSet.
I have already added:
Application.DisplayAlerts = False,
Application.ScreenUpdating = False,
Application.Calculation = xlCalculationManual
And then set it back to default.
I know this is not a direct answer, but sometimes it's better to teach how the work has to be done, intead of simply reply.
Your work needs to be done with Access (or any other dbms). You have to define three tables, each indexed by Emp_ID, and all the staff become simple.
I tend to agree with Sergio.
If using a database is totally not an option, using an array is the way to go.
I have already tried using Variant/Array to store data and then populate the sheets. But it still doesn't seem to work.
Can you show the code you tried?
This works for me:
Dim arData() As Variant
' ... calculate number of rows and columns ...
ReDim arData(1 To numRows, 1 To numCols)
' ... populate arData ...
' Define range with identical dimensions as arData, e.g. insert in second row
Set rng = sh.Range(sh.Cells(2, 1), sh.Cells(numRows + 1, numCols))
' Transfer array to range (this is fast!)
rng.Value = arData
Also agree, Excel isn't really the tool for this.
If you're stuck with it then try the following:
Read all lookup sheets just once into collections using class objects as your data structures. For example, create a class called Employee and add the appropriate properties.
Public ID As Long
Public Age As Integer
Public Name As String
To read them, you'd code it like this...
Private mEmployeeCol As Collection
Dim ws As Worksheet
Dim empData As Employee
Dim v As Variant
Dim r As Long
Set ws = ThisWorkbook.Worksheets("employee stuff")
v = ws.UsedRange.Value2
Set mEmployeeCol = New Collection
For r = LBound(v, 1) To UBound(v, 1)
Set empData = New Employee
empData.ID = v(r, 1)
empData.Name = v(r, 2)
empData.Age = v(r, 3)
mEmployeeCol.Add empData, Key:=CStr(empData.ID)
Next
To look up the values, do it so ...
Set empData = mEmployeeCol(CStr(ID))
v(r, [your col]) = empData.ID
Then, definitely, DEFINITELY populate the final sheet with an array of variants. It's pretty straight forward ...
Dim v(1 To 10000, 1 To 50) As Variant
ws.Range("A1").Resize(UBound(v, 1), UBound(v, 2)).Value = v

Add new row to excel Table (VBA)

I have an excel which serves to record the food you ingest for a specific day and meal. I hav a grid in which each line represent a food you ate, how much sugar it has, etc.
Then i've added an save button to save all the data to a table in another sheet.
This is what i have tried
Public Sub addDataToTable(ByVal strTableName As String, ByRef arrData As Variant)
Dim lLastRow As Long
Dim iHeader As Integer
Dim iCount As Integer
With Worksheets(4).ListObjects(strTableName)
'find the last row of the list
lLastRow = Worksheets(4).ListObjects(strTableName).ListRows.Count
'shift from an extra row if list has header
If .Sort.Header = xlYes Then
iHeader = 1
Else
iHeader = 0
End If
End With
'Cycle the array to add each value
For iCount = LBound(arrData) To UBound(arrData)
**Worksheets(4).Cells(lLastRow + 1, iCount).Value = arrData(iCount)**
Next iCount
End Sub
but i keep getting the same error on the highlighted line:
Application-defined or object-defined error
What i am doing wrong?
Thanks in advance!
You don't say which version of Excel you are using. This is written for 2007/2010 (a different apprach is required for Excel 2003 )
You also don't say how you are calling addDataToTable and what you are passing into arrData.
I'm guessing you are passing a 0 based array. If this is the case (and the Table starts in Column A) then iCount will count from 0 and .Cells(lLastRow + 1, iCount) will try to reference column 0 which is invalid.
You are also not taking advantage of the ListObject. Your code assumes the ListObject1 is located starting at row 1. If this is not the case your code will place the data in the wrong row.
Here's an alternative that utilised the ListObject
Sub MyAdd(ByVal strTableName As String, ByRef arrData As Variant)
Dim Tbl As ListObject
Dim NewRow As ListRow
' Based on OP
' Set Tbl = Worksheets(4).ListObjects(strTableName)
' Or better, get list on any sheet in workbook
Set Tbl = Range(strTableName).ListObject
Set NewRow = Tbl.ListRows.Add(AlwaysInsert:=True)
' Handle Arrays and Ranges
If TypeName(arrData) = "Range" Then
NewRow.Range = arrData.Value
Else
NewRow.Range = arrData
End If
End Sub
Can be called in a variety of ways:
Sub zx()
' Pass a variant array copied from a range
MyAdd "MyTable", [G1:J1].Value
' Pass a range
MyAdd "MyTable", [G1:J1]
' Pass an array
MyAdd "MyTable", Array(1, 2, 3, 4)
End Sub
Tbl.ListRows.Add doesn't work for me and I believe lot others are facing the same problem. I use the following workaround:
'First check if the last row is empty; if not, add a row
If table.ListRows.count > 0 Then
Set lastRow = table.ListRows(table.ListRows.count).Range
For col = 1 To lastRow.Columns.count
If Trim(CStr(lastRow.Cells(1, col).Value)) <> "" Then
lastRow.Cells(1, col).EntireRow.Insert
'Cut last row and paste to second last
lastRow.Cut Destination:=table.ListRows(table.ListRows.count - 1).Range
Exit For
End If
Next col
End If
'Populate last row with the form data
Set lastRow = table.ListRows(table.ListRows.count).Range
Range("E7:E10").Copy
lastRow.PasteSpecial Transpose:=True
Range("E7").Select
Application.CutCopyMode = False
Hope it helps someone out there.
I had the same error message and after lots of trial and error found out that it was caused by an advanced filter which was set on the ListObject.
After clearing the advanced filter .listrows.add worked fine again.
To clear the filter I use this - no idea how one could clear the filter only for the specific listobject instead of the complete worksheet.
Worksheets("mysheet").ShowAllData
I actually just found that if you want to add multiple rows below the selection in your table
Selection.ListObject.ListRows.Add AlwaysInsert:=True works really well. I just duplicated the code five times to add five rows to my table
I had the same problem before and i fixed it by creating the same table in a new sheet and deleting all the name ranges associated to the table, i believe whene you're using listobjects you're not alowed to have name ranges contained within your table hope that helps thanks
Ran into this issue today (Excel crashes on adding rows using .ListRows.Add).
After reading this post and checking my table, I realized the calculations of the formula's in some of the cells in the row depend on a value in other cells.
In my case of cells in a higher column AND even cells with a formula!
The solution was to fill the new added row from back to front, so calculations would not go wrong.
Excel normally can deal with formula's in different cells, but it seems adding a row in a table kicks of a recalculation in order of the columns (A,B,C,etc..).
Hope this helps clearing issues with .ListRows.Add
As using ListRow.Add can be a huge bottle neck, we should only use it if it can’t be avoided.
If performance is important to you, use this function here to resize the table, which is quite faster than adding rows the recommended way.
Be aware that this will overwrite data below your table if there is any!
This function is based on the accepted answer of Chris Neilsen
Public Sub AddRowToTable(ByRef tableName As String, ByRef data As Variant)
Dim tableLO As ListObject
Dim tableRange As Range
Dim newRow As Range
Set tableLO = Range(tableName).ListObject
tableLO.AutoFilter.ShowAllData
If (tableLO.ListRows.Count = 0) Then
Set newRow = tableLO.ListRows.Add(AlwaysInsert:=True).Range
Else
Set tableRange = tableLO.Range
tableLO.Resize tableRange.Resize(tableRange.Rows.Count + 1, tableRange.Columns.Count)
Set newRow = tableLO.ListRows(tableLO.ListRows.Count).Range
End If
If TypeName(data) = "Range" Then
newRow = data.Value
Else
newRow = data
End If
End Sub
Just delete the table and create a new table with a different name. Also Don't delete entire row for that table. It seems when entire row containing table row is delete it damages the DataBodyRange is damaged

How to return a range of cells in VBA without using a loop?

let's say I have a excel spread sheet like below:
col1 col2
------------
dog1 dog
dog2 dog
dog3 dog
dog4 dog
cat1 cat
cat2 cat
cat3 cat
I want to return a range of cells (dog1,dog2,dog3,dog4) or (cat1,cat2,cat3) based on either "dog" or "cat"
I know I can do a loop to check one by one, but is there any other method in VBA so I can "filter" the result in one shot?
maybe the Range.Find(XXX) can help, but I only see examples for just one cell not a range of cells.
Please advice
Regards
Here are some notes on using a recordset to return the range.
Sub GetRange()
Dim cn As Object
Dim rs As Object
Dim strcn, strFile, strPos1, strPos2
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
strFile = ActiveWorkbook.FullName
strcn = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" _
& strFile & ";Extended Properties='Excel 8.0;HDR=Yes;IMEX=1';"
cn.Open strcn
rs.Open "SELECT * FROM [Sheet1$]", cn, 3 'adOpenStatic'
rs.Find "Col2='cat'"
strPos1 = rs.AbsolutePosition + 1
rs.MoveLast
If Trim(rs!Col2 & "") <> "cat" Then
rs.Find "Col2='cat'", , -1 'adSearchBackward'
strPos2 = rs.AbsolutePosition + 1
Else
strPos2 = rs.AbsolutePosition + 1
End If
Range("A" & strPos1, "B" & strPos2).Select
End Sub
This guy has a nice FindAll function:
http://www.cpearson.com/excel/findall.aspx
Forgot another XL2007 feature: advanced filtering. If you want it in VBA, I got this from a recorded macro:
Range("A1:A1000000").AdvancedFilter Action:=xlFilterCopy, CopyToRange:= Range("F1"), Unique:=True
I timed it at about 0.35 sec...
Admittedly, not much use if you don't have 2007.
Thanks DJ.
That FindAll solution still uses a VBA loop to do things.
I'm trying to find a way without using user level loop to filter a range in excel VBA.
Here I found a solution. it takes advantage of excel built-in engine to do the job.
(1) use
worksheetfunction.CountIf(,"Cat") to get the count of "cat" cells
(2) use .Find("cat") to get the first row of "cat"
with the count of rows and the first row, I can get the "cat" range already.
The good part of this solution is: no user-level loop, this might improve the performance if the range is big.
Excel supports the ODBC protocol. I know that you can connect to an Excel spreadsheet from an Access database and query it. I haven't done it, but perhaps there is a way to query the spreadsheet using ODBC from inside Excel.
Unless you're using a veeeery old machine, or you have an XL2007 worksheet with a bazillion rows, a loop is going to be fast enough. Honest!
Don't trust me? Look at this. I filled a million-row range with random letters using this:
=CHAR(RANDBETWEEN(65,90))
Then I wrote this function and called it from a 26-cell range using Control-Shift-Enter:
=TRANSPOSE(UniqueChars(A1:A1000000))
Here's the not-very-optimised VBA function I hacked out in a couple of minutes:
Option Explicit
Public Function UniqueChars(rng As Range)
Dim dict As New Dictionary
Dim vals
Dim row As Long
Dim started As Single
started = Timer
vals = rng.Value2
For row = LBound(vals, 1) To UBound(vals, 1)
If dict.Exists(vals(row, 1)) Then
Else
dict.Add vals(row, 1), vals(row, 1)
End If
Next
UniqueChars = dict.Items
Debug.Print Timer - started
End Function
On my year-old Core 2 Duo T7300 (2GHz) laptop it took 0.58 sec.