I appear to have a significant data integrity issue using Dictionaries in Excel VBA which I use extensively in my code. I have a routine where I use a Dictionary to capture the best results from an optimization routine. Once captured, I then update an existing data table, using the results in calculations where a zero value will cause errors. This routine has been used extensively in the past with no errors. Therefore, I was surprised when errors started occurring. I tracked the error to what appears to be a Dictionary's data being corrupted outside of the actual code.
The Dictionary with the error is loaded by another Dictionary that continually looks for updates and only keeps the values that are better than previous ones, thus the optimization. Once these values have been captured, the Dictionary (DataItems) that causes the error loads the new values into a data table a variable at a time. Here is the code I used to trap the error:
If KeyCount <> DataItems.Count Then: Stop
NewValue = DataItems(NewData)
If KeyCount <> DataItems.Count Then: Stop
I captured the count of Items in DataItems prior to reading the data into the NewValue variable. Prior to reading the data the Dictionary had a count of 1, but after reading the data, the count went to 2. The original key became the key of a new record with an item value of zero and the original item obtained a new key that appears to be a value in the original bulk data being scanned. This appears to occur almost always with only one record in the DataItems dictionary.
I can use the same routine with other calling applications with absolutely no problems, so the problem appears to be tied to the calling application which makes no sense since the dictionaries being used originated in the routine where the error occurs. I have tried eliminating potential timing issues by stepping through the code and other debugging actions, but I remain extremely puzzled. It really makes me question the potential data integrity of using Dictionaries in Excel for application critical data. Any insights would be appreciated.
I'm guessing that you're using the Watch window and/or the Immediate pane while debugging, and that's what's causing the problem (or at least making it worse)... If you add specific expressions such as DataItems("keyHere") to the Watch list then when code is paused that watch will actually add that key if it doesn't exist.
Similarly, if when paused you do something like ? DataItems("thisKeyDoesNotExist") in the Immediate window, you will not see any output, but that key will silently get created.
To illustrate:
Sub DictWatch()
Dim d As Object, k
Set d = CreateObject("scripting.dictionary")
d.Add "one", 1
Stop 'add a watch on the expression d("one") before proceeding
d.RemoveAll
Stop 'enter "? d("test")" in Immediate pane and hit enter before proceeding
For Each k In d.keys
Debug.Print k 'output is "one" and "test"
Next k
End Sub
Related
I'm extremely new to VB. I've managed to put together a form that gives users the option to pick from 10 different checkboxes (all or any combination) that will run a query against our database and bring back the results into an excel worksheet (a separate worksheet for each option). Basically in my submit button code, the code says If ckboxA = True Then Call query A End If, If ckboxB = True Then Call query B End If, etc...(Each of my queries is a separate Sub in 1 Module - not sure that's important, but just in case).
Some of the queries are really quick while others take some time, I want to provide a progress bar so that the user knows the process is running and gets an idea of progress being made. I've watched video after video, but they all involve a loop where the same thing is being done over and over so the progress is pretty easily measured. How do I do this in my situation, can I add something in my "If" statement for each of the options?
My team and I have created a consolidator tool that consolidates data from worksheets uploaded using a button. However, there's an added enhancement that our leads would like to have.
I don't know if it's possible in VBA but what they wanted is a button that checks and highlights "garbage data" (for example: if First Name column contains a blank or if it contains ajsajdj or something similar), it will prompt the user and ask them if they want to delete it.
We already have the code for the consolidator tool (and it's working perfectly) however, this feature is headache inducing as I don't know if it's possible. I would really like to ask suggestions regarding this as I'm really new to VBA and programming.
Maybe, I would be enlightened on what next step I should take.
Let's see how a human would validate that
He would look at the name ajsajdj and think "I have never seen this name in my life before so it must be nonsense data". But he might fail because the fact that he never came accross this name doesn't mean it's not an existing name (parents can be inventive sometimes).
So what a human actually does is comparing the ajsajdj with a list of names (he has in mind because of his experience in life).
Now a program can do the same
You can write a code that compares ajsajdj with a list of valid names. But here we are at the same point where the human can fail too. The list will never be complete because tomorrow parents invent a new name (that you don't have in that list).
Conclusion
This task cannot be coded unless you define a rule for either valid or invalid data. Some programs look like they could do magic, but actually it is only working because of the rules you give them.
I do automated auditing of this type prolifically so I would approach it like this;
Your 'audit macro' is basically an iterator with many quality checks - is 'name' = "", etc. You can run this auto macro either.
On a single line of data each step of the consolidation
After the consolidation has completed.
The first is easiest to use, and works like this:
After your consolidation step run 'auditing macro' on line of data just brought in.
If an issue is found, write the line of data to a separate Tab leaving column A blank; not to your consolidation. At the end of your consolidation give user a warning message if there have been any issue lines found at the end of the consolidation
User skim reads data on separate tab, puts 1s for 'keep' in Column A.
User clicks a button to run a macro which adds the rows with a '1' against them back to your full data set (e.g. on the end if order doesn't matter).
Equally you could approach this by running the audit when the consolidation is totally complete; in this case you'd need to delete or otherwise track rows which may be removed if a user chooses not to keep them.
I like this approach because it is non-blocking; user can leave your consolidator to run without supervising and then deal with exceptions at their convenience. Also you can write/edit as many tests as you want without fundamentally changing your consolidator at all; you can then also start for example counting the number of each issue per import and putting this into a report for continuous improvement... there are options to extend.
In terms of pseudocode its an iterator full of if-else blocks, with a single 'there is an issue' flag, which if it's 1 causes the row to be treated as an issue;
For rowCount = startRow to endRow
' startRow and endRow correspond to lines of data you just imported
'Test 1
if (Some condition e.g. cells(rowCount ,2).value = "") then
issueFlag = 1
End if
'Test n...
if (Some condition e.g. cells(startRow,2).value = "") then
issueFlag = 1
End if
next rowCount
if issueFlag = 1 then
'CODE TO PASTE DATA
'Set some flag/variable which then triggers a Error Message at the end of the whole consolidation or audit
End if
You can put a Exit For at the end of the IF block so if the issueFlag is triggered you immediately exit and skip all further tests.
I am writing a piece of code to copy data from a web table to an excel sheet. I don't understand why I am getting row count and column count 1. Is there something else that I need to add?
Here is my code:
Dim XL
Set XL=createobject("Excel.Application")
XL.Workbooks.Open "D:\QTP\RailwaysforSurat.xlsx"
Set nsheet = XL.Sheets.Item(1)
row=Browser("title:=.*").Page("title:=.*").WebTable("html tag:=TABLE", "index:=0").GetROProperty("rows")
msgbox row
cols=Browser("title:=.*").Page("title:=.*").WebTable("html tag:=TABLE", "index:=1").GetROProperty("cols")
msgbox cols
This not complete code. I have trouble getting rows and columns count. Please help!
P.S. I am using this website for the testing "http://www.indianrail.gov.in/cgi_bin/inet_trnnum_cgi.cgi"
I noticed that you are using Descriptive Programming techniques to identify the WebTable, but you're using extremely generic descriptions for everything. Perhaps that's just scrubbed for the sake of posting publicly, but the important thing that I notice is that your code to read Rows is trying to find a table with Index of 0 and your code to read Cols is trying to find a table with index of 1...
This means you are accessing two different tables. Are you sure that your descriptions are finding the table that you think you want?
My suggestion is to:
1) bring up the page in your browser so that you can see the table.
2) Bring up QTP and open the GuiSPY
3) Click the pointing hand over a cube button to begin spying
4) Click on something inside the table you are trying to work with.
GuiSPY will snap back and show a hierarchy of objects that it found. Next, you want to positively identify what level in that hierarchy is the table at... I would...
5) starting at the top (Browser), select the top row and click "Highlight in Application" and watch what gets lit up.
6) go down the hierarchy list clicking the next item in the hierarchy and clicking "Highlight in Application" until you see it flash on the exact table you're trying to target.
7) Once you have isolated the table, click the button for "Copy the identification properties to the clipboard" button, then close GuiSPY.
8) Open a notepad, or just use QTP's editor window itself and paste in what GuiSPY copied to clipboard.
Ok, you now have a complete list of everything QTP was able to see about the specific table you want to detect. From here, you want to look through the list of properties and find one (or two) that would positively identify that table every time.. For example, on this very page, the table that holds your question could be identified as:
WebTable("text:=I am writing a piece of code to copy data")... (*note I've shaved it down because it's automatically a Regex string... With some cleanup, it could be:
Browser("StackOverFlow.Com").Page("Question 36663629").WebTable("text:=I am writing a piece of code to copy data"))
Now replace your .WebTable("html tag:=TABLE", "index:=0") with that data you've selected, and try it again. Hopefully you can lock in on the exact table you're expecting and get the info you need.
The indianrail page did not open so cannot see the table you are testing. But QTP provides default methods for rows and columns, try using that and see if it works.
NumRows = Browser("Mercury Tours").Page("Search Results").WebTable("OutboundFlights").RowCount
NumColumns = Browser("Mercury Tours").Page("Search Results").WebTable("OutboundFlights").ColumnCount(1)
ColumnCount takes row number as parameter
Not sure what tables you are looking at, but if it is Services and information then the rows/cols value for those table is 1.The whole web page is consisted of concatenated web tables. So its going to be little tricky to capture that information. Try using a child object method and count the number of links within the web table, its going to look something like this -
Set oWebEdit=Description.Create
oWebLink("micclass").value="Link"
Set olink = Browser("title:=.*").Page("title:=.*").WebTable("").childobjects(oWebLink)
olink.count
msgbox olink.count
'Then initiate a for loop
For i = 0 to olink.count-1
' Get the link name
olink. count(i).GetRoproperty("name")
'Initiate an array and save the link names
If get link name does not work then you can use childitem method.
Also make sure you are using the correct index for the tables or define some other properties as well.
P.S. If you are going to use DP in future and haven't already then read about Childitems/childobjects methods.They come in real handy while using DP.
I must be missing something:
[problem]:
I have a 2-dimensional array of data, that i would like to insert into the current sheet starting from cell A1. I also have to format this data.
The current document may be empty or not. I do not have control over this.
At a later point, i need to remove the data from the document and insert new data. This new set of data may have different dimensions.
This seems to be impossible to do using the Office JavaScript Api.
All the things I tried using TableBindings, etc. have failed. In many cases, functionality that should work according to MSDN failed, giving me cryptic error messages such as "internal error" (code 5001) or unsupported binding operation (3010). I have to use tablebindings, because i can't apply formatting to anything else according to the MSDN documentation.
The following solutions are unacceptable:
forcing the user to use a specific document template with preexisting named tables
forcing the user to select cell "A1" before my Add-In gets to work.
forcing the user to select a range prior to inserting the data.
All of which are nightmarish solutions from a usability point-of-view.
I can create a binding from named item and construct the range by counting columns and rows and building a string like this "A1:C232" but this only works once, because:
i can't delete the data (yes. calling "deleteAllDataValuesAsync" on a binding created with such a named range throws error 3010 (even though the binding say's it's a "table" binding.. wat?).
i can't overwrite it with data of different size (overwrite error)
i can't set formatting on it (yes, it's a binding created as a tablebinding, yes, i can call the setFormatAsync function, and it throws an "internal error, 5001" -> #headdesk
I hope someone from Microsoft reads this and can point me in the right direction. I really hope! Because i'm starting to fear that this is actually by design. (i'm so frustrated by office.js after the last few weeks of struggle, i'm having a hard time not ranting, so i'll stop right here.. don't get me started on ui fabric)
With the new Excel Javascript APIs introduced with Office 2016 (and available soon in Office Online), it is easy to manipulate worksheet data using the new Range object. The snippet below runs without requiring range selection or any other action on the part of the user.
var data = [
[1,2,3],
[2,6,7]
]
Excel.run(function (ctx) {
// make space for the data to be inserted
var sheet1 = ctx.workbook.worksheets.getItem("Sheet1");
var firstCell = sheet1.getCell(0,0);
var lastCell = sheet1.getCell(data.length - 1, data[0].length - 1);
var range = firstCell.getBoundingRect(lastCell).insert('down');
range.values = data; // insert data
range.format.font.bold = true;
range.delete('up'); // erase data, shift up
return ctx.sync();
}).catch(function(error) {
console.log(error);
})
Try it out in the Office JS Snippet explorer and see the blog post linked below for more info!
https://github.com/OfficeDev/office-js-snippet-explorer
https://dev.office.com/blogs/Office-js-Public-Preview
I have an ADO recordset that returns no rows (which is expected), but my watches panel shows a valid field collection, with column names that I want to store / capture.
However, using code like
x = rs.Fields(idx)
returns the error '3021' : Either BOF or EOF is True. Requested operation requires a current record.
My question is is it possible to read the Fields collection (noting that the contents that I can see in the watches panel appear correct and what I want to be able to grab), and if not, what are the "gotchas" that explain why?
Many thanks
Mike
You need name:
x = rs.Fields(idx).Name
Field(idx) is the value, which does not exists.