I've managed to put together a VBA script to automate the concatenating of strings in adjoining columns for the purpose of working with a Python/Applescript tool I've programmed for assisting with renaming of thousands of pdfs files.
There are four columns being worked on.
Col A - an article number for a particular computer in the organisation. 5 digits
Col B - the manufacturer's serial number for a given article number. Several digits long, but only the first 12 are important.
Col C - has a CONCATENATE function to derive the original name of a given PDF file related to a given serial number. "=CONCATENATE(LEFT(B2,12)&".pdf")"
Col D - has another CONCATENATE to help to define the new name of the PDF. "=CONCATENATE(A2&"-"&LEFT(B2,12)&".pdf")"
An example of what I am talking about...
"DMPQ44VZF4YD.pdf" gets renamed to "45872-DMPQ44VZF4YD.pdf"
I've recored a macro to record my using the CONCATENATE functions in cols C and D, then using autofill to drag that down manually to the bottom of the spreadsheet, then copying and pasting those (by value) to columns E and F and then deleting the columns A - D (inclusive) to leave behind only what I want. A messy way of doing things but it gets the job done.
Each Excel file can have a differing number of records/rows. I've arbitrarily set it to 1500 items in the VBA script. Please take a look...
Sub Macro1()
Macro1 Macro
Range("C2").Select
ActiveCell.FormulaR1C1 = "=CONCATENATE(LEFT(RC[-1],12)&"".pdf"")"
Range("C2").Select
Selection.AutoFill Destination:=Range("C2:C1500"), Type:=xlFillDefault
Range("C2:C174").Select
Range("D179").Select
ActiveWindow.SmallScroll Down:=-364
Range("D2").Select
ActiveCell.FormulaR1C1 = "=CONCATENATE(RC[-3]&""-""&LEFT(RC[-2],12)&"".pdf"")"
Range("D2").Select
Selection.AutoFill Destination:=Range("D2:D1500"), Type:=xlFillDefault
Range("D2:D174").Select
ActiveWindow.SmallScroll ToRight:=-2
ActiveWindow.SmallScroll Down:=-528
Columns("C:D").Select
Selection.Copy
Columns("F:F").Select
Selection.PasteSpecial Paste:=xlValues, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=False
Columns("A:E").Select
Application.CutCopyMode = False
Selection.Delete Shift:=xlToLeft
Rows("1:1").Select
Selection.Delete Shift:=xlUp
Range("D5").Select
Dim Rng As Range
For Each Rng In ActiveSheet.UsedRange
If Rng.Value2 = ".pdf" Or Rng.Value2 = "-.pdf" Then Rng.ClearContents
Next Rng
End Sub
It works well but can take a while to process (due to the arbitrarily high number of possible records in a given sheet, perhaps as well as the 'for' loop at the end to get rid of any trailing ".pdfs" and "-.pdfs" that are a hangover from otherwise empty cells affected by the CONCATENATE function). Is there any way of making the more efficient when dealign with a small number of records or perhaps making it easily more scalable?
Here is a link to the code if that helps.
Thanks once again.
Will
You may give this a try...
Sub RenamePDFFiles()
Dim lr As Long
lr = Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
With Application
.Calculation = xlCalculationManual
.EnableAutoComplete = False
.ScreenUpdating = False
End With
Range("C2:C" & lr).Formula = "=IF(B2="""","""",LEFT(B2,12)&"".pdf"")"
Range("C2:C" & lr).Value = Range("C2:C" & lr).Value
Range("D2:D" & lr).Formula = "=IF(AND(A2="""",B2=""""),"""",A2&""-""&LEFT(B2,12)&"".pdf"")"
Range("D2:D" & lr).Value = Range("D2:D" & lr).Value
Columns("A:B").Delete
With Application
.Calculation = xlCalculationAutomatic
.EnableAutoComplete = True
.ScreenUpdating = True
End With
End Sub
For formula in column D, I have used AND function for the logical test so that if both columns A and B are empty, the corresponding cell in column D will also be empty. If required, change it to OR condition.
Related
Working on a function to put the filename in a specific column (P) of a file. I've got this running if I specify the cells to put the filename in (e.g. P1:P5).
However, I want to get this to run in the P column, but for all rows that have data in the A column.
I know I could do it for just the whole P column, but i dont want it to run on empty rows (they're of no use)
Code I have so far:
Sub Save_files()
Cells.Select
Selection.Copy
Workbooks.Add
ActiveSheet.Paste
Rows("1:3").Select
Application.CutCopyMode = False
Selection.Delete Shift:=xlUp
Range("P2").Select
ActiveCell.FormulaR1C1 = "=CELL(""filename"")"
Range("P2").Select
Selection.Copy
Range("p1:p5").Select
ActiveSheet.Paste
Application.CutCopyMode = False
Range("D4").Select
End Sub
I want to replace Range("p1:p5").Select with something that selects every P cell that is on a row with data in A of the same row.
Things to note:
Column A will always have data
Columns B through to O may or may not have data
Thanks in advance!
I changed:
Range("p1:p5").Select
to:
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Range("p1:p" & LastRow).Select
Title isn't the best so here a an overview.
I'm using VBA to copy select columns from one workbook to another, as what will be part of a larger automated program.
On the Workbook I am copying from, there are different sheets containing a "Stock Number" column. When pasting into my other workbook, I am trying to get these columns to merge into 1 single column (pasting below the last entry from the first sheet and so on).
Here is my current code:
Sub import_adam_article()
Windows( _
"copyfrom.xlsx" _
).Activate
Columns("F:G").Select
Selection.Copy
Windows("pasteinto.xlsx").Activate
Columns("A:A").Select
ActiveSheet.Paste
Windows( _
"copyfrom.xlsx" _
).Activate
Columns("N:N").Select
Application.CutCopyMode = False
Selection.Copy
Windows("pasteinto.xlsx").Activate
Columns("C:C").Select
ActiveSheet.Paste
Rows("1:1").Select
Selection.Delete Shift:=xlUp
NextRow = Range("A1").End(xlDown).Row + 1
Windows( _
"copyfrom.xlsx" _
).Activate
Columns("F:G").Select
Selection.Copy
Windows("pasteinto.xlsx").Activate
Range("A" & (NextRow)).Select
ActiveSheet.Paste
[A:C].Select
With Selection
.NumberFormat = "General"
.Value = .Value
End With
End Sub
The difficulty is that the amount of Stock Numbers will change every new file that comes through, so it needs to be able to adjust to varying amounts.
I can't seem to find a way to make it work and I've tried searching for answers elsewhere.
EDIT: The current issue with the code that it is selecting the next empty row to paste into, but only that cell, not a variable length down as required by the copyfrom column.
The problem: the dates displayed in my excel files are not the actual dates entered into each cell. For example, I might visually see 04/18/2019 but when I click on the cell, it shows 04/18/2019 06:48:00PM. This becomes an issue when I run the macro I use to convert my xlsx file to txt. The txt file generated will show 04/19/2019 06:48:00PM in the date field rather than what I want, 04/18/2019.
The current fix I'm using is to insert columns next to the date columns, copy the date columns and paste values into the new inserted columns, and then deleting the inserted columns. This was accomplished through recording a macro (I'm a noob if you couldn't tell).
That rudimentary fix does actually work quite well at generating the output I need. Except one problem: if there are any blanks in the date columns, it pastes in 01/01/1900. Blanks in date columns is fairly common in the work I do so this is a major problem.
I tried adding this If in after the inserting/pasting values/deleting code:
'Fix Date format in columns A and C
'
Columns("B").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Columns("E").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Range("B1").Select
ActiveCell.FormulaR1C1 = "=TEXT(RC[-1],""mm"/"dd"/"yyyy"")"
Range("E1").Select
ActiveCell.FormulaR1C1 = "=TEXT(RC[-1],""mm"/"dd"/"yyyy"")"
Range("B1").Select
Selection.AutoFill Destination:=Range("B1:B" & EndRow)
Range("E1").Select
Selection.AutoFill Destination:=Range("E1:E" & EndRow)
Range("B1:B" & EndRow).Select
Selection.Copy
Range("A1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Range("E1:E" & EndRow).Select
Selection.Copy
Range("D1").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Columns("E").Select
Application.CutCopyMode = False
Selection.Delete Shift:=xlToLeft
Columns("B").Select
Application.CutCopyMode = False
Selection.Delete Shift:=xlToLeft
If Range("A1:A") = "01/01/1900" Then ClearContents
'
But I get this error "Compile Error: Sub or Function not defined", with the word Range within the If statement highlighted. I don't understand why it isn't recognizing range since it is used just a few lines up without issue. I have to be doing something wrong but I'm lost. Any ideas?
PS - The If is just a means to an end as far as remedying the blanks, but I'm sure there's a better way to convert these date columns to text or to the date format I want. If you have any recommendations for some easier way to achieve the same results I am all ears.
Thanks guys I really appreciate it!
Range("A1:A")
That's a google-sheets reference for 'everything from A1 down to the last used cell in column A' not a VBA style. Either reference the entire column (e.g. Range("A:A")) or modify the end row with your EndRow var (e.g. Range("A1:A" & EndRow)).
The formating mask used in the VBA application of the TEXT formula seems wrong. When using a quote within a quoted string,double up the quote once every time it ix used. The TEXT formula returns a string (i.e. text); you do not need to attempt any further conversion.
ActiveCell.FormulaR1C1 = "=TEXT(RC[-1],""mm/dd/yyyy"")"
Both xlR1C1 and relative styled xlA1 formulas be applied to all cells at once. A Union of the target cells in columns B and E further expedites the formula insert.
Range("B1:B" & EndRow & ",E1:E" & EndRow).formular1c1 = "=TEXT(RC[-1],""mm/dd/yyyy"")"
You cannot directly compare a multiple cell range to a single value. Either loop through the cells or use a wide-range comparison like Range.Find, the worksheet's MATCH function or an AutoFilter to expose the matching cells as xlCellTypeVisible.
dim m as variant
m = application.match("01/01/1900", ActiveSheet.Range("A:A"), 0)
do while not iserror(m)
ActiveSheet.Cells(m, "A").clearcontents
m = application.match("01/01/1900", ActiveSheet.Range("A:A"), 0)
loop
If you've read down this far, the easiest method of stripping the date out of a date-time is Text-to-Columns. Simply discard anything after the 10th character (e.g. mm/dd/yyyy from mm/dd/yyyy hh:mm amp/pm).
With ActiveSheet.Columns("A")
.TextToColumns Destination:=.Cells(1), DataType:=xlFixedWidth, _
FieldInfo:=Array(Array(0, 1), Array(10, 9))
.NumberFormat = "mm/dd/yyyy"
End With
I would humbly suggest you take some time to investigate How to avoid using Select in Excel VBA. If you continue to develop VBA to make your Office applications more efficient, you should start using recommended methods as soon as possible.
I can see the question has been asked a couple of times in the forum, but as someone who doesn't really know macro, I cannot implement the code provided in the answers.
So I have recorded the below macro.
Macro6 Macro
'
'
Range("C2").Select
ActiveCell.FormulaR1C1 = _
"=INDEX('Account codes'!C[-1],MATCH(DATA!RC[4],'Account codes'!C[-2],0))"
Range("C2").Select
ActiveCell.FormulaR1C1 = _
"=INDEX('Account codes'!C2,MATCH(DATA!RC[4],'Account codes'!C1,0))"
Range("C2").Select
Selection.Copy
Range("E2").Select
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = _
"=INDEX('Account codes'!C2,MATCH(DATA!RC[4],'Account codes'!C1,0))"
Range("E2").Select
ActiveCell.FormulaR1C1 = _
"=INDEX('Account codes'!C2,MATCH(DATA!RC[3],'Account codes'!C1,0))"
Range("E2").Select
Selection.AutoFill Destination:=Range("E2:E4")
Range("E2:E4").Select
Range("C2").Select
Selection.AutoFill Destination:=Range("C2:C4")
Range("C2:C4").Select
End Sub
In simple terms, I want Selection.AutoFill Destination:=Range("E2:E4") to fill down to the last row of data, and not just to E4. That is because the data can change in size.
How do I do this?
You seem to be overwriting some of the formulas with different relative/absolute references. It really doesn't seem to matter which is used as far as relative/absolute addressing is concerned but you have two distinct formulas for column E which reference two different cells on the data worksheet. I'll use the latter of the two.
Put all of the formulas into all of the cells at once. AutoFill is overrated and functionally redundant in this case.
The formulas for column C seem to be using a relative reference to a cell in column F. You can use that column to determine the last row to put formulas into.
sub buildFormulas()
dim lr as long
with worksheets("data")
lr = .cells(.rows.count, "F").end(xlup).row
.range(.cells(2, "C"), .cells(lr, "C")).formular1c1 = _
"=INDEX('Account codes'!C2, MATCH(RC7, 'Account codes'!C1, 0))"
.range(.cells(2, "E"), .cells(lr, "E")).formular1c1 = _
"=INDEX('Account codes'!C2, MATCH(RC8, 'Account codes'!C1, 0))"
end with
end sub
I have the following macro defined which inserts rows into a sheet. After the rows are inserted at specified start addresses, the various ranges are then converted into Tables. My initial thoughts are that the issue lies with the use of xlDown - since this is the place in code where rows are inserted.
At present I have 7 such ranges, however the issue is that first three always have an additional row inserted - this was previously working with no issues, so the fact that its misbehaving is a puzzle to me.
The remaining ranges are correct. The tableStartAdress refers to named ranges whose values correspond to the first cell below the green title, ie A4, A12 etc. rowsToInsert for this example is always 38.
Sub InsertTableRows(tableStartAdress As String, rowsToInsert As Integer)
Dim i As Integer
Dim rowToCopy As String
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.DisplayAlerts = False
Range(tableStartAdress).Offset(1, 0).Rows.Copy
rowToCopy = Range(tableStartAdress).Offset(1, 0).row & ":" & _
Range(tableStartAdress).Offset(1, 0).row
Rows(rowToCopy).Select
Selection.Copy
Range(tableStartAdress).Offset(1, 0).Select
ActiveCell.Resize(rowsToInsert, 1).Select
Selection.Insert Shift:=xlDown
Application.CutCopyMode = False
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
The following pictures depict what I mean.
Before:
.
Once data is populated the first three range/tables have an extra row
, ,
Whilst the remainder are correct
I'd suggest simplifying your code to start. (Might help you track down were things are going wrong.) Since you don't need to select a range before you do something with it....
rowToCopy = Range(tableStartAdress).Offset(1, 0).Row & _
":" & Range(tableStartAdress).Offset(1, 0).Row
Rows(rowToCopy).Select
Selection.Copy
Range(tableStartAdress).Offset(1, 0).Select
ActiveCell.Resize(rowsToInsert, 1).Select
Selection.Insert Shift:=xlDown
is the same as...
Range(tableStartAdress).Offset(1, 0).EntireRow.Copy
Range(tableStartAdress).Offset(1, 0).Resize(rowsToInsert, 1).Insert Shift:=xlDown
which is much easier to look at. A couple thoughts: First, are you sure that tableStartAddress is really always a single cell (and the correct cell)? Are you sure that rowsToInsert is always 38? Beyond that, your code as it's currently written is copying an entire row and inserting it into a range that's theoretically 38 rows by 1 column. I would recommend rewriting this so you first insert however many rows you want, then fill the 38 x 1 range with the data that belongs there.