MS Project 2016 - VBA Cycle Time Issues (Extraction to Excel) - excel

I have numerous macros that seem to be slowing down when interfacing with MS Project (e.g., reading data and processing it) in MS Office 2016. It could be my underlying code, mechanisms for obtaining/storing the data, but I'm not sure and would appreciate input.
I was just writing a new macro for extracting MSP data into an array (held in memory I thought this would be fastest, but it seemed to struggle to even do this). The idea being that the array memory access 'should' be faster than bridging from MS Excel to the Project referenced when needing to extract/place data? Perhaps this is incorrect.
Aim: Hold 3 parts of each MS Project Resource in Array, eventually iterate over array and place into Excel for additional use/processing.
The initial 'read' never quite completed (at 926 of my 1300 resources) before I broke the code to review.
Ideas on the below, or for interfacing with the MSP 2016 Reference library? It seems to have become very slow compared to just working within MS Excel.
Subroutine:
Private Sub cb_IMSResourceImport_Click()
Dim Prj As Project
Set Prj = GetObject(Me.cboMaintainToProject.Value)
Dim ResourceMatrix() As String
Prj.Application.WindowActivate Prj.Name
ReDim ResourceMatrix(Prj.Resources.Count, 2)
ActiveWorkbook.Sheets("Resource Table").Range("A2:C" & C`Str(ActiveWorkbook.ActiveSheet.UsedRange.Rows.Count)).ClearContents
For i = 1 To UBound(ResourceMatrix)
ResourceMatrix(i - 1, 0) = Prj.Resources(i).ID
ResourceMatrix(i - 1, 1) = Prj.Resources(i).Name
ResourceMatrix(i - 1, 2) = Prj.Resources(i).Code
Next i
For i = 0 To UBound(ResourceMatrix)
ActiveWorkbook.Sheets("Resource Table").Cells(i + 1, 1).Value = ResourceMatrix(i, 0)
ActiveWorkbook.Sheets("Resource Table").Cells(i + 1, 2).Value = ResourceMatrix(i, 1)
ActiveWorkbook.Sheets("Resource Table").Cells(i + 1, 3).Value = ResourceMatrix(i, 2)
Next i
End Sub

Using Project 2013 and a mocked-up schedule with 2000 resources, your code took less than 2 minutes to run. That said, if you have many schedules open, calculation left on, etc. it could take longer.
However, there is another way to copy data such as this from Project to Excel that is very fast—use the clipboard.
Private Sub ExtractResources()
Dim prj As MSProject.Project
Set prj = GetObject(Me.cboMaintainToProject.Value)
Dim msp As MSProject.Application
Set msp = prj.Application
msp.WindowActivate prj.Name
ActiveWorkbook.Sheets("Resource Table").Range("A2:C" & CStr(ActiveWorkbook.ActiveSheet.UsedRange.Rows.Count)).ClearContents
msp.ViewApply "Resource Sheet"
msp.SelectSheet
msp.EditCopy
ActiveWorkbook.Sheets("Resource Table").Range("A2").PasteSpecial xlPasteValues
End Sub
Note: Modify the Resource View (or create your own) to define which columns to export out of Project. If you want the ID column to be exported, check the definition of the table used with the resource view to make sure the first column is not locked, otherwise it won't be included in the SelectSheet method.

Related

Connect Excel with SAP using RFC

I need to know how to connect Excel with SAP using RFC. I have not managed to import any SAP data to Excel using the codes found so far.
I would like to be able to import data from any known transaction (e.g. a bill of materials from transaction CO03). From this I would try to understand how to extract other type of tables.
My goal is to be able to import any SAP data on a Excel spreadsheet using RFC. That would be a good start.
Do I need a special SAP account? How to verify my account is enabled to perform this type of tasks?
It is not possible to call any standard transaction remotely as most of them are legacy-like and doesn't return anything directly.
There are couple of ways to fetch data from any transaction but they are out of the scope of this question. The most practical way of retrieveing data from SAP to Excel is to find proper BAPI or remote-enabled FM, (including writing own wrapper FM) and this is the way I gonna describe here.
You don't need special account, you just need to have proper authorizations for RFC-calls, which mainly comprise of S_RFC authorization object
If you use BAPI, you can omit this point. If you created own wrapper, then you have to assure it is remote-enabled.
And then you can call your FM in VBA code and return results to Excel book. Here is the sample code:
' Logging in
Dim retcd As Boolean
Dim SilentLogon As Boolean
Set LogonControl = CreateObject("SAP.LogonControl.1")
Set objBAPIControl = CreateObject("SAP.Functions")
Set R3Connection = LogonControl.NewConnection
R3Connection.Client = "700"
R3Connection.ApplicationServer = "server_address"
R3Connection.Language = "EN"
R3Connection.User = "sap_user"
R3Connection.Password = "sap_pass"
R3Connection.System = "system_id"
R3Connection.SystemNumber = "sys_num"
R3Connection.UseSAPLogonIni = False
retcd = R3Connection.Logon(0, SilentLogon)
If retcd <> True Then MsgBox "Logon failed": Exit Sub
' Declaring FM interface
objBAPIControl.Connection = R3Connection
Set objgetaddress = objBAPIControl.Add("ZNM_GET_EMPLOYEE_DETAILS")
Set objkunnr = objgetaddress.Tables("ET_KUNNR")
Set objaddress = objgetaddress.Tables("ET_CUST_LIST")
' Filling select-options values table from sheet
Dim sht As Worksheet
Set sht = ThisWorkbook.ActiveSheet
If sht.Cells(6, 2).Value <> " " Then
objkunnr.Rows.Add
objkunnr.Value(1, "SIGN") = sht.Cells(6, 2).Value
objkunnr.Value(1, "OPTION") = sht.Cells(6, 3).Value
objkunnr.Value(1, "LOW") = sht.Cells(6, 4).Value
objkunnr.Value(1, "HIGH") = sht.Cells(6, 5).Value
R3Connection.Logoff
P.S. For all this to work in your VBA project you should add references to SAP ActiveX controls, which are located in %ProgramFiles%\SAP\FronEnd\SAPgui directory:
wdtaocxU.ocx
wdtfuncU.ocx
wdtlogU.ocx
wdobapiU.ocx
So references list of your VBA project should look like this
Additionally to Excel solution you may try this open source MS Access application I created long time ago and used many times:
https://blogs.sap.com/2013/08/16/read-data-from-sap-tables-into-ms-access-2003-database/

Using VBA to get data from SAS and limit the data set

I need to import data from SAS to excel via VBA. The import needs to run eg. on workbookOpen or Worksheet_BeforeDoubleClick or it can be called in any macro. This is solved in the below code:
Sub GetSASdata()
Dim obConnection As ADODB.Connection
Dim obRecordset As ADODB.Recordset
Dim i As Integer
Set obConnection = New ADODB.Connection
' Do not get stuck on the choice of connection provider.
obConnection.Provider = "sas.LocalProvider"
obConnection.Properties("Data Source") = "C:\path\"
obConnection.Open
Set obRecordset = New ADODB.Recordset
obRecordset.Open "MySAStable", obConnection, adOpenDynamic, adLockReadOnly, ADODB.adCmdTableDirect
'add header row
Cells(1, 1).Select
For i = 0 To obRecordset.Fields.Count - 1
ActiveCell.Offset(0, i).Value = obRecordset.Fields(i).Name
Next i
obRecordset.MoveFirst
obRecordset.Filter = "Weight > 0"
Cells(2, 1).Select
ActiveCell.CopyFromRecordset obRecordset, 100
obRecordset.Close
Set obRecordset = Nothing
obConnection.Close
Set obConnection = Nothing
End Sub
In this example I have restricted the output to be only the first 100 rows. However, the original data set is 1.4 m rows and 150 columns, and I want to be able to restrict the data import to only take columns that I define and rows which meet certain criteria. In sql terms:
select col1, col2, col10, col11 from MySAStable where code = MyCode and Date > MyDate
But I cannot find a way to do it. The first criteria is that the code should run entirely from Excel.
I have experimented some with obRecordset.Filter but the performance is poor. It takes forever. So idealy I would like to import only the data that I need. Is there a way to do this?
The
obConnection.Provider = "sas.LocalProvider"
is arbitrary. I found an example online, tested it and it worked. If someone has an answer to my problem that involves a different connection type, i am still interested to know. Very idealy the code can also be run by users who do not have SAS installed on their computer (but have access to the folder where data is placed.)
Thank you for any help
I have used two methods to read SAS data from within Excel.
The first uses SAS Add-In to MS Office. Do you have this product?
You can define the source with filters, and when the user opens the workbook, it will automatically refresh agains the datasource. You can also automate the refresh task with VBA code.
Secondly, I have done it with a Stored Process. If you have a stored process server, you can set up a web query in Excel and read the Stored Process that way, using any filter you need.

VBA subroutine slows down a lot after first execution

I have a subroutine that generates a report of performance of different portfolios within 5 families. The thing is that the portfolios in question are never the same and the amount in each family neither. So, I copy paste a template (that is formated and...) and add the formated row (containing the formula and...) in the right family for each portfolio in the report. Everything works just fine, the code is not optimal and perfect of course, but it works fine for what we need. The problem is not the code itself, it is that when I execute the code the first time, it goes really fast (like 1 second)... but from the second time, the code slows down dramatically (almost 30 second for a basic task identical to the first one). I tried all the manual calculation, not refreshing the screen and ... but it is really not where the problem comes from. It looks like a memory leak to me, but I cannot find where is the problem! Why would the code runs very fast but sooooo much slower right after... Whatever the length of the report and the content of the file, I would need to close excel and reopen it for each report.
**Not sure if I am clear, but it is not because the code makes the excel file larger or something, because after the first (fast) execution, if I save the workbook, close and reopen it, the (new) first execution will again be very fast, but if I would have done the same excat thing without closing and reopening it would have been very slow...^!^!
Dim Family As String
Dim FamilyN As String
Dim FamilyP As String
Dim NumberOfFamily As Integer
Dim i As Integer
Dim zone As Integer
Sheets("RapportTemplate").Cells.Copy Destination:=Sheets("Rapport").Cells
Sheets("Rapport").Activate
i = 3
NumberOfFamily = 0
FamilyP = Sheets("RawDataMV").Cells(i, 4)
While (Sheets("RawDataMV").Cells(i, 3) <> "") And (i < 100)
Family = Sheets("RawDataMV").Cells(i, 4)
FamilyN = Sheets("RawDataMV").Cells(i + 1, 4)
If (Sheets("RawDataMV").Cells(i, 3) <> "TOTAL") And _
(Sheets("RawDataMV").Cells(i, 2) <> "Total") Then
If (Family <> FamilyP) Then
NumberOfFamily = NumberOfFamily + 1
End If
With Sheets("Rapport")
.Rows(i + 8 + (NumberOfFamily * 3)).EntireRow.Insert
.Rows(1).Copy Destination:=Sheets("Rapport").Rows(i + 8 + (NumberOfFamily * 3))
.Cells(i + 8 + (NumberOfFamily * 3), 6).Value = Sheets("RawDataMV").Cells(i, 2).Value
.Cells(i + 8 + (NumberOfFamily * 3), 7).Value = Sheets("RawDataMV").Cells(i, 3).Value
End With
End If
i = i + 1
FamilyP = Family
Wend
For i = 2 To 10
If Sheets("Controle").Cells(16, i).Value = "" Then
Sheets("Rapport").Cells(1, i + 11).EntireColumn.Hidden = True
Else
Sheets("Rapport").Cells(1, i + 11).EntireColumn.Hidden = False
End If
Next i
Sheets("Rapport").Cells(1, 1).EntireRow.Hidden = True
'Define printing area
zone = Sheets("Rapport").Cells(4, 3).End(xlDown).Row
Sheets("Rapport").PageSetup.PrintArea = "$D$4:$Y$" & zone
Sheets("Rapport").Calculate
Sheets("RANK").Calculate
Sheets("SommaireGroupeMV").Calculate
Sheets("SommaireGroupeAlpha").Calculate
Application.CutCopyMode = False
End Sub
I do not have laptop with me at the moment but you may try several things:
use option explicit to make sure you declare all variables before using them;
from what I remember native vba type for numbers is not integer but long, and integers are converted to long, to save the computation time use long instead of integers;
your Family variables are defined as strings but you store in them whole cells and not their values i.e. =cells() instead of =cells().value;
a rule of a thumb is to use cells(rows.count, 4).end(xlup).row
instead of cells(3, 4).end(xldown).row.;
conditional formatting may slow down things a lot;
use for each loop on a range if possible instead of while, or even copy range to variant array and iterate over that (that is the fastest solution);
use early binding rahter of late binding, i.e., define objects in a proper type as soon a possible;
do not show printing area (page breaks etc.);
try to do some pofiling and look for the bottlenecks - see finding excel vba bottlenecks;
paste only values if you do not need formats;
clear clipboard after each copy/paste;
set objects to Nothing after finishing using them;
use Value2 instead of Value - that will ignore formatting and take only numeric value instead of formatted value;
use sheet objects and refer to them, for example
Dim sh_raw As Sheet, sh_rap As Sheet
set sh_raw = Sheets("RawDataMV")
set sh_rap = Sheets("Rapport")
and then use sh_raw instead of Sheets("RawDataMV") everywhere;
I had the same problem, but I finally figured it out. This is going to sound ridiculous, but it has everything to do with print page setup. Apparently Excel recalculates it every time you update a cell and this is what's causing the slowdown.
Try using
Sheets("Rapport").DisplayPageBreaks = False
at the beginning of your routine, before any calculations and
Sheets("Rapport").DisplayPageBreaks = True
at the end of it.
I had the same problem. I am far from expert programer. The above answers helped my program but did not solve the problem. I'm running excel 2013 on a 5 year old lap top. Open the program without running it, go to File>OptionsAdvanced, Scroll down to Data and uncheck "Disable undo for large Pivot table refresh...." and "Disable undo for large data Model operation". You could also try leaving them checked but decreasing their value. One or both of these seem to be creating a ever increase file that slows the macro and eventual grinds it to a stop. I assume closing excel clears the files they create so that's why it runs fast when excel is closed and reopened at least for a while. Someone with more knowledge will have to explain what these changes will do and what the consequences are of unchecking them. It appears these changes will be applied to any new spread sheets you create. Maybe these changes would not be necessary if I had a newer more powerful computer.

Copying Tables from Outlook Email to Excel File - VBA

I am currently working on a Outlook 2010 VBA macro to pull information from a email messages and place it into an Excel file. The idea is that each email has the same fields in tables embedded in the email message every time (name, order number, date, etc.) and that data is put into a spreadsheet. To do this, I have currently used the following code to get the table and move it into Excel:
'This code is inside a for each loop for each message
Set excelWorksheet2 = excelWorkbook.Worksheets.item(2)
Set excelWorksheet3 = excelWorkbook.Worksheets.item(3)
Set excelWorksheet4 = excelWorkbook.Worksheets.Add(After:=excelWorkbook.Sheets(excelWorkbook.Sheets.count))
Dim mailObj As Outlook.MailItem
Dim doc As Word.Document
Set doc = mailObj.GetInspector.WordEditor
Dim table1, table2, table3 As Object
Set table3 = doc.Tables(4).Range
Set table2 = doc.Tables(3).Range
Set table1 = doc.Tables(2).Range
table1.Copy
excelWorksheet2.Paste
table2.Copy
excelWorksheet4.Paste
table3.Copy
excelWorksheet3.Paste
Set table1 = Nothing
Set table2 = Nothing
Set table3 = Nothing
'I do much more of this to get the data from each table and put it into a master worksheet...
excelWorksheet.Cells(rows, cols + 1).Value = excelWorksheet2.Cells(4, 2).Value 'Contract Number
excelWorksheet.Cells(rows, cols + 2).Value = excelWorksheet2.Cells(4, 4).Value 'Contractor Name
Set doc = Nothing
Set excelWorksheet2 = Nothing
Set excelWorksheet3 = Nothing
Set excelWorksheet4 = Nothing
I get the following errors every so often, but it doesn't occur on the same data, it is sort of random and seems to occur on the Outlook/email side only:
"The requested member of the collection does not exist." (Error code
5941) at the .Range line
"Method 'Copy' of object 'Range' failed" at the .Copy
line
Sometimes both of these errors occur if I step through, if the copy fails, the macro will crash.
I have tried:
Putting in 2 second delays
Go through fewer emails (this code usually fails when I select > 10
emails to process)
Clearing the clipboard after every email
Close/deallocate objects through Nothing (not sure if
this is the best practice as I'm more of a C/C++/C#/Java guy)
None of these seemed to remotely fix this issue as both errors pop up frequently, but intermittently.
I'm truly at a loss as to what the next step would be in debugging this issue, any help would be much appreciated!
Based on research I have been doing on the issue of the WordEditor tables, it seems as the amount of copy/paste operations and the searching HTML tables just simply do not allow for reliable behavior. One possible solution to this could be to copy the entire email into Excel and parse the tables that way (this still requires copy/paste).
What I did to solve this problem (indirectly) is to save all the emails as HTML files (through Outlook.Application running in Excel: email.SaveAs folderPath + fileName + ".html", olHTML) in a temporary directory and have Excel open the HTML files in a workbook and work with the data that way. This has been much more reliable (added overhead though) and allows for large volumes of emails to be exported to Excel properly.
If anyone wants the exact code to my problem, message me (vindansam at hotmail.com, it's a tad long and has some proprietary information in it).
It's too bad that the WordEditor seems to not handle the information well, maybe Microsoft will beef things up in the next version of Office.

How to Correctly Specify ATP 2.0 XIRR Function Call in Access-to-Excel Automation

If someone can help, I need some in properly defining some call parameters in an Access 2003 to Excel 2003 VBA problem. I'm trying to use the XIRR function in the ATP 2.0 Type Library from Access. I have referenced the ATP 2.0 Type Library in my Access project. Here is the relevant VBA code (with a little pseudocode) I'm using behind a form:
Dim aCF as Variant 'this variant will hold the cash flows
Dim aDates as Variant 'this variant will hold the dates
Dim oATP2 As ATP2.OCATP
Set oATP2 = New ATP2.OCATP 'used in the Form_Open event to instantiate the object
In this model I always have five cash flows to work with: prior quarter value as an outflow, three months of net collections and the current quarter terminal value. (If there were more elements involved, I would certainly use a loop structure.) In a user-defined sub I redim the variants, load the arrays and call XIRR:
GetAssetReturn_X()
REDIM aDates(4) 'base 0
aDates(0) = DateSerial(Year(wDBBaseDate), Month(wDBBaseDate) - 2, 1) - 1 'e.g. 3-31- 2010
aDates(1) = DateSerial(Year(wDBBaseDate), Month(wDBBaseDate) - 1, 1) - 1 'e.g. 4-30-2010
aDates(2) = DateSerial(Year(wDBBaseDate), Month(wDBBaseDate), 1) - 1 'e.g. 5-31-2010
aDates(3) = wDBBaseDate 'e.g. 6-30-2010
aDates(4) = wDBBaseDate 'e.g. 6-30-2010
REDIM aCF(4) 'base 0
'from a recordset...
aCF(0) = -rs.Fields(2) 'pprd cash flow
aCF(1) = rs.Fields(3) 'net collection cprd - 2
aCF(2) = rs.Fields(4) 'net collection cprd - 1
aCF(3) = rs.Fields(5) 'net collection cprd
aCF(4) = rs.Fields(6) 'cprd cash flow
GetAssetReturn_X = oATP2.XIRR(aCF, aDates)
End Sub
The autosense feature works; when I type "oATP2." and I get a list of available functions ater the dot. So I assume the object is, in fact, correctly instantiated. Maybe not. However, whenever I run the code, I get the infamous runtime error "91: Object variable or With block variable not set." For the life of me, I'm missing the structural problem here. So I am presently assuming that the calling parameters have not been correctly described. I read somewhere these have to be variants. Maybe these need to be arrays or maybe range objects. Thanks.
I added to your code Set oATP2 = New ATP2.OCATP but I get the 429 error. Did you finally could use ATP2 library? as an alternative solution this code run in MS access but It is too slow:
Function tasa1(A, B)
Set AnalysisApp = CreateObject("Excel.Application")
Set AnalysisWkb = AnalysisApp.Workbooks.Open("C:\Archivos de programa\Microsoft Office\OFFICE11\Macros\Análisis\atpvbaen.xla")
AnalysisWkb.RunAutoMacros xlAutoOpen
tasa1 = AnalysisApp.Application.Run(AnalysisWkb.Name & "!XIRR", A, B, 0.1) * 100
End Function

Resources