I have some number in a column in excel like this:
201
202
208-1
210
when I sort this column, sorted column is like below:
201
202
210
208-1
How do I sort this column? I want the sorted column becomes like this:
201
202
208-1
210
or
210
208-1
202
201
One option is a hidden column, say if your values listed above were in A2:A5, insert a column to the right and in B2 enter the formula below and copy this down to the other B cells:
=IFERROR(VALUE(LEFT(A2,FIND("-",A2)-1)),VALUE(A2))
or alternative suggested by #Gary'sStudent that handles values after the hyphen as well by converting to decimals:
=IFERROR(VALUE(SUBSTITUTE(A2,"-",".")),VALUE(A2))
This strips out the number up to the first hyphen. Select all of the values in the two columns, select sort and then sort by columnB. you can then right click on column B and select hide.
If you do not want to use hidden columns then I think your only option would be to write some VBA to do a custom sort procedure. You would then also need a way of triggering this such as a control in the spreadsheet or just a keyboard shortcut.
UPDATE
I have had a go at the VBA procedure, it was not as straight forward as I expected so it may be that there is an easier way to do this.
The basic steps I went through are to prompt the user for a cell range (you just have to select the cells when prompted), store the values to an array of strings, create an equivalent numeric array where the hyphens are replaced by decimal points, sort the numeric array and then loop through the initial range pasting in the values in order.
I was surprised to find out that VBA does not have a built in method for sorting an array, but found some code that could be used here. This creates a temp worksheet and uses the worksheet function, there is also code there for a pure VBA solution but it's pretty lengthy.
To create the VBA procedure you will need to open the VBA editor with alt F11 and create a new module then paste the code below into a module (create a new module - right click on modules on right and insert) then paste in the code below.
The procedure that you need to call is sort_with-hyphens.
You will need to create a control or create a keyboard short cut to trigger this. For either you will need to enable the developer ribbon tab through File>Options. For the control do developer>control>button and right click to assign a macro. For the keyboard short cut developer>Macros select the VBA procedure name from the list of macros and select options.
Sub sort_with_hyphens()
On Error GoTo sort_with_hyphens_err
Dim vRange As Range
Dim vCell As Variant
Dim vStrArray(), vNumArray()
Dim i As Long, vStart As Long, vEnd As Long
Dim vStep As String: vStep = "Initialising values"
' prompt user to specify range
Set vRange = Application.InputBox("Select a range to be sorted", _
"Obtain Range Object", _
Type:=8)
vStrArray = vRange.Value
vStart = LBound(vStrArray)
vEnd = UBound(vStrArray)
ReDim vNumArray(vStart To vEnd)
vStep = "Populating Numeric Array"
' loop through array copying strings with hyphen to decimal equivalent
For i = vStart To vEnd
vNumArray(i) = Val(Replace(vStrArray(i, 1), "-", "."))
Debug.Print i, vNumArray(i)
Next i
' sort numeric array
vStep = "Sorting Numeric Array"
SortViaWorksheet vNumArray
' write out sorted values
vStep = "Writing out Sorted Values"
For i = vStart To vEnd
' convert back to string and switch periods back to hyphens
vRange.Cells(i, 1).Value = Replace(CStr(vNumArray(i)), ".", "-")
Next
sort_with_hyphens_exit:
Exit Sub
sort_with_hyphens_err:
If vStep = "Writing out Sorted Values" Then
MsgBox ("An error has occurred, the original values will " & _
"be restored. Error in Step: " & vStep & vbCrLf & _
"Error Details:" & vbCrLf & err.Number & " - " & _
err.Description)
For i = vStart To vEnd
' replace with original value incase of error
vRange.Cells(i, 1).Value = vStrArray(i)
Next
Else
MsgBox ("An error has occurred in Step: " & vStep & vbCrLf & _
"Aborting sort procedure." & vbCrLf & _
"Error Details:" & vbCrLf & err.Number & " - " & _
err.Description)
End If
End Sub
Sub SortViaWorksheet(pArray)
Dim WS As Worksheet ' temporary worksheet
Dim R As Range
Dim N As Long
Application.ScreenUpdating = False
' create a new sheet
Set WS = ThisWorkbook.Worksheets.Add
' put the array values on the worksheet
Set R = WS.Range("A1").Resize(UBound(pArray) - LBound(pArray) + 1, 1)
R = Application.Transpose(pArray)
' sort the range
R.Sort key1:=R, order1:=xlAscending, MatchCase:=False
' load the worksheet values back into the array
For N = 1 To R.Rows.Count
pArray(N) = R(N, 1)
Next N
' delete the temporary sheet
Application.DisplayAlerts = False
WS.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
' test/debug/confirmation
Debug.Print vbCrLf & "Sorted Array:" & vbCrLf & "------------"
For N = LBound(pArray) To UBound(pArray)
Debug.Print N, pArray(N)
Next N
End Sub
Let me know if you have any questions.
Related
I have a file which is modified through VBA.
It is concatenating three columns in the sheet to create a name.
However, another information needs to be concatenated to create the new data.
The data needs to be created by deducing something from data in another workbook.
In a scpecific column, with always the same name (but whose location can change, however in the sheet), the macro needs to look for a specific information. There can be four possibilities.
Once this possibility is identified, once the term is matched from either of these four, the VBA should increment the number in the end of the term in the workbook needs to be incremented.
The structure of is as follows in the first workbook:
Nip Nup Noupx
For "Noup" there are four cases : Noupx, Noupy, Noupu, Noupa
The VBA concatentes : NipNupNoupa
(or possibly NipNupNoupx, NipNupNoupu...)
Then the VBA should go in the other workbook, look for either the term "Noupa", "Noupu", "Noupx", "Noupy".
For each of these the specific number comming after "Noupa" (or the other) should be identified and should increment it by adding "+1".
Thus the result would be:
Noupa002 (resulting from the identification of Noupa001)
Noupu034 (resulting from the identificiation of Noupu033)
For the time being, I have the following VBA code, I do not know how to look for data in another workbook and increment it.
Sub TralaNome()
Const q = """"
' get source data table from sheet 1
With ThisWorkbook.Sheets(1).Cells(1, 1).CurrentRegion
' check if data exists
If .Rows.Count < 2 Or .Columns.Count < 2 Then
MsgBox "No data table"
Exit Sub
End If
' retrieve headers name and column numbers dictionary
Dim headers As Dictionary
Set headers = New Dictionary
Dim headCell
For Each headCell In .Rows(1).Cells
headers(headCell.Value) = headers.Count + 1
Next
' check mandatory headers
For Each headCell In Array(("Costumer", "ID", "Zone“, "Product Quali", "Spec A", "Spec B", "Spec_C", "Spec_D", "Spec_1", " Spec_2", " Spec_3", " Spec_4", " Spec_5", " Spec_6", " Spec_7", "Chiavetta", "Tipo_di _prodotto", "Unicorno_Cioccolato", “cacao tree“)
If Not headers.Exists(headCell) Then
MsgBox "Header '" & headCell & "' doesn't exists"
Exit Sub
End If
Next
Dim data
' retrieve table data
data = .Resize(.Rows.Count - 1).Offset(1).Value
End With
' process each row in table data
Dim result As Dictionary
Set result = New Dictionary
Dim i
For i = 1 To UBound(data, 1)
MsgBox "Empty row"
Exit For
result(result.Count) = _
q & "ID " & data(i, headers("ID ")) & _
q & " Tipo_di _prodotto " & data(i, headers("Tipo_di _prodotto")) & _
q & " cacao tree " & data(i, headers("Nupu")) & _
q
End Select
Next
' output result data to sheet 2
If result.Count = 0 Then
MsgBox "No result data for output"
Exit Sub
End If
With ThisWorkbook.Sheets(2)
.Cells.Delete
.Cells(1, 1).Resize(result.Count).Value = _
WorksheetFunction.Transpose(result.Items())
End With
MsgBox "Completed"
End Sub
The columns are grouped through this macro, but I need to now look in the other worksheet, increment the various Noupu, Noupy etc etc etc...
I think that a VBA of that sort should be used to add an incremented value :
Function GetLastRowWithData(WorksSheetNoupa As Worksheet, Optional NoupaLastCol As Long) As Long
Dim lCol, lRow, lMaxRow As Long
If NoupaLastCol = 0 Then
NoupaLastCol = wsSheet.Columns.Count
End If
lMaxRow = 0
For lCol = NoupaLastCol To 1 Step -1
lRow = wsSheet.Cells(wsSheet.Rows.Count, lCol).End(xlUp).Row
If lRow > lMaxRow Then
lMaxRow = lRow
End If
Next
GetLastRowWithData = lMaxRow
End Function
(sorry, this probably should be a comment but I don't have enough reputation as yet).
However even without checking through your code in detail, I'm seeing an exit for in the middle of a for loop without an If to avoid it in certain conditions. Presumably this means that whatever's written below that line in the loop, never gets done - nor is the loop any good for anything but the first instance. (it's the loop that's annotated 'process each row in table data)
Have you tried running this step by step? (go into the VBEditor with a test dataset open, and hit F8 or the 'step into' button in debug toolbar )
In excel I have a text box labeled textbox1. In this text box are 17 queries. They are all separated by a start and end string. Query 1 and 2 look like this in the text box as an example:
Query1_start
Select * from table1
Query1_end
Query2_start
Select * from table2
Query2_end
I am trying to go into textbox1 and select everything between Query1_start and Query1_end and copy it via VBA. I can’t seem to find the syntax for this as everything I come across relating to textbox results in selecting all which I do not want.
When I try to record a macro where I am only selecting between Query1_start and Query1_end, it just shows that I am entering the text box and selecting but not showing what I am selecting.
UPDATE:
I did find some VBA code that would help with this, however, it is contingent on selecting all from the text box and pasting it into column A.
Sub SelectBetween()
Dim findrow As Long, findrow2 As Long
findrow = Range("A:A").Find("Query1_start", Range("A1")).Row
findrow2 = Range("A:A").Find("Query1_End", Range("A" & findrow)).Row
Range("A" & findrow + 1 & ":A" & findrow2 - 1).Select
End Sub
I still need VBA to go to "textbox1", then select all within "textbox1", then paste selection into column A of sheet1.
What about something like:
Sub Test()
Dim BOX As String, STRNG As String
Dim POS1 As Long, POS2 As Long, X As Long
For X = 1 To 17
BOX = Worksheets("Blad1").TextBox1.Value
POS1 = InStr(1, BOX, "Query" & X & "_start")
POS2 = InStr(1, BOX, "Query" & X & "_End")
STRNG = Mid(BOX, Len("Query" & X & "_start") + POS1, POS2 - (POS1 + Len("Query" & X & "_start")))
Debug.Print STRNG
Next X
End Sub
In Excel VBA when using the Range.Find method the output is the address of the found cell relative to the sheet (ie: SomeSheet.Range("C3")). Is there a way to return the position of the found cell within the range being searched? (ie: SearchedRange.Cells(2,4))
I am presently handling this with the snippet below but was wondering if there is a built in method or property that will handle this.
RowInRange = SomeRange.Find("foo").Row - (SomeRange.Row - 1)
As mentioned in comments, this is just simple maths to get the answer:
Sub Test()
Dim SearchedRange As Range
Dim FoundRange As Range
Dim RelativeRangeAddress As String
Range("E15").Value = "ABC" ' Dummy some data
Set SearchedRange = Range("D5:G123") ' Dummy search area
Set FoundRange = SearchedRange.Find(What:="ABC")
'Find address relative to start of search range
RelativeRangeAddress = FoundRange.Offset(1 - SearchedRange.Row, _
1 - SearchedRange.Column).Address
MsgBox RelativeRangeAddress & " is " & _
SearchedRange.Range(RelativeRangeAddress).Value ' will display "$B$11 is ABC"
'To just find the row and column (which is probably easier)
MsgBox "Relative row is " & FoundRange.Row - SearchedRange.Row + 1
MsgBox "Relative column is " & FoundRange.Column - SearchedRange.Column + 1
End Sub
I am getting an error I can't figure out:
After I run the macro below, two certain string values are pasted into the same two cells in ALL sheets, although I am sure that the sheets are not grouped or do not contain individual code of their own. Specifically, the items "B12" and "B25" are pasted on all pages at the same cells (A29 and A30) (See code). "B12" and "B25" have nothing to do with a cell location but are just identifiers unique to my application. They are values which are copied+pasted from one sheet into another. If it is a copy+paste error in the code, then I would expect all the items to have the same error because the "algorithm" subroutine is called for every sheet.
Sometimes, this also occurs without execution of the macro. And when I try to edit my workbook back to how it was before fields were pasted over (by clicking each cell and typing what used to be there), it still makes those changes to all sheets, even though I am sure they are not grouped or running code.
' Title: DSR AutoFill Macro
Sub autofill_DSR()
' Variable Declarations:
Dim x_count As Long
Dim n As Long
Dim item_a As String
Dim item_b As String
'Dim test_string As String
' Variable Initializations:
x_count = 0
Process_Control_NumRows = 15
Electrical_NumRows = 8
Environmental1_NumRows = 17
Env2_Regulatory_NumRows = 14
FIRE_NumRows = 15
Human_NumRows = 16
Industrial_Hygiene_NumRows = 16
Maintenance_Reliability_NumRows = 10
Pressure_Vacuum_NumRows = 16
Rotating_n_Mechanical_NumRows = 11
Facility_Siting_n_Security_NumRows = 10
Process_Safety_Documentation_NumRows = 3
Temperature_Reaction_Flow_NumRows = 18
Valve_Piping_NumRows = 22
Quality_NumRows = 10
Product_Stewardship_NumRows = 20
fourB_Items_NumRows = 28
'test_string = "NN"
' Main Data Transfer Code:
Sheets(Array("SUMMARY P.1", "SUMMARY P.2", "Process Control", _
"Electrical", "Environmental1", "Env.2 - Regulatory", "FIRE", _
"Human", "Industrial Hygiene", "Maintenance_Reliability", _
"Pressure_Vacuum", "Rotating & Mechanical", _
"Facility Siting & Security", "Process Safety Documentation", _
"Temperature-Reaction-Flow", "Valve-Piping", "Quality", _
"Product Stewardship", "4B ITEMS")).Select 'Create Array of all Sheets
'Sheets(Array("Sheet1", "Sheet2", "Sheet3")).Select ' For testing
' Process Control Sheet:
For n = 0 To (Process_Control_NumRows - 1) 'Cycle 16 times for each
'item row in process controls tab
Sheets("Process Control").Activate 'Choose specific sheet
Range("D15").Select 'Choose starting cell of "Yes" column
Call Module2.algorithm(n, x_count) 'Call on subroutine (see algorithm code)
Next n 'increment index to account for offset
' Electrical Sheet:
For n = 0 To (Electrical_NumRows - 1)
Sheets("Electrical").Activate
Range("D15").Select
Call Module2.algorithm(n, x_count)
If (x_count > 21) Then 'Abort autofill if too many items to hold
Sheets("SUMMARY P.1").Activate 'on both summary pages put together (21 count)
GoTo TooMany_Xs
End If
Next n
This continues for all the sheets...
' 4B ITEMS Sheet:
For n = 0 To (fourB_Items_NumRows - 1)
Sheets("4B ITEMS").Activate
Range("D16").Select ' NOTE: Starting cell is "D16"
Call Module2.algorithm(n, x_count)
If (x_count > 21) Then
Sheets("SUMMARY P.1").Activate
GoTo TooMany_Xs
End If
Next n
If (x_count > 5) Then 'Bring user back to last logged sheet
Sheets("SUMMARY P.2").Activate
Else
Sheets("SUMMARY P.1").Activate
End If
TooMany_Xs:
If Err.Number <> 0 Then
Msg = "you put more than 21 Items on the Summary Pages." & Chr(13) & _
"Consider editing your DSR or taking some other action."
MsgBox Msg, , "Error", Err.HelpFile, Err.HelpContext
End If
End Sub
And then this following macro is located in Module2:
Sub algorithm(n As Long, x_count As Long)
'If an "x" or "X" is marked in the "Yes" column,
'at descending cells down the column offset by the for loop index, n
If (ActiveCell.Offset(n, 0) = "x" Or ActiveCell.Offset(n, 0) = "X") Then
item_a = ActiveCell.Offset(n, -3).Value ' Store Letter value
item_a = Replace(item_a, "(", "") ' Get rid of "(", ")", and " " (space)
item_a = Replace(item_a, ")", "") ' characters that are grabbed
item_a = Replace(item_a, " ", "")
item_b = ActiveCell.Offset(n, -2).Value ' Store number value
item_b = Replace(item_b, "(", "") ' Get rid of "(", ")", and " " (space)
item_b = Replace(item_b, ")", "") ' characters that are grabbed
item_b = Replace(item_b, " ", "")
x_count = x_count + 1 ' increment the total x count
If (x_count > 5) Then ' If there are more than 5 "x" marks,
Sheets("SUMMARY P.2").Activate ' then continue to log in SUMMARY P.2
Range("A18").Select ' Choose "Item" column, first cell
ActiveCell.Offset((x_count - 6), 0).Value = (item_a & item_b)
'Insert cocatenated value of item_a and item_b
'(for example "A" & "1" = "A1")
'at the cells under the "Item" column, indexed by x_count
Else ' If there are less than 5 "x" marks,
Sheets("SUMMARY P.1").Activate ' log in SUMMARY P.1
Range("A25").Select
ActiveCell.Offset((x_count - 1), 0).Value = (item_a & item_b)
End If
End If
End Sub
By selecting all the sheets in your array, you are grouping them, and anything you write to a cell in any sheet will be written to all sheets.
This is the culprit:
Sheets(Array("SUMMARY P.1", "SUMMARY P.2", "Process Control", _
"Electrical", "Environmental1", "Env.2 - Regulatory", "FIRE", _
"Human", "Industrial Hygiene", "Maintenance_Reliability", _
"Pressure_Vacuum", "Rotating & Mechanical", _
"Facility Siting & Security", "Process Safety Documentation", _
"Temperature-Reaction-Flow", "Valve-Piping", "Quality", _
"Product Stewardship", "4B ITEMS")).Select
The fact that your issue occurs even if the code you posted hasn't been run makes me think there is something else going on after you've selected all the sheets.
Note that selecting and activating are a really bad idea. Declare variables for the objects you want to work with and interact with them that way instead of selecting them.
Here is a quick example of how you can loop through all the sheets in a workbook and modify them without selecting or activating. You can modify your code to use this pattern:
Sub LoopThroughAllSheets()
Dim wb As Workbook
Dim ws As Worksheet
Set wb = ThisWorkbook
For Each ws In wb.Sheets
ws.Range("D15").Value = ws.Name
Next ws
End Sub
Please read the following to get you started on writing cleaner, more efficient VBA code:
Beginning VBA: Select and Activate
Excel macro - Avoid using Select
Sheets(Array("SUMMARY P.1", "SUMMARY P.2", "Process Control", _
"Electrical", "Environmental1", "Env.2 - Regulatory", "FIRE", _
...
"Product Stewardship", "4B ITEMS")).Select
is grouping all these worksheets. At some point you need to Ungroup them by selecting a single worksheet:
Worksheets("Whatever").Select
You should also examine your code to check whether grouping the worksheets is actually necessary.
I am fairly new to Excel Macros and I am looking for a way to loop through the row headings and columns headings and combine them into one cell for each row and column heading until I have combined all of them.
An example of the First Column cell would be "Your Organizations Title"
An Example of the First Row Cell Would be "22. Cheif Investment Officer"
An example of the first combined cell that I want on a new sheet would be this: "22. Chief Investment Officer (Your Organization's Title)
I then want the combined cells on the new sheet to offset one column to the right until it has iterated through all of the rows and columns.
I have just joined the forum and it will not let me post images or I would have. Perhaps this gives a better idea, here is my code now:
Sub Fill()
' Select cell A2, *first line of data*.
Set title = Sheets("Compensation, 3").Range("B6:B500")
Set descr = Sheets("Compensation, 3").Range("C5:AAA5")
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(title.Value)
Do Until IsEmpty(descr.Value)
ActiveCell.Offset(0, 1).Formula = _
"=title.value & "" ("" & descr.value & "")"""
Set descr = descr.Offset(0, 1)
Loop
Set title = title.Offset(1, 0)
Loop
End Sub
When I run it goes puts this into the active cell:
=title.value & " (" & descr.value & ")"
It does not recognize the variables and come up with the NAME error. It also goes into an infinite loop with no output besides the one cell.
Edit:
I cannot answer my own question because I am new to the forum, but using a combination of your answers I have solved the problem!
Here is the finished code:
Sub Fill()
' Select cell A2, *first line of data*.
Set title = Sheets("Compensation, 3").Range("B6")
Set descr = Sheets("Compensation, 3").Range("C5")
offsetCtr = 0
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(title.Value)
Do Until IsEmpty(descr.Value)
ActiveCell.Offset(0, offsetCtr).Formula = title.Value & " (" & descr.Value & ")"
offsetCtr = offsetCtr + 1
Set descr = descr.Offset(0, 1)
Loop
Set descr = Sheets("Compensation, 3").Range("C5")
Set title = title.Offset(1, 0)
Loop
End Sub
Thank you so much!
Option Explicit
Sub GenerateAndPasteFormulaForTitleAndDescription( _
ByVal titlesRange As Range, ByVal descriptionRange As Range, _
ByVal startCellOnDestination As Range)
Dim title As Range
Dim descr As Range
Dim offsetCtr As Long
Dim formulaTemplate As String
Dim newFormula As String
formulaTemplate = "=CONCATENATE([1], '(', [2], ')')"
startCellOnDestination.Worksheet.EnableCalculation = False
For Each title In titlesRange.Cells
For Each descr In descriptionRange.Cells
If title.Value <> "" And descr.Value <> "" Then
newFormula = Replace(formulaTemplate, "[1]", _
title.Address(External:=True))
newFormula = Replace(newFormula, "[2]", _
descr.Address(External:=True))
newFormula = Replace(newFormula, "'", Chr(34))
startCellOnDestination.Offset(0, offsetCtr).Formula = newFormula
offsetCtr = offsetCtr + 1
End If
Next
Next
startCellOnDestination.Worksheet.EnableCalculation = True
End Sub
Here is how to call the above procedure
GenerateAndPasteFormulaForTitleAndDescription _
Sheets("Compensation, 3").Range("B6:B500"), _
Sheets("Compensation, 3").Range("C5:AAA5"), _
Sheets("new sheet").Range("B5")
EDIT: The code loops through combination of title and description, checks if both of them aren't empty and creates a formula. It pastes the formula into the start cell (Sheets("new sheet").Range("B5") in this case) and moved ahead and pastes the next formula in the column next to it
Basically, you are trying to use VBA objects in worksheet functions. It doesn't quite work that way.
Try replacing
"=title.value & "" ("" & descr.value & "")"""
with
=title.value & " (" & descr.value & ")"