So I have this SQL Query
SELECT *
FROM [Employee To Manager]
WHERE [Employee To Manager].[Manager UID] In(getMyTeamUserNames());
Which has a VBA function getMyTeamUserNames()
Public Function getMyTeamUserNames() As String
Dim rs As DAO.Recordset
Dim dbs As DAO.Database
Set dbs = CurrentDb
getMyTeamUserNames = commaDelimitArray(getTeamUserNames(getUserName, dbs))
End Function
Public Function commaDelimitArray(arrayStr) As String
Dim sepStr As String
sepStr = "','"
commaDelimitArray = "'" & Join(arrayStr, sepStr)
End Function
Public Function getTeamUserNames(username, dbs) As String()
Dim sqlstatement As String
sqlstatement = "SELECT * FROM [Employee to Manager] WHERE [Employee to
Manager].[Manager UID] = '" & username & "'"
Set rs = dbs.OpenRecordset(sqlstatement, dbOpenSnapshot)
Dim ComputerUsernames() As String
Dim FindRecordCount As Integer
If rs.EOF Then
FindRecordCount = 0
Exit Function
Else
rs.MoveLast
FindRecordCount = rs.RecordCount
End If
ReDim ComputerUsernames(FindRecordCount) As String
Dim i As Integer
i = 0
rs.MoveFirst
Do Until rs.EOF = True
ComputerUsernames(i) = rs("Computer Username")
If (ComputerUsernames(i) <> "") Then
i = i + 1
End If
If (ComputerUsernames(i - 1) <> username) Then
Dim recurResult() As String
recurResult = getTeamUserNames(ComputerUsernames(i - 1), dbs)
Dim resultSize As Integer
If Len(Join(recurResult)) > 0 Then
resultSize = UBound(recurResult) - LBound(recurResult) + 1
ReDim Preserve ComputerUsernames(UBound(ComputerUsernames) + resultSize)
For Each resultStr In recurResult
ComputerUsernames(i) = resultStr
If (ComputerUsernames(i) <> "") Then
i = i + 1
End If
Next resultStr
End If
End If
rs.MoveNext
Loop
ReDim Preserve ComputerUsernames(i - 1)
getTeamUserNames = ComputerUsernames
End Function
Query runs and I get no data.
However if I take the result from getMyTeamUserNames() and put it in the query by hand it works. getMyTeamUserNames() result varies from possibly 2 results to 40 (recursively gets subordinates all the way down the tree).
So a C Perkins specifically pointed out this would never work so I have rebuilt the query with some other queries.
Related
I have a list of account ID in column A. The range of that column is dynamic. How do I write a module that will take those values and use them in an SQL IN statement. Below is my attempt at doing this. I pieced together multiple scripts I found so sorry if it is a mess.
Sub ConnectSqlServer()
Dim conn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim sConnString As String
Dim wbBook As Workbook
Dim wsSheet As Worksheet
Dim lastrow As Long
Dim sl As Long
With wsSheet
lastrow = .Range("A" & .Rows.Count).End(xlUp).Row
End With
' Appending the values to a single variable
For i = 1 To lastrow
s1 = s1 & "'" & Val(wsSheet.Cells(i, 1)) & "'" & ","
Next
' Variable which could be used in IN command
If lastrow > 0 Then
s1 = Mid(s1, 1, Len(s1) - 1)
s1 = "(" & s1 & ")"
Else
Exit Sub
End If
' ' Create the connection string.
sConnString = "Driver={ODBC Driver 13 for SQL Server}; Server=snapshot;" & _
"Database=salesforce_replica;" & _
"Trusted_Connection=yes;"
' Create the Connection and Recordset objects.
Set conn = New ADODB.Connection
Set rs = New ADODB.Recordset
' Open the connection and execute.
conn.Open sConnString
Set rs = conn.Execute("SELECT * FROM dbo.account where Account_ID_18__c = " & s1;)
' Check we have data.
If Not rs.EOF Then
' Transfer result.
Sheets(1).Range("A1").CopyFromRecordset rs
' Close the recordset
rs.Close
Else
MsgBox "Error: No records returned.", vbCritical
End If
' Clean up
If CBool(conn.State And adStateOpen) Then conn.Close
Set conn = Nothing
Set rs = Nothing
End Sub
My goal is to figure out how to take a dynamic range of values and use them within an SQL Where statement.
Try something like this:
Sub ConnectSqlServer()
Dim conn As ADODB.Connection, rs As ADODB.Recordset
Dim sConnString As String
Dim wb As Workbook, ws As Worksheet, rngIds As Range, sql As String, inList As String
Set wb = ThisWorkbook
Set ws = wb.Sheets("list")
Set rngIds = ws.Range("A1:A" & ws.Cells(Rows.Count, "A").End(xlUp).Row)
inList = InClause(rngIds)
If Len(inList) = 0 Then
MsgBox "No id values!"
Exit Sub 'nothing to query...
End If
sConnString = "Driver={ODBC Driver 13 for SQL Server}; Server=snapshot;" & _
"Database=salesforce_replica;" & _
"Trusted_Connection=yes;"
Set conn = New ADODB.Connection
conn.Open sConnString
Set rs = conn.Execute("SELECT * FROM dbo.account where Account_ID_18__c in " & inList)
If Not rs.EOF Then
wb.Sheets(1).Range("A1").CopyFromRecordset rs
Else
MsgBox "Error: No records returned.", vbCritical
End If
rs.Close
conn.Close
End Sub
'Generate a SQL "in" list from distinct values in range `rng`
' Add single quotes around values unless `IsNumeric` is True
' Note if `rng` has too many values you may exceed your max. SQL query size!
Function InClause(rng As Range, Optional IsNumeric As Boolean = False) As String
Dim c As Range, dict As Object, arr, qt As String, v
Set dict = CreateObject("scripting.dictionary")
For Each c In rng.Cells
v = Trim(c.Value)
If Len(v) > 0 Then dict(v) = 1
Next c
If Not IsNumeric Then qt = "'"
If dict.Count > 0 Then
InClause = "(" & qt & Join(dict.keys, qt & "," & qt) & qt & ")"
End If
End Function
I have adapted some code I found to extract a mySQL table and write it to a worksheet. However, it is slow for some of the larger tables(30,000+). I am trying to find a better way to import the values and avoid looping. I was hoping to be able to assign it directly to a range, but have been unsuccessful. From my research, it seems Excel is limited when it comes to mySQL. Any suggestions?
Dim password As String
Dim sqlstr As String
Dim dbTable As String
'OMIT Dim Cn statement
Dim server_Name As String
Dim user_ID As String
Dim database_Name As String
Dim lRow As Integer, lCol As Integer
'Start timer
Dim Count As Long
Dim BenchMark As Double
BenchMark = Timer
Application.ScreenUpdating = False
Application.EnableEvents = False
'OMIT Dim rs statement
Set rs = CreateObject("ADODB.Recordset") 'EBGen-Daily
server_Name = Sheet10.Range("b1").Value
database_Name = Sheet10.Range("b2").Value ' Name of database
user_ID = Sheet10.Range("b3").Value 'id user or username
password = Sheet10.Range("b4").Value 'Password
dbTable = Sheet10.Range("tbl_name").Value
sqlstr = "SELECT * FROM " & dbTable
Set cn = New ADODB.Connection
'On Error Goto ErrorHandler
cn.Open "DRIVER={MySQL ODBC 8.0 Unicode Driver};" & _
"SERVER=" & server_Name & ";" & _
"DATABASE=" & database_Name & ";" & _
"USER=" & user_ID & ";" & _
"PASSWORD=" & password & ";" & _
"Option=16427"
rs.Open sqlstr, cn, adOpenStatic
'MsgBox cn.Execute("SELECT COUNT(*) As row_count FROM elite_advocacy;")!row_count + 1
Dim myArray()
myArray = rs.GetRows()
kolumner = UBound(myArray, 1)
rader = UBound(myArray, 2)
'Delete existing table
On Error Resume Next
Sheet2.ListObjects("tbl_data").Delete
On Error GoTo 0
'Write array to sheet <<< Slow for large datasets
For k = 0 To kolumner ' Using For loop data are displayed
Sheet2.Range("rng_s_data").Offset(0, k).Value = rs.Fields(k).Name
For r = 0 To rader
Sheet2.Range("rng_s_data").Offset(r + 1, k).Value = myArray(k, r)
Next
Next
'Write array to range <<< Failed
'Attempt 2
'Dim r1 As Range, rBase As Range
'Dim L As Long, U As Long
'Set rBase = Sheet2.Range("rng_s_data")
'L = LBound(myArray)
'U = UBound(myArray)
'r1 = rBase.Resize(1, rader - kolumner + 1)
'r1 = myArray
'Find lRow and lCol
lRow = Cells(Rows.Count, Range("rng_s_data").Column).End(xlUp).Row
lCol = Cells(Range("rng_s_data").Row, Columns.Count).End(xlToLeft).Column
'Create a table from Data
'Sheet2.ListObjects.Add(xlSrcRange, Sheet2.Range("A$5:$Z$100"), , xlYes).Name = "tbl_data"
Sheet2.ListObjects.Add(xlSrcRange, Sheet2.Range(Sheet2.Cells(Sheet2.Range("rng_s_data").Row, Sheet2.Range("rng_s_data").Column), _
Sheet2.Cells(lRow, lCol)), , xlYes).Name = "tbl_data"
Sheet2.ListObjects("tbl_data").TableStyle = "TableStyleLight1"
'Autofit Sheet
Cells.EntireColumn.AutoFit
Application.ScreenUpdating = True
Application.EnableEvents = True
'End Timer
MsgBox Timer - BenchMark
Errorhandler:
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
I don't have mySQl for testing, but something like this would be a generic approach to querying any database from Excel using ADO.
Performance is optimum if you avoid any looping which involves cell-by-cell access, and do as much as you can with arrays, before transferring the final array to the worksheet in a single operation.
It's worth putting in extra effort to create re-usable pieces of code as standalone Subs or Functions - that allows your main logic to stay focused on the task at hand.
Sub Tester()
Dim cn As ADODB.Connection, rs As ADODB.Recordset
Dim sql As String, dbTable As String, data, rngTbl As Range
Dim BenchMark As Double
BenchMark = Timer
Set cn = GetConnection()
Set rs = New ADODB.Recordset
dbTable = Sheet10.Range("tbl_name").Value
sql = "SELECT * FROM " & dbTable
rs.Open sql, cn, adOpenStatic
data = RecordSetToArray(rs) 'Includes field names
'data = RecordSetToArray(rs,False) 'False = no field names
'Delete existing table
On Error Resume Next
sheet2.ListObjects("tbl_data").Delete
On Error GoTo 0
'put the data on the worksheet
Set rngTbl = ArrayToSheetRange(data, sheet2.Range("rng_s_data"))
With sheet2.ListObjects.Add(xlSrcRange, rngTbl, , xlYes)
.Name = "tbl_data"
.TableStyle = "TableStyleLight1"
.Range.EntireColumn.AutoFit
End With
Debug.Print "Done in " & Timer - BenchMark
End Sub
'return an opened connection object
Function GetConnection() As ADODB.Connection
Dim serverNm As String, userId As String, dbNm As String, pw As String
Dim cn As ADODB.Connection
Set cn = New ADODB.Connection
serverNm = Sheet10.Range("b1").Value
dbNm = Sheet10.Range("b2").Value ' Name of database
userId = Sheet10.Range("b3").Value 'id user or username
pw = Sheet10.Range("b4").Value 'Password
cn.Open "DRIVER={MySQL ODBC 8.0 Unicode Driver};" & _
"SERVER=" & serverNm & ";" & _
"DATABASE=" & dbNm & ";" & _
"USER=" & userId & ";" & _
"PASSWORD=" & pw & ";" & _
"Option=16427"
Set GetConnection = cn
End Function
'Create a 2-D array from a recordset
Function RecordSetToArray(rs As ADODB.Recordset, _
Optional IncludeFieldNames As Boolean = True)
Dim tmp, nC As Long, nR As Long, data, r As Long, c As Long, rowNum As Long
tmp = rs.GetRows() 'cols x rows
nC = UBound(tmp, 1) + 1 'zero-based --> 1-based
nR = UBound(tmp, 2) + 1
ReDim data(1 To nR + IIf(IncludeFieldNames, 1, 0), 1 To nC) 'allow for headers?
If IncludeFieldNames Then
For c = 1 To nC
data(1, c) = rs.Fields(c - 1).Name
Next c
rowNum = 1
End If
For r = 1 To nR
rowNum = rowNum + 1
For c = 1 To nC
data(rowNum, c) = tmp(c - 1, r - 1)
Next c
Next r
RecordSetToArray = data
End Function
'Fill an array to a worksheet starting at `rng`, and return the filled range
Function ArrayToSheetRange(data, rng As Range) As Range
Dim rv As Range
Set rv = rng.Cells(1).Resize(UBound(data, 1), UBound(data, 2))
rv.Value = data
Set ArrayToSheetRange = rv
End Function
I'm trying to update one field (tblUSA.RunSum) with the running sum of another field (Length), starting at tblUSA.RunSum= 0 for the first value. So far, I'm having no luck. Mo updates tblUSA are writing.
Dim db As Database
Set db = CurrentDb()
Dim lastValue, thisValue
s = "tblUSA"
Set rs = db.OpenRecordset(s, dbOpenDynaset)
'rs.Sort ("DateS")
lastValue = rs.Fields("Length")
rs.MoveNext
While (Not rs.EOF())
thisValue = rs.Fields("Length")
rs.Edit
rs!RunSum = thisValue + lastValue
rs.Update
lastValue = thisValue ' remember previous value
rs.MoveNext ' advance to next record
Wend
MsgBox "Done with " & s
This might do:
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim lastValue As Double
Dim s As String
Set db = CurrentDb()
s = "Select * From tblUSA Order By DateS"
Set rs = db.OpenRecordset(s, dbOpenDynaset)
While Not rs.EOF
rs.Edit
rs!RunSum.Value = lastValue ' Initially = 0
rs.Update
lastValue = lastValue + rs.Fields("Length").Value
rs.MoveNext
Wend
rs.Close
i am trying to get the frequency of terms within a collection of variable length strings.The context is descriptions in an Access database. Would prefer to keep the solution in VBA. Delimiter is " " (space) character
Dim db As DAO.Database
Set db = CurrentDb()
Call wordfreq
End Sub
Function wordfreq()
Dim myCol As Collection
Dim myArray() As String
Dim strArray As Variant
Dim strDescr, strTerm, strMsg As String
Dim i, j As Integer
Set myCol = New Collection
strDescr = "here it should accept the table and display the result in seperate table"
' db.Execute "select columns from table"
myArray = Split(strDescr, " ")
For Each strArray In myArray
On Error Resume Next
myCol.Add strArray, CStr(strArray)
Next strArray
For i = 1 To myCol.Count
strTerm = myCol(i)
j = 0
For Each strArray In myArray
If strArray = strTerm Then j = j + 1
Next strArray
'placeholder
strMsg = strMsg & strTerm & " --->" & j & Chr(10) & Chr(13)
Next i
'placeholder
'save results into a table
MsgBox strMsg
End Function
See an example below using a Scripting.Dictionary object.
Function wordfreq()
Dim objDict As Object
Dim myArray() As String
Dim strInput As String
Dim idx As Long
Set objDict = CreateObject("Scripting.Dictionary")
strInput = "here it should accept the table and display the result in seperate table"
myArray = Split(strInput, " ")
For idx = LBound(myArray) To UBound(myArray)
If Not objDict.Exists(myArray(idx)) Then
'Add to dictionary with a count of 1
objDict(myArray(idx)) = 1
Else
'Increment counter
objDict(myArray(idx)) = objDict(myArray(idx)) + 1
End If
Next
'Test it
Dim n As Variant
For Each n In objDict.Keys
Debug.Print "Word: " & n, " Count: " & objDict(n)
Next
End Function
Output:
'Word: here Count: 1
'Word: it Count: 1
'Word: should Count: 1
'Word: accept Count: 1
'Word: the Count: 2
'Word: table Count: 2
'Word: and Count: 1
'Word: display Count: 1
'Word: result Count: 1
'Word: in Count: 1
'Word: seperate Count: 1
Edit
The process:
Loop through the Input recordset.
Split the Description into words.
Check if the word exist in Dictionary and add or
increment.
Add the Keys (words) and Values (count) of the aforementioned
Dictionary to the Output table.
To achieve this two helper functions have been set up:
One loops through the description recordset and returns a
Dictionary object filled with unique words as Keys and their
count as Values.
The other takes the above Dictionaryobject and adds it to the Output table.
You need to change [TABLE] to the name of your Input and Output tables.
Option Explicit
Sub WordsFrequency()
On Error GoTo ErrTrap
Dim rs As DAO.Recordset
Set rs = CurrentDb().OpenRecordset("SELECT Description FROM [TABLE] WHERE Description Is Not Null;", dbOpenSnapshot)
If rs.EOF Then GoTo Leave
With rs
.MoveLast
.MoveFirst
End With
If AddDictionaryToTable(ToDictionary(rs)) Then
MsgBox "Completed successfully.", vbInformation
End If
Leave:
If Not rs Is Nothing Then rs.Close
Set rs = Nothing
On Error GoTo 0
Exit Sub
ErrTrap:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub
' Returns Scripting.Dictionary object
Private Function ToDictionary(rs As DAO.Recordset) As Object
Dim d As Object 'Dictionary
Dim v As Variant 'Words
Dim w As String 'Word
Dim i As Long, ii As Long 'Loops
Set d = CreateObject("Scripting.Dictionary")
For i = 1 To rs.RecordCount
v = Split(rs![Description], " ")
For ii = LBound(v) To UBound(v)
w = Trim(v(ii))
If Not d.Exists(w) Then d(w) = 1 Else d(w) = d(w) + 1
Next
rs.MoveNext
Next
Set ToDictionary = d
End Function
' Adds Dictionary object to table
Private Function AddDictionaryToTable(objDict As Object) As Boolean
On Error GoTo ErrTrap
Dim rs As DAO.Recordset
Dim n As Variant
Set rs = CurrentDb().OpenRecordset("[TABLE]")
With rs
For Each n In objDict.Keys
.AddNew
.Fields("Words").Value = n
.Fields("Counts").Value = objDict(n)
.Update
Next
End With
'all good
AddDictionaryToTable = True
Leave:
If Not rs Is Nothing Then rs.Close
Set rs = Nothing
On Error GoTo 0
Exit Function
ErrTrap:
MsgBox Err.Description, vbCritical
Resume Leave
End Function
I am running some code to split a slash-separated field into multiple rows, but the first value in the series is not carrying over. Does anyone know what I'm missing. Also, Rows that only have one record are not carrying over.
Public Sub ReformatTable()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim rsADD As DAO.Recordset
Dim strSQL As String
Dim strMPG, strBusinessName, strCustomerNumber, strCustomerName, strCountStartDate, strCCStatus As String
Dim strSplitMPG As String
Dim varData As Variant
Dim i As Integer
Set db = CurrentDb
' Select all eligible fields (have a comma) and unprocessed (SPLIT_MPG is Null)
strSQL = "SELECT BUSINESS_NAME, CUSTOMER_NUMBER, CUSTOMER_NAME, COUNT_START_DATE, CC_STATUS, MPG, SPLIT_MPG FROM [tmStarCycleCountStatuses_SlashesforCommas] WHERE ([MPG] Like ""*/*"") AND ([SPLIT_MPG] Is Null)"
Set rsADD = db.OpenRecordset("tmStarCycleCountStatuses_SlashesforCommas", dbOpenDynaset, dbAppendOnly)
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
With rs
While Not .EOF
strMPG = !MPG
strBusinessName = !BUSINESS_NAME
strCustomerNumber = !CUSTOMER_NUMBER
strCustomerName = !CUSTOMER_NAME
strCountStartDate = !COUNT_START_DATE
strCCStatus = !CC_STATUS
varData = Split(strMPG, "/") ' Get all comma delimited fields
' Update First Record
.Edit
!SPLIT_MPG = Trim(varData(0)) ' remove spaces before writing new fields
.Update
' Add records with same first field
' and new fields for remaining data at end of string
For i = 1 To UBound(varData)
With rsADD
.AddNew
!MPG = strMPG
!SPLIT_MPG = Trim(varData(i)) ' remove spaces before writing new fields
!BUSINESS_NAME = strBusinessName
!CUSTOMER_NUMBER = strCustomerNumber
!CUSTOMER_NAME = strCustomerName
!COUNT_START_DATE = strCountStartDate
!CC_STATUS = strCCStatus
.Update
End With
Next
.MoveNext
Wend
.Close
rsADD.Close
End With
Set rsADD = Nothing
Set rs = Nothing
db.Close
Set db = Nothing
End Sub
I can't post images yet due to reputation constrictions, but here are some links.
Here's the multiple after the code:
https://s9.postimg.org/fn3u70b5b/Multiple.jpg
Here's the single records after code run:
https://s10.postimg.org/bfq9z4snt/Singles.jpg
I feel like there's something super simple that I'm missing here, but it looks like I'm already taking the MPG over, so i'm curious as to why I'm not getting the singles or the first of the series. Any help would be appreciated! Thank you.
It looks right to me, except that you have to declare like this:
Dim strMPG As String
Dim strBusinessNames As String
Dim strCustomerNumbers As String
Dim strCustomerNames As String
Dim strCountStartDates As String
Dim strCCStatus As String
I guess you will have to insert some lines with Debug.Print .. to narrow down the happenings.
Got it. Ended up changing my approach. This code will work. The pattern, regardless of length, would always be the same, so I used a Right/Left combo and integers
Public Sub ReformatmStarCycleCountStatusesTable()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Dim strMPG As String
Dim strBusinessName As String
Dim strCustomerNumber As String
Dim strCustomerName As String
Dim dteCountStartDate As Date
Dim strCCStatus As String
Dim strSplitMPG As String
Dim intRemainingLength As Integer
Dim intInitialLength As Integer
Dim intStartPoint As Integer
Dim varData As Variant
Set db = CurrentDb
DoCmd.SetWarnings False
' Select all eligible fields (have a comma)
strSQL = "SELECT * FROM [mStar Cycle Count Statuses] WHERE [PRODUCT_FILTER_VALUE] is not null"
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
With rs
While Not .EOF
strMPG = !PRODUCT_FILTER_VALUE
strBusinessName = !BUSINESS_NAME
strCustomerNumber = !CUSTOMER_NUMBER
dteCountStartDate = !COUNT_START_DATE
strCCStatus = !Status
intInitialLength = Len(strMPG)
intRemainingLength = intInitialLength
intStartPoint = 2
Do While intRemainingLength > 0
strSQL = "INSERT INTO tmStarCycleCountStatuses VALUES ('" & strBusinessName & "', '" & strCustomerNumber _
& "', #" & dteCountStartDate & "#, '" & strCCStatus & "', '" & Right(Left(strMPG, intStartPoint), 2) & "')"
DoCmd.RunSQL (strSQL)
intStartPoint = intStartPoint + 3
intRemainingLength = intRemainingLength - 3
Loop
intStartPoint = 1
.MoveNext
Wend
.Close
End With
DoCmd.SetWarnings True
Set rs = Nothing
db.Close
Set db = Nothing
End Sub