Requesting some help from you more advanced excel VBA wizards.
Situation: I work as a medical administrator of sorts. HIPPA is obviously a concern for me as my personal tracker does contain a lot of HIPPA, and the whole "need to know" bit is a big deal. I need to have a workbook available for my subordinate staff to see without violating HIPPA
I have a workbook with a lot of data. I would like a separate workbook (Book2) to pull names from column A(the patients unit) and B(their name) if they meet a number or text condition from a separate column (let's call it column D).
I know I can filter, then copy/paste the list or data that is needed for them, but that is time consuming for 5 separate units with 100+ patients each. If at all possible, I would prefer to share Book2 with the option for them to leave comments next to the name. The idea is to just update BookA, so the can have the most up to date names in real time.
I've tried VBAs and customizing them to my criteria , but cant seem to find anything that works. Any help is appreciated.
*OP note - I'm still very much a novice at this whole macro thing. I'm not to the point of writing any code of my own yet, just stealing other people efforts. Which has been done successfully in previous needs.
The following code should get you started (Run from Book2);
Sub CopyIfCriteria()
'Get other workbook and worksheet
Dim wb As Workbook
Set wb = Excel.Workbooks("BookA.xlsx")
Dim ws As Worksheet
Set ws = wb.Worksheets("Sheet1")
'Column 1 = A
Dim unit As Long
unit = 1
'Column 2 = B
Dim name As Long
name = 2
'Column 4 = D
Dim criteria As Long
criteria = 4
'Row 1 = 1, change if headers
Dim firstRow As Long
firstRow = 1
'Row n = last row with data
Dim lastRow As Long
lastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
'Current row index on BookA, starts at first row
Dim copyIndex As Long
copyIndex = firstRow
'Current row index on Book2, starts at row 1
Dim pasteIndex As Long
pasteIndex = 1
For copyIndex = firstRow To lastRow
'Change what the condition is to check if criteria is correct
If (CBool(ws.Cells(copyIndex, criteria).Value) = True) Then
'Copy unit and name to the next available row on Book2; pasteIndex
ws.Cells(copyIndex, unit).Copy Cells(pasteIndex, unit)
ws.Cells(copyIndex, name).Copy Cells(pasteIndex, name)
'Use pasteIndex to find what row we're up to on Book2, increment everytime we use a row
pasteIndex = pasteIndex + 1
End If
Next copyIndex
End Sub
This will simply get the BookA workbook, assuming it is opened in Excel, and then iterate through every row. If column D (4) has the correct criteria, then it'll copy column 1 and 2 (A & B) of that row to the next un-used row in Book2.
You will ned to have a blank Excel file (from which the code is run) open, as well as having BookA open (and having the data on Sheet1) of BookA. If they're not in 'BookA.xlsx' or 'Sheet1' then simply change the names to suit your needs.
Do take the time to just read through it and take not of the comments to help you understand better how it is doing it.
Related
First off:
On file1 > sheet1 - I have Ids of data on column A.
On source file - I have a huge data with multiple columns with same column of Ids on column A in sheet1.
I trying vlookup to get data for multiple columns from another closed workbook but result is coming only for one column. Also i don't want to open a source file as file size is bit heavy (approx. 600mb).
below are the code which i am using for above scenario. i know this code not is correct and need more correction. So can someone help me into this.
Sub MyMacro()
Dim rw As Long, x As Range, lastrow As Long, lastcol As Long
Dim book1 As Workbook, twb As Workbook
Set twb = ThisWorkbook
Set book1 = Workbooks.Open("C:\Users\Charles Paul\Desktop\VBA\12-Oct\Record.xlsx")
Set x = book1.Worksheets("Sheet1").Range("A:A")
With twb.Sheets("Sheet1")
lastrow = x.cells(x.Rows.Count, x.Column).End(xlUp).Row
lastcol = x.cells(x.Row, x.Columns.Count).End(xlToRight).Column
For rw = 1 To .cells(Rows.Count, 1).End(xlUp).Row
.cells(rw, 2) = Application.VLookup(.cells(rw, 1).Value2, x, 1, False)
Next rw
End With
book1.Close savechanges:=False
End Sub
For large data sets, you might want to look into power query.
It is accessible from here:
I will not get into details, as setting up a query is a separate thing, but you can manage it with relevant VBA code.
I wrote a macro that compares the columns B, which contains file numbers, in two worksheets. There are three possibilities: the file number exists in both columns, the file number exists only in the first column and the file number exists only in the second column. If e.g. the file number exists in both columns, the macro should copy/paste the entire row to another sheet. Same for the other two scenario's.
My code work perfect for a small file (around 500 rows, 236 columns), but for the large files it doesn't work. It takes way too long, and at the end it just crashes. I already tried the usual tricks to speed up the macro.
Option Explicit
Sub CopyPasteWorksheets()
Dim wbDec As Workbook, wbJune As Workbook, wbAnalysis As Workbook
Dim wsDec As Worksheet, wsJune As Worksheet
Dim PresPres As Worksheet, PresAbs As Worksheet, AbsPres As Worksheet
'Stop screen from updating to speed things up
Application.ScreenUpdating = False
Application.EnableEvents = False
'Add 3 new worksheets. They each represent a different category, namely the one with already existing insurances, one with new insurances
'and one with the insurances that are closed due to mortality, lapse or maturity. Add two (temporary) worksheets to paste the databases.
Worksheets.Add().Name = "PresPres"
Worksheets.Add().Name = "PresAbs"
Worksheets.Add().Name = "AbsPres"
Worksheets.Add().Name = "DataDec"
Worksheets.Add().Name = "DataJune"
'Define the active workbook
Set wbAnalysis = ThisWorkbook
'Define the first database. Copy/paste the sheet and close them afterwards.
Set wbDec = Workbooks.Open(Filename:="F:\Risk_Management_2\Embedded_Value\2015\20151231\Data\DLL\Master Scala\Extract.xlsx")
wbDec.Sheets("SCALA").Range("A1").CurrentRegion.Copy
wbAnalysis.Sheets("DataDec").Range("A1").PasteSpecial xlPasteValues
wbDec.Close
'We have to do the same for the other database. We cannot do it at the same time, because both files have the same name,
'and can't be opened at the same time.
Set wbJune = Workbooks.Open(Filename:="F:\Risk_Management_2\Embedded_Value\2016\20160630\Data\DLL\Master Scala\extract.xlsx")
wbJune.Sheets("SCALA").Range("A1").CurrentRegion.Copy
wbAnalysis.Sheets("DataJune").Range("A1").PasteSpecial xlPasteValues
wbJune.Close
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Sub Compare()
Dim DataDec As Worksheet, DataJune As Worksheet
Dim lastRowDec As Long
Dim lastRowJune As Long
Dim lastRowPresAbs As Long
Dim lastRowPresPres As Long
Dim lastRowAbsPres As Long
Dim foundTrue As Boolean
Dim i As Long, j As Long, k As Long, l As Long
'Define the last row of the different sheets
lastRowDec = Sheets("DataDec").Cells(Sheets("DataDec").Rows.Count, "B").End(xlUp).Row
lastRowJune = Sheets("DataJune").Cells(Sheets ("DataJune").Rows.Count, "B").End(xlUp).Row
lastRowPresAbs = Sheets("PresAbs").Cells(Sheets("PresAbs").Rows.Count, "B").End(xlUp).Row
lastRowPresPres = Sheets("PresPres").Cells(Sheets ("PresPres").Rows.Count, "B").End(xlUp).Row
lastRowAbsPres = Sheets("AbsPres").Cells(Sheets("AbsPres").Rows.Count, "B").End(xlUp).Row
'Compare the file numbers in column B of both sheets. If they are the same, copy/paste the entire row to sheet PresPres,
'if they are not, copy/paste the entire row to sheet PresAbs.
For i = 1 To lastRowDec
foundTrue = False
For j = 1 To lastRowJune
If Sheets("DataDec").Cells(i, 1).Value = Sheets("DataJune").Cells(j, 1).Value Then
foundTrue = True
Sheets("PresPres").Rows(lastRowPresPres + 1) = Sheets("DataDec").Rows(i)
lastRowPresPres = lastRowPresPres + 1
Exit For
End If
Next j
If Not foundTrue Then
Sheets("DataDec").Rows(i).Copy Destination:= _
Sheets("PresAbs").Rows(lastRowPresAbs + 1)
lastRowPresAbs = lastRowPresAbs + 1
End If
Next i
'Look if there are file numbers that are only present in June's database. If so, copy/paste entire row to sheet AbsPres.
For k = 1 To lastRowJune
foundTrue = False
For l = 1 To lastRowDec
If Sheets("DataJune").Cells(k, 1).Value = Sheets("DataDec").Cells(l, 1).Value Then
foundTrue = True
Exit For
End If
Next l
If Not foundTrue Then
Sheets("DataJune").Rows(k).Copy Destination:= _
Sheets("AbsPres").Rows(lastRowAbsPres + 1)
lastRowAbsPres = lastRowAbsPres + 1
End If
Next k
'Stop screen from updating to speed things up.
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
I've added some comments to explain what I'm trying to do. I'm relatively new to VBA so I believe I'm not coding very efficient.
Could someone have a look and try to make it work?
Basically what your are doing is comparing 2 column of elements, you want to know when:
an element is in both columns
an element is only in the first column
an element is only in the second column
To do that, your solution do:
For each element in column 1,
Find if there is this element in column 2
If found, it is in both, if not, it's just in 1
Continue to next element in column 1
Do quite the same with the element of the column 2
So basically, your examining column 2 for each element of column 1
And the same for the column 1 with the element of column 2
if we consider n the length of column1 and m the length of column2.
That is roughly 2*m*n comparison.
That's a lot !
My solution:
You are looking for numbers in column B.
Therefore you can sorted both sheet base on the value in column B
Then you can:
Create counter1 and counter2 referring to the current row in sheet1 and sheet2
Compare the value of sheet1.Value('B' + counter1) to sheet2.Value('B' + counter2)
Then you have 3 choice :
a) That is the same value, then copy the line in the right file and increments both counter
b) Value from sheet1 is greater, then you will never find the value from sheet2 in sheet1. So copy the line of sheet2 in the right file and increment only the counter2
c) The opposite
Do that until counter1 or counter2 is at the end.
As it is possible that both won't be at the end at the same time, you will have to copy the remaining lines in the right file as they will never be in the "finished" sheet.
With that solution, you will only read each "column" once ! So roughly about m+n comparison :)
You win a lot of time :)
With M=n=90 000:
you have a solution with about m*n=8 100 000 000 comparison
the other solution is just about 180 000 comparison
This should be the fastest approach as copying all data at once is much faster than copying it by row.
Select both columns > Home tab > Conditional Formatting > Highlight Cell Rules > Duplicate Values...
Now you need a filter from Data > Filter, but for that you will need to insert a header row above the numbers. After you have the filter, you can click on the second column filter and Filter by Color. Now you can copy the visible cells to wherever you copy the duplicates. I recommend sorting by color too before copying as copying one contiguous area should be a bit faster.
You can use the same method for the other two cases by filtering the columns with Filter by Color > No Fill.
Before you Record Macro of the process you can select View tab > Macros > Use Relative References.
Edit
I think I misunderstood the question. This method needs both columns to be next to each other, so if they are in separate sheets you can copy and insert them in column A. You can hide the column after the filter is applied. Then you can delete the column and header rows if needed.
Similar approach without conditional formatting is to inset a column with a helper function that checks if the id exists in the other sheet, but I think it will be a bit slower. For example:
= CountIf( Sheet2!A1:A1234, B2 )
I received an answer to my question on the Mr. Excel forum:
http://www.mrexcel.com/forum/excel-questions/963415-visual-basic-applications-speed-up-macro-large-file.html
Thanks for your answers!
I have 100 tabs which are financials of different facilities (all financial tabs are identical) and they list the revenue, expenses, etc down column A. I want to write a Macro or something that can take each tab and put that information horizontally into a summary tab so that my tab names will be going down column A with the revenue expenses, etc going across the top in row 1. I can write a simple macro that does 1 sheet at a time but I can't seem to get it to loop so it will continue doing the rest of the worksheets in the book. Does anyone know the code for this?
Thanks!
Jeff
You are going to need some variation on this concept: If you could provide the information requested in the comments of the OP, it could be more accurate. This example is assuming your summary info on each page is Row 2. If it isn't look for the line saying "assuming your data is row 2" and change the row from 2 to the actual row.
Also, you might have issues if you have other sheets in the Workbook that aren't to be included in the summary. If I knew how you named your sheets, I could help. You could test each of the sheets in the workbook for something, like if it is hidden or not. Or just check the name.. Do the first characters of ws.Name = something specific? You would need to un-Comment the IF statement and the End If, and include a real test for the IF at that stage.
As is, this will include EVERY Sheet, including the Summary Sheet that you are creating. It will involve you going through the Summary sheet and making sure only sheets you want got copied.
Sub SummaryBuilder()
Dim ws As Worksheet
Dim lastCol As Long
Dim lRow As Long
'Set your Summary Sheet up with Header Rows to match your Source Sheets
lastCol = Sheets("Summary").Cells(1, Columns.Count).End(xlToLeft).Column
lRow = 2
For Each ws In ActiveWorkbook.Worksheets
'If ws passes test Then 'If Left(ws.Name, 4) = "2014" for example or If ws.Visible = True
Sheets("Summary").Cells(lRow, 1) = ws.Name 'Sets Column A with NAME of Sheet
For lCol = 2 To lastCol 'Loops through all summary columns
Sheets("Summary").Cells(lRow, lCol) = Sheets(ws).Cells(2, lCol) 'Assuming your data is Row 2
Next lCol
lRow = lRow + 1
'End If
Next ws
End Sub
Here is my code :
Dim i As Integer, a As Integer, rowsInThere As Integer, rowsInI As Integer
Dim ws As Worksheet, b As Integer
Dim x As Integer, z As Integer
Dim total As Integer
Dim value As String
rowsInProjects = Sheets("Projects").UsedRange.Rows.Count
z = 3
Worksheets("Summary_Sheet (2)").Range("b5:b50").ClearContents
Worksheets("Summary_Sheet (2)").Range("c5:c50").ClearContents
Worksheets("Summary_Sheet (2)").Range("d5:d50").ClearContents
Worksheets("Summary_Sheet (2)").Range("e5:e50").ClearContents
Worksheets("Summary_Sheet (2)").Range("F5:F50").ClearContents
Worksheets("Summary_Sheet (2)").Range("G5:G50").ClearContents
Worksheets("Summary_Sheet (2)").Range("H5:H50").ClearContents
For a = 1 To rowsInProjects
value = Worksheets("Projects").Cells(a, 1).value
Worksheets("Summary_Sheet (2)").Cells(a + 4, 2).value = value
For i = 5 To Worksheets.Count
rowsInI = Worksheets(i).UsedRange.Rows.Count
For x = 1 To rowsInI
If Worksheets(i).Cells(x + 8, 3).value = value Then
total = total + Worksheets(i).Cells(x + 8, 6).value
End If
Worksheets("Summary_Sheet (2)").Cells(i, z).value = total
Next x
z = z + 1
Next i
z = 3
Next a
There error arises on the total = total + ... line. What my code is doing is copying a list of projects from a worksheet into a new one.
It then has to search through the other worksheets for each of the project names added. Each of the other worksheets will have 2-3 records with the project name. I want to get the total cost of the project from each worksheet and then insert it back into the orginal file.
Steps:
1. Create list of projects
Iterate through List
iterate through each worksheet
totaling values from matching projects
Insert value back into project list
J O'Brien, Choate and Townsend are the 3 worksheets
This is the Choate worksheet
Is this approach right for what I am trying to achieve?
you're probably overflowing the max size of an Integer, which is 32767. Try using a long for your range loop counter instead.
This would apply to a, x, z and rowsInI
You also asked if this (your method) was the right approach, Yours works so yes it is. However, it could possibly be optimised.
For every item in your project list, you're iterating over every row in your data sheet in all of your data sheets.
Which, depending on the number of rows in each sheet, is a fair few! (its at least Projects * Rows * 3)
I dont know if your "projects" list is one you generate per-run, so you only get a few projects or if its just everything you've got.
Hopefully the code below makes some sense, if you decide to give it a go please make sure you run it on a copy of your data! It's an example and it only dumps the result to the debug window.
The code below (which may not function perfectly as I might have got the columns and rows wrong) will loop over each sheet once and calculate a per-sheet total for each project (allowing for multiple instances of the same project in a single sheet, if this is possible in your data)
Sub Main()
Dim Projects As Object
'Dim Projects As Scripting.Dictionary
Set Projects = CreateObject("Scripting.Dictionary")
'Set Projects = New Scripting.Dictionary
Dim Project As String
Dim Sheets() As String
Dim Name As String
Dim Sheet As Worksheet
Dim SheetIndex As Integer
Dim ProjectColumn As Variant
Dim TotalColumn As Variant
Dim Index As Integer
Dim Max As Long
Dim MaxRow As Long
' You'll need to put your sheet names below
' not very nice, just a way to predefine an array containing sheet names
Sheets = Split("Sheet1,Sheet2,Sheet3", ",")
' loop over all the sheets
For SheetIndex = 0 To UBound(Sheets)
' get a reference to the sheet were looking at
Set Sheet = ThisWorkbook.Worksheets(Sheets(SheetIndex))
' calculate the last row in the workbook (as using UsedRange isnt always right)
MaxRow = Sheet.Cells(Sheet.Rows.Count, 3).End(xlUp).Row
' get the data were looking for
' the 9 in the next 2 lines might be wrong, it should be the row# for the first data row
Set ProjectColumn = Sheet.Range(Sheet.Cells(9, 3), Sheet.Cells(MaxRow, 3)) ' the 9 here might be wrong!
Set TotalColumn = Sheet.Range(Sheet.Cells(9, 6), Sheet.Cells(MaxRow, 6)) ' again, 9
Max = MaxRow - 8 ' adjust the max row to account for the +8 header cells above the data
For Index = 1 To Max
' loop over all the projects in the current sheet
Project = ProjectColumn(Index, 1)
' this allows for multiple instances of the same project per sheet (no idea if this occurs in your data)
If Projects.Exists(Project) Then
If Projects(Project).Exists(Sheets(SheetIndex)) Then
' update the total
Projects(Project)(Sheets(SheetIndex)) = Projects(Project)(Sheets(SheetIndex)) + TotalColumn(Index, 1)
Else
' inclue the total for the sheet
Projects(Project).Add Sheets(SheetIndex), CLng(TotalColumn(Index, 1))
End If
Else
' new project, add it and the total value for the current sheet
'Projects.Add Project, New Scripting.Dictionary
Projects.Add Project, CreateObject("Scripting.Dictionary")
Projects(Project).Add Sheets(SheetIndex), CLng(TotalColumn(Index, 1))
End If
Next Index
Set ProjectColumn = Nothing
Set TotalColumn = Nothing
Next SheetIndex
' Projects now contains a list of all projects, and the totals for your sheets.
' Projects
' - Project Name
' - - Sheet Name - Sheet Total
' dump the data to the immediate window in the vba editor
For Each Key In Projects.Keys
For Each SubKey In Projects(Key).Keys
Debug.Print Key & ", " & SubKey & " = " & Projects(Key)(SubKey)
Next SubKey
Next Key
End Sub
Using this you'd only need to iterate over each sheet once and then one further iteration over the projects sheet to extract the required totals from the result.
My normal approach is to hold all the available data in one worksheet so I can analyse it using pivot tables. If I then need to create a non-pivot table worksheet, I then use VBA to copy and PasteSpecial the pivot table as a regular range.
If there's a really good reason to hold this data in 3 separate worksheets, I would amalgamate the 3 sheets using VBA (basically copying and pasting all the data into a single worksheet with one set of column headers), adding an extra column that contains either "Choate", "OBrien" or "Townsend" during the copy/paste process, then creating a pivot table from the resulting amalgamation.
I use this approach a lot - all my data is standardised and I can filter the pivot table as required - by date, project, manager/salesperson/creator (Choate, Townsend and O'Brien), currency, or whatever.
I'm afraid this isn't strictly speaking an answer to your question, more a suggestion of a different approach. Of course, I don't know the circumstances of how you get this data, so it may not be feasible for you.
Could someone please help me with some VBA code.
I am trying to copy 2 ranges of cells between workbooks (both workbooks should be created beforehand as i don't want the code to create a new workbook on the fly).
Firstly I need to copy these ranges-
From 'Sheet 3' of booka.xls, Range: Cell H5 to the last row in column H with data
copy this to 'Sheet 1' of bookb.xls, starting in Cell B2 for as many cells down in the B column
Secondly I need to copy these ranges-
From 'Sheet 3' of booka.xls, Range: Cell K5 to the last row in column K with data
copy this to 'Sheet 1' of bookb.xls, starting in Cell D2 for as many cells down in the D column
Here is what I have so far:
Sub CopyDataBetweenBooks()
Dim iRow As Long
Dim wksFr As Worksheet
Dim wksTo As Worksheet
wksFr = "C:\booka.xls"
wksTo = "C:\bookb.xls"
Set wksFrom = Workbooks(wksFr).Worksheets("Sheet 3")
Set wksTo = Workbooks(wksTo).Worksheets("Sheet 1")
With wksFrom
For iRow = 1 To 100
.Range(.Cells(iRow, 8), .Cells(iRow, 9)).Copy wksTo.Cells(iRow, 8)
Next iRow
End With
End Sub
Assuming you have the reference to wksFrom and wksTo, here is what the code should be
wksFrom.Range(wksFrom.Range("H5"), wksFrom.Range("H5").End(xlDown)).Copy wksTo.Range("B2")
wksFrom.Range(wksFrom.Range("K5"), wksFrom.Range("K5").End(xlDown)).Copy wksTo.Range("D2")
Here's an example of how to do one of the columns:
Option Explicit
Sub CopyCells()
Dim wkbkorigin As Workbook
Dim wkbkdestination As Workbook
Dim originsheet As Worksheet
Dim destsheet As Worksheet
Dim lastrow As Integer
Set wkbkorigin = Workbooks.Open("booka.xlsm")
Set wkbkdestination = Workbooks.Open("bookb.xlsm")
Set originsheet = wkbkorigin.Worksheets("Sheet3")
Set destsheet = wkbkdestination.Worksheets("Sheet1")
lastrow = originsheet.Range("H5").End(xlDown).Row
originsheet.Range("H5:H" & lastrow).Copy 'I corrected the ranges, as I had the src
destsheet.Range("B2:B" & (2 + lastrow)).PasteSpecial 'and destination ranges reversed
End Sub
As you have stated in the comments, this code above will not work for ranges with spaces, so substitute in the code below for the lastrow line:
lastrow = originsheet.range("H65536").End(xlUp).Row
Now ideally, you could make this into a subroutine that took in an origin workbook name, worksheet name/number, and range, as well as a destination workbook name, worksheet name/number, and range. Then you wouldn't have to repeat some of the code.
You can use special cells like Jonsca has suggested. However, I usually just loop through the cells. I find it gives me more control over what exactly I am copying. There is a very small effect on performance. However, I feel that in the office place, making sure the data is accurate and complete is the priority. I wrote a response to a question similar to this one that can be found here:
StackOverflow - Copying Cells in VBA for Beginners
There is also a small demonstration by iDevelop on how to use special cells for the same purpose. I think that it will help you. Good luck!
Update
In response to...
good start but it doesn't copy anything after the first blank cell – trunks Jun 9 '11 at 5:08
I just wanted to add that the tutorial in the link above will address the issue brought up in your comment. Instead of using the .End(xlDown) method, loop through the cells until you reach the last row, which you retrieve using .UsedRange.Rows.Count.