0

I have the dataset:

enter image description here

I want to iterate all columns and rows to select out the non-zero values and place it into a new sheet with the month:

enter image description here

Is it possible to achieve that using VBA or Vlookup?

My idea is:

For y in Item No Column
For x in Row
If Qty != 0, append to new sheet
Else go to the next cell

I am not very sure if it is achievable by VBA.

Thanks in advance!

braX
  • 11,506
  • 5
  • 20
  • 33
icedmilocode
  • 95
  • 1
  • 2
  • 15

3 Answers3

0

This is for sure possible to do in VBA. I would suggest to save your data in three different Variant Arrays, one for your item numbers, one for your months and one for your quantities. You can read out the values like this:

dim quantities, months, numbers as Variant

quantities = range("YourQuantityRange")
months = range("YourMonthRange")
numbers = range("YourNumberRange")

Note that you have to replace the range values with rangs like "A2:A10" or whatever ranges you are using. I don't know where your values placed within your original sheet.

You also need to make a new sheet, you can do so like this

Dim mySheet As Worksheet
Set mySheet = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))

Then you can loop through your quantities Array like this and print the correct values to your second sheet if the number is not 0

dim i as Long, j as Long, rowCounter as Long
rowCounter = 2 'in which line do you want your first row of data to be written in the second sheet

For i = 1 To UBound(quantities, 1)
    For j = 1 To UBound(quantities, 2)

        if quantities(i, j) <> 0 then
            mySheet.Cells(rowCounter, 1) = numbers(i,1) 'The second parameter of Cells() specifies the column so your item numbers will be pastet in the first column in this example
            mySheet.Cells(rowCounter, 2) = quantities(i,j)
            mySheet.Cells(rowCounter, 3) = months(1,j)
            rowCounter = rowCounter + 1
        end if

    Next
Next

Note, this will place your values in the first, second and third column starting with the second row. You can for sure just edit the values to your needs.

Edited based on comment by Pᴇʜ

Ruvox
  • 116
  • 1
  • 10
  • 1
    Please note that `dim i, j, rowCounter as integer` only declares `rowCounter` as `Integer` but all others as `Variant`. You must decare a type for **every** variable in VBA! Also you cannot use `Integer` for row counting variables because Excel has more rows than `Integer` can handle. Therefore you must use `Long`: `Dim i As Long, j As Long, rowCounter As Long` • I recommend [always to use Long instead of Integer](https://stackoverflow.com/a/26409520/3219613) in VBA since there is no benefit in `Integer` at all. – Pᴇʜ Nov 29 '18 at 09:02
  • @Pᴇʜ Wow, that's something completely new to me, thank you! I always thought it would be possible to declare several variables of the same type that way. – Ruvox Nov 29 '18 at 09:06
  • 1
    People often get confused on this because declaring multiple variables like you did is possible in VB.NET, but it doesn't work in VBA. In VBA you must specify the type for every variable, otherwise VBA assumes `Variant`. – Pᴇʜ Nov 29 '18 at 09:09
0

This is an Example of how to loop through your data using an array.

Option Explicit

Public Sub UnPivotData()
    Dim wsSrc As Worksheet 'define source sheet
    Set wsSrc = ThisWorkbook.Worksheets("Source")

    Dim wsDest As Worksheet 'define output sheet
    Set wsDest = ThisWorkbook.Worksheets("Destination")

    Dim LastRow As Long 'find last used row
    LastRow = wsSrc.Cells(wsSrc.Rows.Count, "A").End(xlUp).Row

    Dim LastCol As Long 'find last used column
    LastCol = wsSrc.Cells(1, wsSrc.Columns.Count).End(xlToLeft).Column

    Dim srcArr As Variant 'read data range into an array (makes it faster)
    srcArr = wsSrc.Range("A1", wsSrc.Cells(LastRow, LastCol)).Value

    Dim OutRow As Long 'find next free output row in destination sheet.
    OutRow = wsDest.Cells(wsDest.Rows.Count, "A").End(xlUp).Row + 1

    Dim iRow As Long, iCol As Long
    For iRow = 2 To UBound(srcArr) 'loop through all rows
        For iCol = 3 To UBound(srcArr, 2) 'loop through month columns
            If srcArr(iRow, iCol) <> 0 Then 'check if quantity is not 0
                With wsDest.Cells(OutRow, 1) 'write everything
                    .Value = srcArr(iRow, 1)
                    .Offset(0, 1).Value = srcArr(iRow, iCol)
                    .Offset(0, 2).Value = srcArr(1, iCol)
                End With
                OutRow = OutRow + 1 'move to the next free row

                'Debug.Print srcArr(iRow, 1), srcArr(iRow, iCol), srcArr(1, iCol)
            End If
        Next iCol
    Next iRow
End Sub

Alternative if you want to use a even quicker way using an array for output too

Option Explicit

Public Sub UnPivotDataFastOutput()
    Dim wsSrc As Worksheet 'define source sheet
    Set wsSrc = ThisWorkbook.Worksheets("Source")

    Dim LastRow As Long
    LastRow = wsSrc.Cells(wsSrc.Rows.Count, "A").End(xlUp).Row

    Dim LastCol As Long
    LastCol = wsSrc.Cells(1, wsSrc.Columns.Count).End(xlToLeft).Column

    Dim srcArr As Variant 'read data range into an array
    srcArr = wsSrc.Range("A1", wsSrc.Cells(LastRow, LastCol)).Value

    Dim OutRow As Long
    OutRow = 1

    Dim destRowCount As Long 'calculate array size
    destRowCount = Application.WorksheetFunction.CountIf(wsSrc.Range("C2", wsSrc.Cells(LastRow, LastCol)), "<>0")

    Dim destArr As Variant
    ReDim destArr(1 To destRowCount, 1 To 3)

    Dim iRow As Long, iCol As Long
    For iRow = 2 To UBound(srcArr)
        For iCol = 3 To UBound(srcArr, 2)
            If srcArr(iRow, iCol) <> 0 Then
                'output into array
                destArr(OutRow, 1) = srcArr(iRow, 1)
                destArr(OutRow, 2) = srcArr(iRow, iCol)
                destArr(OutRow, 3) = srcArr(1, iCol)
                OutRow = OutRow + 1

                'Debug.Print srcArr(iRow, 1), srcArr(iRow, iCol), srcArr(1, iCol)
            End If
        Next iCol
    Next iRow

    'write array into sheet
    ThisWorkbook.Worksheets("Destination").Range("A2").Resize(destRowCount, 3).Value = destArr
End Sub
Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
  • You use a variant array only when loading data, but the speed depends on whether the result is a variant array. – Dy.Lee Nov 29 '18 at 15:20
  • I know but `.Transpose` has a limitation and can only handle arrays of length up to 2¹⁶ (~64,000) so it doesn't work for a bigger amount of data! That's why I avoided it. – Pᴇʜ Nov 29 '18 at 15:27
  • I know 64556, but most of the data is less than that, and even if it is longer, it is better to artificially plan the array. – Dy.Lee Nov 29 '18 at 15:30
  • I don't agree with *"most of the data is less"*. You cannot proof this. Most of my data isn't less :) – Pᴇʜ Nov 29 '18 at 15:32
  • I did not say all the data I said was the most data. – Dy.Lee Nov 29 '18 at 15:43
  • When I use this, my qty gets summed together, is there any way to separate by month? – icedmilocode Nov 29 '18 at 15:49
  • @icedmilocode no this code does definitely not sum your quantities! It produces exactly the output you asked for. Try it with the example data of your question. – Pᴇʜ Nov 29 '18 at 16:03
  • 1
    I think added answer is more beautiful, becasuse op's data is huge. – Dy.Lee Nov 29 '18 at 16:29
0

Try bellow.

Storing a one-to-one value in a cell has bad results. I do not know when the data is small, but it slows down when working with large amounts of data. I recommend that you have a habit of using a variant array.

Dim rstWs As Worksheet
Dim strSQL As String

Sub test()
    Dim vDB As Variant, vR()
    'vDB is static variant, vR() is Dynamic Variant
    Dim Ws As Worksheet, toWs As Worksheet
    Dim i As Long, j As Integer, n As Long
    Dim r As Long, c As Integer
    Dim wsName As String

    Set Ws = ActiveSheet ' Sheets("Special Name")

    vDB = Ws.Range("a1").CurrentRegion

    r = UBound(vDB, 1)
    c = UBound(vDB, 2)

    For i = 2 To r
        For j = 3 To c
            If vDB(i, j) <> 0 Then
                n = n + 1
                ReDim Preserve vR(1 To 3, 1 To n) 'get data with Tranpose type
                vR(1, n) = vDB(i, 1)
                vR(2, n) = vDB(i, j)
                vR(3, n) = vDB(1, j)
            End If
        Next j
    Next i
    Set toWs = Sheets.Add 'Sheets("Results")
    With toWs
        .UsedRange.Clear
        .Range("a1").Resize(1, 3) = Array("Item No", "Qty", "Month")
        .Range("a2").Resize(n, 3) = WorksheetFunction.Transpose(vR)
    End With
    Set rstWs = Sheets.Add

    wsName = toWs.Name

    strSQL = "Select [Item No], sum(Qty) as Qty "
    strSQL = strSQL & "FROM [" & wsName & "$] "
    strSQL = strSQL & "GROUP BY [Item No] "

    DoSQL
End Sub
Sub DoSQL()

    Dim Rs As Object
    Dim strConn As String
    Dim i As Integer

    strConn = "Provider=Microsoft.ACE.OLEDB.12.0;" & _
        "Data Source=" & ThisWorkbook.FullName & ";" & _
            "Extended Properties=Excel 12.0;"


    Set Rs = CreateObject("ADODB.Recordset")
    Rs.Open strSQL, strConn

    If Not Rs.EOF Then
         With rstWs
            .Range("a1").CurrentRegion.ClearContents
            For i = 0 To Rs.Fields.Count - 1
               .Cells(1, i + 1).Value = Rs.Fields(i).Name
            Next
            .Range("a" & 2).CopyFromRecordset Rs
        End With
    End If
    Rs.Close
    Set Rs = Nothing
End Sub

It is assumed that the data is as follows.

enter image description here

Dy.Lee
  • 7,527
  • 1
  • 12
  • 14
  • Worth to note that `.Transpose` has a limitation and can only handle arrays of length up to 2¹⁶ (~64,000) so it doesn't work for a bigger amount of data! So you should check if your data is less. Otherwise you will silently loose data without knowing, because Excel does't throw an error and instead just cuts off the data. – Pᴇʜ Nov 29 '18 at 15:35
  • When I try this, I get item 100002 qty sum together as 2 instead of 1 in Jan and 1 in Mar – icedmilocode Nov 29 '18 at 15:36
  • @ dy.Lee My idea to improve it and avoid the `Transpose` of the array you could `COUNTIF(C2:F4,"<>0")` to calculate how big your array will be in the end and size it correctly *before* you start filling it with data. So you can fill it the correct way (because you don't resize it anymore) and don't need to transpose it. – Pᴇʜ Nov 29 '18 at 15:40
  • I do not understand where I can add the COUNTIF and means I can remove transpose? How can I show the breakdown of qty in months? – icedmilocode Nov 29 '18 at 15:52
  • @icedmilocode, If your data is less than 60,000 lines, there is no problem. – Dy.Lee Nov 29 '18 at 16:00
  • @Dy.Lee see [here](https://stackoverflow.com/a/53535178/3219613) I've added an answer using an array for output that doesn't need to transpose at all. – Pᴇʜ Nov 29 '18 at 16:06
  • @Pᴇʜ Oh yes sorry! 'Cause I was testing with quite a huge dataset so I got mixed up when I see large values. Your solution is working. Thank you so much! – icedmilocode Nov 29 '18 at 16:14