Creating separate line entries for multi-value fields - vba

I have a table with a list of items. Essentially it's an export from an issue tracking tool. One of the columns of that table contains comma-separated values. I am looking for a way to create separate entries for the individual values of multi-value entries.
Example: (this is a simplified example, the real case contains around a dozen columns)
Source data:
ID | Title | Areas Affected |
1 | Issue title A | Area X, Area Y |
2 | Issue title B | Area Y, Area Z |
3 | Issue title C | Area X, Area Z |
What I am trying to get to:
ID | Title | Areas Affected |
1 | Issue title A | Area X |
1 | Issue title A | Area Y |
2 | Issue title B | Area Y |
2 | Issue title B | Area Z |
3 | Issue title C | Area X |
3 | Issue title C | Area Z |
It is OK that there are now duplicate entries for IDs and Titles?
Is there a formula, macro, or VBA script to achieve this?

You need to split rows on that column using the comma as a separator. In VBA you have the Split() function that you can use to return an array. For the first element just put it back in the cell where the list was. For the others, insert a new line for each element in the array (meaning you can have n elements in that comma-separated list), copy the entire row on that new line and put the i-th value in there.

After some reading/going through sample code, here's the answer if anyone needs. This is the actual working code, which doesn't fit 1:1 the examples I posted in the question.
Sub DataLobs()
Application.ScreenUpdating = False 'Nice to have to increase the script speed.
Dim wsSrc As Worksheet
Dim wsDst As Worksheet
Dim curRowSrc As Integer
Dim curRowDst As Integer
Dim ttlRows As Integer
Dim splitLob() As String
' Setting initial values to start rows in source and destination
' tables, as well as the total number of rows
curRowSrc = 5
curRowDst = 5
ttlRows = 10000
Set wsSrc = Worksheets("Source") 'whatever you worksheet is
Set wsDst = Worksheets("Destination") 'or whatever your worksheet is called
wsDst.Range("A5:F" & ttlRows).Clear
' Goes through column D in the source table
' and copies rows where the D cell is not blank
' into the destination table
For curRowSrc = 5 To ttlRows
If wsSrc.Range("D" & curRowSrc).Value <> "" Then ' There are some blank cells in the source table, so we are eliminating them.
' Split the cell value against the comma
splitLob = Split(wsSrc.Range("D" & curRowSrc).Value, ", ") 'THIS IS WHERE #AlexandreP.Levasseur's HINT COMES INTO PLAY!
For i = LBound(splitLob) To UBound(splitLob)
wsDst.Range("A" & curRowDst).Value = splitLob(i)
wsDst.Range("B" & curRowDst).Value = wsSrc.Range("A" & curRowSrc)
wsDst.Range("C" & curRowDst).Value = wsSrc.Range("C" & curRowSrc)
wsDst.Range("D" & curRowDst).Value = wsSrc.Range("AC" & curRowSrc)
wsDst.Range("E" & curRowDst).Value = wsSrc.Range("AE" & curRowSrc)
wsDst.Range("F" & curRowDst).Value = wsSrc.Range("AD" & curRowSrc)
curRowDst = curRowDst + 1
Next
End If
Next curRowSrc
End Sub

Related

Remove row when value is found in another row and replace value with string SQL Amazon Redshift

I want to reduce rows into smaller rows.
I have some IDs that are found in other rows and I want to remove those duplicates but also concatenate to have the list of IDs that are related to each other.
Here is my current output:
| IDs |
|----------------------|
|1147457338 |
|1147457340 |
|1150006529 |
|1150012541, 1175373594|
|1150012541, 20296677 |
|1150012542 |
|1150012543 |
|1150012543, 1150012542|
|1175373593, 1150006529|
|1175373594, 1175373593|
|1275678231 |
|20296677 |
|782676061 |
|782676061, 1147457338 |
The first ID in Row 4 is found in Row 5. Since that value is found there, I want to merge them in one row like this:
| IDs |
|--------------------------------|
|1150012541, 1175373594, 20296677|
And so on with the all.
Since I'm not an expert with SQL, the only thing that came to my mind was to do a code with VB.net that can be executed in Excel and it works just as I was expecting.
Below the code in case it helps to understand what I want to achieve:
Sub idMerge()
Dim lRow As Long
Dim i As Long
With Globals.ThisAddIn.Application.ActiveSheet
lRow = .Range("A1").CurrentRegion.Rows.Count 'Last Row
For i = lRow To 2 Step -1
If InStr(.Range("A" & i).Value, ",") = 0 Then 'If it is only one ID and it's found in another row, delete row
If .Range("A" & i - 1 & ":A1").Find(.Range("A" & i).Value,,, XlLookAt.xlPart) IsNot Nothing Then
.Range("A" & i).EntireRow.Delete
End If
Else 'If there are multiple IDs, look for each one in another rows and if it's found, the ID found is replace with the whole cell content and deletes the row
For Each id In Split(.Range("A" & i).Value, ", ")
If .Range("A1:B" & i - 1).Find(id,,, XlLookAt.xlPart) IsNot Nothing Then
Replace(.Range("A1:A" & i - 1).Find(id,,, XlLookAt.xlPart).Value, id, .Range("A" & i).Value)
.Range("A1:A" & i - 1).Find(id,,, XlLookAt.xlPart).Value = Replace(.Range("A1:A" & i - 1).Find(id,,, XlLookAt.xlPart).Value, id, .Range("A" & i).Value)
.Range("A" & i).EntireRow.Delete
Exit For
End If
Next id
End If
Next i
End With
End Sub
The output using this code returns this:
| IDs |
|--------------------------------------------------------|
|782676061, 1147457338 |
|1147457340 |
|1150012541, 20296677, 1175373594, 1175373593, 1150006529|
|1150012543, 1150012542 |
|1275678231 |
Which is EXACTLY what I'm looking for with SQL only
Thanks in advance!
EDIT 1:
I posted another question a few days ago (linked to that post) which I haven't received a solution. But the closest I could get to that question is to get only 2 geometry that are connected to each other, but I cannot connect all of them. Here is what I made to achieve that output:
WITH geoSet AS (
SELECT geoID, vector
FROM geometry
WHERE geoID IN (1147457338, 1147457340, 782676061, 20296677, 1175373594, 1175373593, 1150006529, 1150012542, 1150012541, 1150012543, 1275678231)
)
SELECT DISTINCT LISTAGG(geoID, ', ') AS path
FROM (
(SELECT geoID,
CONCAT(SPLIT_PART(vector,',',1),')') AS startEndPoint
FROM geoSet
)
UNION
(SELECT geoID,
CONCAT('LINESTRING (',TRIM(SPLIT_PART(vector,',',LEN(vector)-LEN(REPLACE(vector,',',''))+1))) AS startEndPoint
FROM geoSet
)
)GROUP BY startEndPoint ORDER BY path

Search for a word with Prefix in the column and copy the entire word with prefix to another column on the same sheet

I Download an excel file which has multiple Row's and endless columns.
In a particular Row we have data where every Cell is containing details of a specific product, Separated by Alt+Enter.
I have to pull the data of 2-3 such descriptions by copy paste to separate the product from the huge list.
Eg:-
_A_______B____________C____D___E___F___G______H_________________________________
| Product | Range |A | B | C |D |description|....
________________________________________________________________________________
1 | Apple | R 1 |A1| B1| C1|D1| Description1
Description2
Description3
Description4
________________________________________________________________________________
2 | ball | R 1 |A1| B1| C1|D1| Description1
Description2
Description3
Description4
From the above example My Requirment is to copy Description Details with Prefix of Say DLL:123456 or LLM: 654321 and copy the same to the next Row.
This will help to seperate the product with perticular Description.
If this what you looking for :
APPLE A B C D E F G H 2460 APPLE : 2460
6521 APPLE : 6521
4532 APPLE : 4532
3021 APPLE : 3021
1234 APPLE : 1234
BALL 6521 BALL : 6521
4532 BALL : 4532
3021 BALL : 3021
1234 BALL : 1234
Then create a column and use this formula. You can also use your own prefix
=IF(ISBLANK(a2),LEFT(a1,(SEARCH(":",a1)) & " " &l2),a2&" : "&l2)
Based on what I'm guessing you need: This might get you started:
Sub Example1()
Dim rowArray() As Variant, rowArrayCounter As Long
Dim myStringArray, itemThatIwant As String, rowItIsIn As Long
' The following code will find all instances of "LLM: 654321" in column "F"
' It places the row number of each one into an array called rowArray()
' and places the value of that item into a variable called itemThatIwant
' The "split" function assumes that you enttered each list into a single cell
' by using alt-enter to put them on individual lines within that cell. If so, then
' the split delimiter would be chr(10), as below. Otherwise it will probably be one space
' but you will need to find the correct delimiter for this to work.
rowArrayCounter = 1
ReDim rowArray(1 To 1)
With Worksheets(1).Range("F1:F250")
Set c = .Find("LLM: 654321", LookIn:=xlValues)' this text is what you change
If Not c Is Nothing Then
firstAddress = c.Address
Do
myString = Split(c.Value, Chr(10)) ' split cell list into separate items
For i = LBound(myString) To UBound(myString)
If Left(myString(i), 11) = "LLM: 654321" Then
itemThatIwant = myString(i)
rowItIsIn = c.Row
ReDim Preserve rowArray(1 To rowArrayCounter)
rowArray(rowArrayCounter) = c.Row
' do your events with data
' the entire item (if found) is in the variable itemThatIwant
Exit For
End If
Next i
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
End Sub

VBA fill cells by random numbers depending on other cells

I am working with Excel 2013 and I need to fill the range of cells by random numbers. To be exact, Range(B1:B4) fill by numbers in order to value of cells in previous column, I mean in Range(A1:A4). I really have no idea how to fill that using VBA if there is that condition, otherwise it's simple.
Here is a scetch of cells
# | A | B |
----------------------
1 | Yes | 1 |
----------------------
2 | No | 2 |
----------------------
3 | Maybe | 3 |
----------------------
4 | No | 2 |
----------------------
If all you need is random numbers, you don't need VBA. Just set your cell formula equal to:
"=RANDBETWEEN(1,3)"
However, your random numbers will change every time your worksheet is calculated. To avoid this, you can define the following sub and associate it, for example, with an action button:
Sub makeRand()
Dim targetRange As Range
Dim xlCell As Range
Dim upperBound As Integer
Dim lowerBound As Integer
Set targetRange = Range("B1:B4")
upperBound = 3
lowerBound = 1
Randomize
For Each xlCell In targetRange
xlCell.Value = Int((upperBound - lowerBound + 1) * Rnd + lowerBound)
Next xlCell
End Sub
If all you need to do is set this with vba, this should give you values 1, 2, or 3:
Range("B1:B4").Formula = "=RANDBETWEEN(1,3)"
If you only need an Excel formula, you can always just paste =RANDBETWEEN(1,3) into the formula bar.
If you're trying to define column B values based on column A values, just use:
Range("B1:B4").Formula = "=IF(A1 = ""Yes"", 1, IF(""No"", 2, If(""Maybe"", 3, ""ERROR"")))"
If neither of those are what you want, you're going to have to clarify better.

Write a formula into a cell depending on another cell value

I was hoping to write a Macro that does a very repetitive task for me but entering VBA is harder than expected. I will learn how to program macros for excel when I have some time because it seem extremely useful, but I can't spend 5 to 12 hours this week.
Maybe someone here can help!
I have a few excel files that follow this pattern:
Column C - Column D
--------------------
text | (empty)
number | (empty)
number | (empty)
text | (empty)
number | (empty)
text | (empty)
text | (empty)
number | (empty)
text | (empty)
number | (empty)
Where text and number alternate randomly for a few thousand cells. I need column D to hold, when column C is a number, the difference with previous number, otherwise it must stay blank:
Column C - Column D
--------------------
text | (empty)
3 | (empty)
14 | (=C3-C2) : 11
text | (empty)
16 | (=C5-C3) : 2
text | (empty)
text | (empty)
21 | (=C8-C5) : 5
22 | (=C9-C8) : 1
So the algorithm is:
var previousNumberCell
var i = 1
for all (selected) cells/rows
if (Row(i).column(C) holds number) {
Row(i).column(D).value = "=C"+i+"-"C"+previousNumberCell
previousNumberCell = i;
}
i++
End
I don't care if for the first or last cell it doesn't work.
Thank you so much for the help, or if you can point me to where I can find the answer to this.
EDIT: this is a simplified version of the problem, there are 2 things I don't know how do well with excel macros: select a cell, and tell if cell is a number... for the record, number cells have been converted from text to number format.
Give this a shot:
Sub MyMacro()
Dim rng as Range
Dim cl as Range
Dim lastNum as Range
Set rng = Range(Selection.Address) 'Make sure that your active SELECTION is correct before running the macro'
If Not rng.Columns.Count = 1 Then
MsgBox "Please select only 1 column of data at a time.",vbCritical
Exit SUb
Else:
For each cl in rng
If IsNumeric(cl.Value) Then
If lastNum Is Nothing Then
cl.Offset(0,1).Formula = "=" & cl.Address
Else:
cl.Offset(0,1).Formula = "=" & cl.Address & "-" & lastNum.Address
End If
set lastNum = cl
End If
Next
End If
End Sub
Do you require VBA?
Insert a new Column before column C
Column C with your values becomes column D
You might need columnheaders..
In cell C2 put: =IF(E2=0;0;SUM(E$2:$E2)) this identifies rows with number
In cell E2 put: =IF(ISNUMBER(D2);1;0) this sets order for each row with a number to use next in vlookup
in cell F2 put: =IF(ISNUMBER(D2);ABS(D2-VLOOKUP(MAX($C$1:C1);$C$1:D1;2;0));"")
Autofill columns C, E and F.
In column F you get your results, except first, which is "#VALUE"
Hi you can do this with an if formula and a named formula . if (isnumber ,named formula,0)
named formula (=lookup formula)

Selecting/grouping top/bottom *x* rows in a pivot table based on a sort of values

I am trying to group items in a pivot table based on a sorting of the values for those rows. I'm trying to set up a pareto chart based on this data. I've looked through a number of questions here on SO and on google, but was not able to find much help... It looks like this is not really that common a problem, unfortunately.
Example:
Original Pivot
State | Count
AK | 14
AL | 76
AR | 40
AZ | 129
CA | 2666
CO | 244
CT | 61
Sorted Pivot
State | Count
CA | 2666
CO | 244
AZ | 129
AL | 76
CT | 61
AR | 40
AK | 14
Grouped/Final Pivot
State | Count
CA | 2666
CO | 244
AZ | 129
OTHER | 191
I've never done pivot sorting/grouping in VBA before (but manually doing this is fairly simple), so I started with recording a macro. I figured out how to apply the sort in the first place, but trying to group gave me something like this:
ActiveSheet.PivotTables("PT7").PivotSelect _
"State[AL,CT,AR,AK] Original 'NON-AA'", _
xlDataAndLabel + xlFirstRow, True
The problem with this is that the states are hard-coded based on the values in the pivots. If I wanted to run this grouping on another set of data where a different state is below the threshold (top 3 rows, not actual value of count), then the wrong states would be included.
Is there a way to read just the top/bottom x number of rows without needing to know which row labels those correspond to?
I want something to work like this (using row numbers instead of captions/labels):
ActiveSheet.PivotTables("PT7").PivotSelect _
"State[4, 5, 6, 7] Original 'NON-AA'", _
xlDataAndLabel + xlFirstRow, True
I believe this does it:
Sub GroupLowerPivotItems()
Dim pt As Excel.PivotTable
Dim ptField As Excel.PivotField
Dim ptItem As Excel.PivotItem
Dim GroupStart As Long
Dim FirstCell As Excel.Range
Dim LastCell As Excel.Range
GroupStart = 4
Set pt = ActiveSheet.PivotTables(1)
Set ptField = pt.PivotFields("State")
Set ptItem = ptField.PivotItems(GroupStart)
Set FirstCell = ptItem.LabelRange
Set ptItem = ptField.PivotItems(ptField.PivotItems.Count)
Set LastCell = ptItem.LabelRange
pt.Parent.Range(FirstCell, LastCell).Group
End Sub
I believe I have solved this on my own -- needs to be refactored.
Dim iFilterLoop As Integer
Dim vValue2List As Variant
Dim sValue2List As String
vValue2List = ActiveSheet.PivotTables("PT7").RowRange.Value2
sValue2List = ""
'Here's the key step in reading the string values:
For iFilterLoop = 4 To ActiveSheet.PivotTables("PT7").RowRange.Count ' Get all but the top 3 -- skip the first (start at 4), since that's the title
sValue2List = sValue2List & vValue2List(iFilterLoop, 1) & ","
Next iFilterLoop
sValue2List = Left(sValue2List, Len(sValue2List) - 1)
ActiveSheet.PivotTables("PT7").PivotSelect( _
"State[" & sValue2List & "] Original 'NON-AA'", _
xlDataAndLabel + xlFirstRow, True).Group