Adding data to worksheet where unique identifier accumulates - excel

I had a mate of mine create a VBA script for me for a rather large Excel file containing football player game records.
The file currently has around 7000 player names over 190000 odd rows and each player is granted a unique PID (PlayerID). So a player with 10 games played will have 10 rows with one unique PID.
The script goes off to a website, copies player data and pastes it to the end of my excel worksheet called (Goals). When this data is added to the worksheet if a player matched on surname and firstname already exists, the PID will be populated with that players PID. If, in the data being added from the website, the player does not already exist a new unique number is to be given to this player.
For example:
The Player Fred SMITH PID=1234 already exists, any new record for him will receive the PID of 1234.
A new player Joe BLOGGS is added via the script the PID for him shall be the highest existing PID + 1. So if Fred SMITH has the highest PID then Joe BLOGGS would then be assigned the PID of 1235.
The script works well until a new player is added.
Data before import:
PID | surname | firstname | Game |
1233| Jones | Mark | 1
1234| Smith | Fred | 2
Expected after import - Joe Blogs New player
PID | surname | firstname | Game |
1233| Jones | Mark | 1
1234| Smith | Fred | 2
1235| Bloggs | Joe | 3
1234| Smith | Fred | 3
Actual after import - Joe Blogs New player
PID | surname | firstname | Game |
1233| Jones | Mark | 1
1234| Smith | Fred | 2
1235| Bloggs | Joe | 3
1236| Smith | Fred | 3
I can see why this happens as the script says add 1 in column A, but how can I change it so it adds 1 to the highest number in column A rather than the number on the row above?
Here's the script:
For d = 1 To 300000
If Worksheets("Goals").Range("G" & CStr(d)).Value = surname Then
If Worksheets("Goals").Range("H" & CStr(d)).Value = firstname Then
PID = Worksheets("Goals").Range("A" & CStr(d)).Value
ID = Worksheets("Goals").Range("B" & CStr(d - 1)).Value + 1
Exit For
Else:
If Worksheets("Goals").Range("H" & CStr(d)).Value = "" Then
PID = Worksheets("Goals").Range("A" & CStr(d - 1)).Value + 1
ID = Worksheets("Goals").Range("B" & CStr(d - 1)).Value + 1
Exit For
End If
End If
Else:
If Worksheets("Goals").Range("A" & CStr(d)).Value = "" Then
PID = Worksheets("Goals").Range("A" & CStr(d - 1)).Value + 1
ID = Worksheets("Goals").Range("B" & CStr(d - 1)).Value + 1
Exit For
End If
End If
Next d

There's a bunch of other optimizations you would probably benefit from making to your script, leaving aside anything else - you're accessing the Object layer a LOT which will slow things down.
As for calculating the PID quickly, there's a number of good ways to do it. My personal favourite (because it minimizes worksheet layer access) would be to build a dictionary of existing PIDs before you do anything else. If you're only importing say, 20 records, that might take an appreciable amount of time, but otherwise, the ability to use .HasKey(PID) methods will save you a LOT of time. Dictionaries are great, especially in languages like VBA.
Side note: What you really want is a primary key in a database. For all that Access gets a lot of flac for being too easy to use wrong, it is probably a better tool for this kind of thing than Excel.
Anyway, I'll throw in an optimized script in a few minutes, but in the mean time, you might want to look at using something like
Excel.WorksheetFunction.Max(Worksheets("Goals").Range("A1:A30000"))
when you're after the highest number in a range...

While a helper column that combined the firstname and surname would speed up general lookup operations, creating a virtual one in a Scripting.Dictionary might be the best for your import operation.
To use a Scripting.Dictionary you will need to go into the VBE's Tools ► References and add Microsoft Scripting Runtime.
Your code sample looks like it is walking through 300K rows for each new imported player. You will be able to see where my understanding of your supplied snippet breaks down.
Dim d As Long, lID As Long, lPID As Long, sPLYR As String
Dim surname As String, firstname As String
Dim dPLYRs As New Scripting.Dictionary
dPLYRs.CompareMode = TextCompare
'populate scripting dictionary
With Worksheets("Goals")
lID = Application.Max(.Range("B:B"))
lPID = Application.Max(.Range("A:A"))
For d = 1 To .Cells(Rows.Count, "G").End(xlUp).Row
sPLYR = .Cells(d, "G").Value & Chr(124) & .Cells(d, "H").Value
If Len(sPLYR) > 1 And Not dPLYRs.Exists(sPLYR) Then
dPLYRs.Add Key:=sPLYR, Item:=.Cells(d, "A").Value
ElseIf Len(sPLYR) > 1 And dPLYRs.Exists(sPLYR) Then
'repair broken PIDs
.Cells(d, "A") = dPLYRs.Item(sPLYR)
End If
Next d
End With
'this is where your sample code loses me. I have no idea where surname and firstname come from
'you probably need a loop to cycle through the imported names
'you have a unique index of surname & Chr(124) & firstname as the dictionary keys for lookup with the PID as each key's item
sPLYR = surname & Chr(124) & firstname
dPLYRs.RemoveAll: Set dPLYRs = Nothing
So the dictionary gets populated with all existing players and repairs and PIDs it finds. I cannot determine from what you've offered up what to do past then.

Related

VBA for Excel to insert rows based on text in sequential rows

I receive an Excel report showing number of patients in a center based on the payer type. Column A lists the center name and the payer types. If there are no patients for a particular payer type, that payer is not listed in the list. I would like to run a script to insert a row as a placeholder wherever a payer is not listed. For example, this is the output of the report:
A | B | C
| |
Region A | Date1 | Date2
| |
Center 123 | |
| |
Private | 4 | 6
Medicaid | 60 | 58
Total | 64 | 64
| |
Center 456 | |
| |
Private | 4 | 4
Medicare | 6 | 8
Hospice | 2 | 2
Managed Cr | 8 | 10
Total | 20 | 24
But I need for all payers/total lines to be listed for each center:
Center 123
Private
Medicare
Medicaid
Medicaid Pnd
Veterans
Hospice
Assisted Lv
Managed Cr
Unassigned
Total
Bed Hold
Total with Bed Holds
Can this be done with VBA to, for example, insert a row after "Private" if "Medicare" is not in the next row - and then put "Medicare" in that new row (column A)? Then insert a row after "Medicare" if "Medicaid" is not in the next row - and put "Medicaid" in the new row, and so on down the list. Thank you in advance for any help you can offer.
Quick question. Your title is vbscript, but your tag is excel-vba. No sure if you know they are two different things.
If your looking for a vba solution, also referred to as a macro, here's an idea.
If you are doing this for esthetics, turn the used range into a table, and hide the rows that don't have any values. This can be done without a macro. But here's a quick macro example:
Sub Button1_Click()
Dim objTable As ListObject
Worksheets("Sheet1").Activate
ActiveSheet.UsedRange.Select
Set objTable = ActiveSheet.ListObjects.Add(xlSrcRange, Selection, , xlYes)
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=2, Criteria1:= _
"<>"
End Sub
If you were looking for a vbscript solution, its similar, but different syntax.
And a note, you can "Record Macros" in excel. This will allow you to perform a task manually, and excel will give you the macro version of the action. It's not perfect, but for stuff like this it will give you a good place to start.
Yes, this can easily be done with VBA. These are the logical steps you need to take:
Go through each row;
for each row;
Check text in column A and text in the cell below;
if no action is required, do nothing and go to the next row;
if an action is required, execute the desired action;
You can update your question with the code you have so far and describe in detail where exactly you are stuck, and I am pretty sure that we will be able to help you. Please google a specific question first before you ask it here.

AutoIncrement ID Number (Works with Sorting)

Good Morning,
I'm constructing a table and I'd like to assign each entry a unique ID number that autoincrements as new values are entered into the table. Here's a sample table to give a visual:
+----+------------+-----------+
| ID | First Name | Last Name |
+----+------------+-----------+
| 1 | John | Smith |
| 2 | Steve | Wozniak |
| 3 | Steve | Jobs |
+----+------------+-----------+
In the above table, if I enter a 4th person, what I would like to happen is that the ID is automatically generated (in this case as 4). I've tried an in cell formula such as:
=Row([#ID])-1
This works until you sort the table, in which case the ID numbers get all scrambled up. I'd like to calculate what the row number should be, but then keep that value constant. Any ideas?
If you want a static increment key, you should use a code like this in your output worksheet
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 2 Then
Target.Offset(0, -1).Value = Target.Row - 1
End If
End Sub
If you just want a unique key that isn't actually incrementing based on previous one, you can use a formula like =SUM(CODE(MID(B2&C2,ROW(INDIRECT("1:"&LEN(B2&C2))),1))) in your A2 cell (closing with CSE) and pull down. However, if you extend a formula too much it will affect your sheet's performance.

In Excel, how would I divide a sum, and have one cell with a different percentage

I am looking to divide a single sum among various participants.
I have 12 people listed in a column, the next column will contain the divided sum amounts. The cell at the top of the sum column (C13) will have an amount input into it ($90.63) and the formula will then divide that amount among the 12 cells under it.
people | share |
| 90.63 |
-------- | --------- |
person01 | =(C13/12) |
...
person12 | =(C13/12) |
That part I got figured out. so if column C cell 13 had an amount of $90.63 entered, the formula in the 12 cells under it would read =SUM(C13/12), each cell with this formula would then contain a divided amount to show each persons share ($7.55) of the entered amount ($90.63).
However, if one of those 12 people were to only get half of this sum amount share, how do I calculate that while giving the remaining people the extra share?
In essence, if person01 would only get =(C13/24) how do i calculate the share of the remaining 11?
people | fair share | actual share
| 90.63 |
-------- | ---------- | ------------
person01 | =(C13/12) | =(C13/24)
...
person12 | =(C13/12) | ?
Referring to my comments "For one person $C$13/24 and for rest 11 =($C$13-($C$13/24))/11 " I have rechecked and the formula proposed earlier gives correct results. Screenshot is appended below. Moreover this solution is based on Excel terminology rather than Maths.
for 1st person = c13/2 and for others =($c$13)/2/11
Place the total in C1 and then run this:
Sub cropier()
Dim V As Double, i As Long
V = Range("C1").Value
i = 2
Do Until V < 0.01
Cells(i, "C").Value = Cells(i, "C").Value + 0.01
V = V - 0.01
If V < 0.01 Then Exit Sub
For i = 3 To 13
Cells(i, "C").Value = Cells(i, "C").Value + 0.02
V = V - 0.02
If V < 0.01 Then Exit Sub
Next i
i = 2
Loop
End Sub
The macro deals out the money one or two pennies at a time until the money is completely distributed.
The person at C2 gets only half the shares of the others.
Let x be the amount that everyone (but one) pays. Let .5x be the amount that one person pays. The sum, .5x + 11x = 90.63. If you let 11.5x = 90.63, then x=7.88087. The first person pays $3.94 and the rest pay $7.88.
You can double check; 3.94 + 11 * (7.88) = 90.63.
(And yes, this is an algebra problem, not an Excel problem. Nevertheless...)
Create a column of shares that each person would have. Place a .5 in the first row and 1 in the rest. (The sum at the bottom is 11.5, in B15.)
Create a column with 90.63 at the top (here, C1). In C3, place the equation =C$1/$B$15*B3, which says to take the 90.63 and divide it by the total number of shares (11.5) and multiply by how many shares this person is assigned.
Besides them adding up to 90.63, please note that the .5 share is exactly half the amount of the rest.
(For the other answers that are dividing by 11 and such: would you please try your solution and see if you are adding up to 90.63 and whether person 1 pays 3.94?)

Pulling data out of an worksheet by country

I have a huge amount of people in my excel sheet and I want to split them by country with excel coding, here is an example of my data:
Country | Name
UK | Tom
Austria | Bobsky
UK | Ralf
Germany | Badolf
Germany | Schwartz
UK | Andy
So would it be possible to just separate the people who are in the UK into s different part of my spreadsheet?
I have already tried
INDEX(B1:B6, MATCH("UK", A1:A6,0)) - this returns a repeated row if the match function returns no result
I have also tried many things with
if(VLOOKUP(etc etc) = "UK".....
and I have found this doesn't work either. I thought this would be something excel could do simply without having to filter + copy & paste or use VBA but this is not easy.
This is doable for a couple thousand rows of data. If your 'huge amount of people' is much more than that, an advanced filter or pivot table is a more viable solution.
      
With UK in D3 use the following in E3.
=IFERROR(INDEX($B$2:$B$9999, SMALL(INDEX(ROW($1:$9995)+($A$2:$A$9996<>D3)*1E+99, , ), ROW(1:1))), "")
Fill down as necessary.
Without wasting time to draft a complex Excel formula:
Option 1
Use a PivotTable where you use the Country column as a filter
Option 2
Use a Microsoft Query Data->From Other Sources->Microsoft Query on each worksheet like this:
SELECT * FROM [Sheet1$] WHERE Country = 'UK' ORDER BY NAME
Sub processData()
'I have workout for only UK
'this macro copies records of UK people from Sheet1 to Sheet2
lastrows = Worksheets("Sheet1").Cells(Rows.count, 1).End(xlUp).Row
count = 1
For x = 2 To lastrows
If (Worksheets("Sheet1").Cells(x, 1) = "UK") Then
Worksheets("Sheet2").Cells(count, 1) = Worksheets("Sheet1").Cells(x, 1)
Worksheets("Sheet2").Cells(count, 2) = Worksheets("Sheet1").Cells(x, 2)
count = count + 1
End If
Next x
MsgBox "Task finished"
End Sub

Get a random row in a sheet, where column is a known value

I have an Excel file that contains data like this:
id line | idsector |sector | isSectorPrior |etc...
1 | 1 | east | no
2 | 1 | east | no
3 | 1 | east | yes
4 | 1 | east | yes
5 | 2 | west | yes
6 | 2 | west | yes
7 | 2 | west | no
8 | 2 | west | yes
I need to draw one line where isSectorPrior will be "yes", and then, when I have the row, get id line that is in the cell 1.
So here, it can choose row 3, 4, 5, 6, or 8.
I found a lot or samples, that uses the range (an example), but they can return e.g. row number 7, even though it has a "no". This should not happen.
How can I specify, get a random row, but only where iSectorPrio is yes?
My current code, not so much because I didn't find anything that could match what I want :
'get a sheet to use and find a random row
Set tempSheet = ActiveWorkbook.Sheets(1).Select
'loop 50 times, to draw 50 different lines
For i = 1 To 50
'todo : get randow row, where IsSelectPrio is yes
'then do whatever I want with it, like write copy it in another sheet or a csv
Next i
The difficulty here (for me) is, there is no particular range, all I found were using the range. And considering, thats in the first rows, it it not part of the table.
Is there a way to do what I want?
There are at least two approaches.
Approach 1: Get a random row number (e.g. using the code from that other solution you link to), then check if you have a "yes" on that row. If not, then get a new random number; repeat until you get a yes. (Warning: this will go into an infinite loop if you have no yes'es.)
Approach 2: Start by making a list of row numbers that have a yes; then pick a random one among those.
If your only problem is finding the right range to pick a random row you can do this:
(If your worksheet has to look the same way it looks now, make a copy of it)
Dim ws As Worksheet
Dim sortRangeRows As Integer
Dim lastRow, lastColumn As Integer
'Get sortRange
lastRow = ThisWorkbook.Sheets(1).UsedRange.SpecialCells(xlCellTypeLastCell).Row
lastColumn = ThisWorkbook.Sheets(1).UsedRange.SpecialCells(xlCellTypeLastCell).Column
Set ws = ThisWorkbook.Sheets(1)
Set sortRangeRows = ws.Range(ws.Cells(1, 1), ws1.Cells(lastRow, lastColumn))
'Sort after isSectorPrior:
sortRangeRows.Sort Key1:=sortRangeRows(1, 4), Order1:=xlAscending, Header:=xlYes, Orientation:=xlSortColumns
'Find the last row with a "yes" in it (not entirely sure about this piece of code)
lastRowYes = ws1.Cells.Find("yes", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Now you have your range of rows with a yes.
HTH

Resources