Can I get data from power query to a vba variable? - vba

I have a power query function, e.g. getData with one parameter filename
Can I invoke that function directly in a vba variable? Something like:
Sub MyTest()
Dim MyVar
MyVar = ThisWorkbook.Queries("getData").Invoke("mytable.xls")
MsgBox "The Value is " & MyVar
End Sub
I use Excel 2016

Unfortunately, you cannot load the Power Query result to a VBA variable. You have to first load it to the sheet.
Here is a thread, where a Microsoft employee confirms that:
Link to Microsoft Employee relating to that issue

Contrary to the link in the accepted answer, it is possible to get an array from a query if you load the query to the data model. It can be a pain, and there are lots of pitfalls (spaces in table names, and I haven't tested where the query output is a list or otherwise not a table).
These functions aren't cleaned up, but they have given me success, and show where to poke around.
Function GetModelADOConnection()
'We just need the ADOConnection; the rest is for perusal
Set wbConnections = ThisWorkbook.Connections
Set Model = ThisWorkbook.Model
Set ModelDMC = Model.DataModelConnection
Set ModelDMCMC = ModelDMC.ModelConnection
Set GetModelADOConnection = ModelDMCMC.ADOConnection
End Function
Sub ListConnectionTables()
'Run this to dump a list of available tables in the immediate window, so you can see what you'll need to query
'https://learn.microsoft.com/en-us/office/client-developer/access/desktop-database-reference/schemaenum
Set conn = GetModelADOConnection
Set TablesSchema = conn.OpenSchema(20)
Debug.Print "TABLE_SCHEMA", "TABLE_NAME", "COLUMN_NAME" 'headers for immediate window
Do While Not TablesSchema.EOF
Set ColumnsSchema = conn.OpenSchema(4, Array(Empty, Empty, "" & TablesSchema!TABLE_NAME))
Do While Not ColumnsSchema.EOF
If TablesSchema!TABLE_SCHEMA <> "$SYSTEM" Then
Debug.Print TablesSchema!TABLE_SCHEMA, TablesSchema!TABLE_NAME, ColumnsSchema!COLUMN_NAME
End If
ColumnsSchema.MoveNext
Loop
TablesSchema.MoveNext
Loop
End Sub
Function GetRecordSetFromConnection(TABLE_NAME)
'Requires that connection is added to data model.
'Watch out for table names with spaces in them - would need additional handling
'Use the ListConnectionTables function provided above to try to sniff out what to use for the TABLE_NAME, and additional trial and error may be needed
Set conn = GetModelADOConnection
Set rs = CreateObject("ADODB.RecordSet")
rs.Open "SELECT * From $" & TABLE_NAME & ".$" & TABLE_NAME, conn
Set GetRecordSetFromConnection = rs
End Function
If you succeed in getting your query into a recordset, then hopefully you know where to go from there. The simplest way to turn that into an array is with myRecordSet.GetRows(). That gives a transposed version of the table, but for help from there it will just require some Googling.

Related

Runtime error 3164 for Access when copying [duplicate]

I am having a difficult time how to properly copy specific field data from previous records on my user form. I don't have a code sample to show but my request is very simplistic.
Currently, out of 12 fields, I have 6 that I often repeat data. I can click on and press Ctrl+' ("Insert the value from the same field in the previous record") and it performs the task I want. However, it adds a lot of time to the task. I simply want to write VBA code to perform that command to those specific fields.
I haven't been able to get SendKeys to work. DLast appears to provide random data at times. I feel like this should be a very simple request but for some reason I am not finding a functional solution for it.
Don't fiddle with arrays or queries - use the power of DAO:
Private Sub CopyButton_Click()
CopyRecord
End Sub
If a record is selected, copy this.
If a new record is selected, copy the last (previous) record.
Private Sub CopyRecord()
Dim Source As DAO.Recordset
Dim Insert As DAO.Recordset
Dim Field As DAO.Field
' Live recordset.
Set Insert = Me.RecordsetClone
' Source recordset.
Set Source = Insert.Clone
If Me.NewRecord Then
' Copy the last record.
Source.MoveLast
Else
' Copy the current record.
Source.Bookmark = Me.Bookmark
End If
Insert.AddNew
For Each Field In Source.Fields
With Field
If .Attributes And dbAutoIncrField Then
' Skip Autonumber or GUID field.
Else
Select Case .Name
' List names of fields to copy.
Case "FirstField", "AnotherField", "YetAField" ' etc.
' Copy field content.
Insert.Fields(.Name).Value = Source.Fields(.Name).Value
End Select
End If
End With
Next
Insert.Update
Insert.Close
Source.Close
End Sub
This also, by the way, is an excellent example of the difference between the RecordsetClone and the Clone of a recordset - the first being "the records of the form", while the second is an independant copy.
This also means, that the form will update automatically and immediately.
Provided that it's a simple form to edit a simple table, and that the bound data field names match the control names, you may get away with
If Me.Recordset.AbsolutePosition > 0 Then
With Me.Recordset.Clone()
.AbsolutePosition = Me.Recordset.AbsolutePosition - 1
Dim control_name As Variant
For Each control_name In Array("field1", "field2", "field3", "field4", "field5", "field6")
Me.Controls(control_name).Value = .Fields(control_name).Value
Next
End With
End If
which you assign to a separate button on the same form.
You have a good idea post here already.
You could also say place a function in the before insert event. This event ONLY fires when you start typing into a NEW reocrd, and it becomes dirty.
So, maybe this:
Private Sub Form_BeforeInsert(Cancel As Integer)
Dim rstPrevious As DAO.Recordset
Dim strSQL As String
strSQL = "SELECT TOP 1 * FROM tblPeople ORDER BY ID DESC"
Set rstPrevious = CurrentDb.OpenRecordset(strSQL)
' auto file out some previous values
If rstPrevious.RecordCount > 0 Then
Me.Firstname = rstPrevious!Firstname
Me.LastName = rstPrevious!LastName
End If
End Sub
And some good ideas in say having a "list" or "array" of controls/fields to setup, so you don't have to write a lot of code. (as suggested in the other post/answer here)

MS Access VBA equivalent to Ctrl+'?

I am having a difficult time how to properly copy specific field data from previous records on my user form. I don't have a code sample to show but my request is very simplistic.
Currently, out of 12 fields, I have 6 that I often repeat data. I can click on and press Ctrl+' ("Insert the value from the same field in the previous record") and it performs the task I want. However, it adds a lot of time to the task. I simply want to write VBA code to perform that command to those specific fields.
I haven't been able to get SendKeys to work. DLast appears to provide random data at times. I feel like this should be a very simple request but for some reason I am not finding a functional solution for it.
Don't fiddle with arrays or queries - use the power of DAO:
Private Sub CopyButton_Click()
CopyRecord
End Sub
If a record is selected, copy this.
If a new record is selected, copy the last (previous) record.
Private Sub CopyRecord()
Dim Source As DAO.Recordset
Dim Insert As DAO.Recordset
Dim Field As DAO.Field
' Live recordset.
Set Insert = Me.RecordsetClone
' Source recordset.
Set Source = Insert.Clone
If Me.NewRecord Then
' Copy the last record.
Source.MoveLast
Else
' Copy the current record.
Source.Bookmark = Me.Bookmark
End If
Insert.AddNew
For Each Field In Source.Fields
With Field
If .Attributes And dbAutoIncrField Then
' Skip Autonumber or GUID field.
Else
Select Case .Name
' List names of fields to copy.
Case "FirstField", "AnotherField", "YetAField" ' etc.
' Copy field content.
Insert.Fields(.Name).Value = Source.Fields(.Name).Value
End Select
End If
End With
Next
Insert.Update
Insert.Close
Source.Close
End Sub
This also, by the way, is an excellent example of the difference between the RecordsetClone and the Clone of a recordset - the first being "the records of the form", while the second is an independant copy.
This also means, that the form will update automatically and immediately.
Provided that it's a simple form to edit a simple table, and that the bound data field names match the control names, you may get away with
If Me.Recordset.AbsolutePosition > 0 Then
With Me.Recordset.Clone()
.AbsolutePosition = Me.Recordset.AbsolutePosition - 1
Dim control_name As Variant
For Each control_name In Array("field1", "field2", "field3", "field4", "field5", "field6")
Me.Controls(control_name).Value = .Fields(control_name).Value
Next
End With
End If
which you assign to a separate button on the same form.
You have a good idea post here already.
You could also say place a function in the before insert event. This event ONLY fires when you start typing into a NEW reocrd, and it becomes dirty.
So, maybe this:
Private Sub Form_BeforeInsert(Cancel As Integer)
Dim rstPrevious As DAO.Recordset
Dim strSQL As String
strSQL = "SELECT TOP 1 * FROM tblPeople ORDER BY ID DESC"
Set rstPrevious = CurrentDb.OpenRecordset(strSQL)
' auto file out some previous values
If rstPrevious.RecordCount > 0 Then
Me.Firstname = rstPrevious!Firstname
Me.LastName = rstPrevious!LastName
End If
End Sub
And some good ideas in say having a "list" or "array" of controls/fields to setup, so you don't have to write a lot of code. (as suggested in the other post/answer here)

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).

Excel VBA: PivotTable CubeField Fact Table Name?

I'm working with a large OLAP Cube, and I'm building very rudimentary search functionality to help me parse through the amount of data (~30 fact tables, 30 dimensions, plus calculated measures), but I've come across an interesting issue. When I access a CubeField.Name property for a fact table member or a calculated measure, it comes through as [Measures].[FieldName] instead of pointing me to the correct Fact table / calculated measure folder.
Here's the code I'm using:
Sub X()
Dim pvtTable As PivotTable
Dim oCubeField As CubeField
Set pvtTable = ActiveSheet.PivotTables(1)
For Each oCubeField In pvtTable.CubeFields
If InStr(LCase(oCubeField.Name), "spread") > 0 Then
Debug.Print oCubeField.Parent & ": " & oCubeField.Name
End If
Next
End Sub
Any ideas on how I can actually reach the fact table name? Or the calculated measure's folder structure? Thanks!
Try oCubeField.Value perhaps. The reference for the CubeField object says:
Name Returns a String value that represents the name of the object.
Value Returns a String value that represents the name of the specified field.
Unfortunately, you can't do that, because a measure expression may link to a few fact tables.
But you can query and analyse cube metadata with OpenSchema() function.
Something like this:
Sub GetMeasuresList()
Const adSchemaMeasures = 36
Set ADOCon = ActiveCell.PivotTable.PivotCache.ADOConnection
Set RecSet = ADOCon.OpenSchema(adSchemaMeasures)
While Not RecSet.EOF
Debug.Print RecSet!MEASURE_NAME & ": " & RecSet!EXPRESSION
RecSet.MoveNext
Wend
End Sub
OLE DB for OLAP Schema Rowsets
How To Use the ADO OpenSchema Method in Visual Basic

VBA to Trim all Cells in an Access Table

I'm relatively experienced with Object oriented programming, but this is my first time ever working in Office with VBA and I'm entirely stumped by the syntax. I've been doing some searching and messing with it for the past hour or so, but have been trouble actually getting a macro that runs successfully and does what I need.
I'm attempting to loop through every cell in an Access table and apply the Trim function to the contents of that cell and, as a bonus, I'd like to remove all extra spaces in the string (if any). I.e. " Trim this__string " would simply become "Trim this string" (I used the underscore there to represent individual, multiple spaces since StackOverflow didn't want to show my multiple spaces).
Any code example of doing something like this, or at least something to get me close and then I can tinker with it, would be greatly appreciated. Thanks!
You can remove leading and trailing spaces with the Trim() function in a query.
UPDATE YourTable
SET text_field = Trim(text_field);
If you will be doing this from within an Access session, you could use Replace() to replace a sequence of two spaces with a single space.
UPDATE YourTable
SET text_field = Replace(text_field, ' ', ' ');
However you may need to run that Replace() query more than once to get all the contiguous space characters down to only one.
You could also do a regular expression-based replacement with a user-defined function. I don't know if that's worth the effort, though. And a user-defined function is also only available from within an Access application session.
I overlooked the "every cell in a table" aspect. That makes this more challenging and I don't think you can solve it with a standard macro or query. You can however use VBA code to examine the TableDef, and iterate through its fields ... then call your Trim and/or Replace operations on any of those fields whose data type is text or memo.
Here's a rough code outline to identify which fields of a given table are text type.
Public Sub FindTextFields(ByVal WhichTable As String)
Dim db As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Set db = CurrentDb
Set tdf = db.TableDefs(WhichTable)
For Each fld In tdf.Fields
If fld.Type = dbText Or fld.Type = dbMemo Then
Debug.Print "Do something with " & fld.Name
End If
Next
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
End Sub
Option Compare Database
Private Sub Command3_Click()
Call findField(Text1.Value)
End Sub
Public Function findField(p_myFieldName)
Dim db As Database, _
tb As TableDef, _
fd As Field
'''''''''Clearing the contents of the table
DoCmd.RunSQL "delete * from Field_Match_Found"
Set db = CurrentDb
For Each tb In db.TableDefs
For Each fd In tb.Fields
If fd.Name = p_myFieldName Then
strsql = "INSERT INTO Field_Match_Found Values (""" & tb.Name & """, """ & fd.Name & """)"
DoCmd.RunSQL strsql
End If
Next fd
Next tb
Set fd = Nothing
Set tb = Nothing
Set db = Nothing
If DCount("Account_number", "Field_Match_Found") = 0 Then
MsgBox ("No match was found")
Else
MsgBox ("Check Table Field_Match_Found for your output")
''''''''''making textbox blank for next time
Text1.Value = ""
End Function