-1

I have a workbook that has thousands of defined name regions located in various worksheets. I'm trying to extract them all and line them up in another workbook.

Most of the defined name regions are 1 row tall (and hundreds of cols wide)... but a few are 3-4 rows tall.

So for example,

Name1

10 5 10 12 30 10 12 10 5 10 12 30 10 12 ...

Name2

10 11 10 12 30 10 12 10 11 10 12 30 10 12 ...
10 11 10 12 30 10 12 10 11 10 12 30 10 12 ...
10 11 10 12 30 10 12 10 11 10 12 30 10 12 ...

For instances where the region is more than one row tall, I'd like to collapse it to a single row by taking the SUM of the entire column.

So Name2 would be copied to the new workbook as the following:

30 33 30 36 90 30 36 30 33 30 36 90 30 36

I have some VBA/VBS written that works perfectly (and fast!) for cases where the region is 1 row tall, but I'm not sure how to handle summing the taller regions in an efficient way.

What's the best way to fill in the question marks below?

My code so far hasn't had to explicitly loop through the cells of a region; I'm hoping that that won't be the case here either. Any advice appreciated!

Dim irow
irow = 0
Dim colsum

'rem Loop through all names and copy over the valid ones
For Each nm in wbSource.Names

    'rem Dont copy any name that isnt visible
    If nm.Visible = True Then

        'rem Only copy valid references that start with "ByWeek"
        If InStr(1, nm.RefersTo, "#REF") = 0 And InStr(1, nm.Name, "ByWeek") > 0 Then

            'rem Only copy if the range is one row tall
            If nm.RefersToRange.Row.Count = 1 Then
                wsDest.Range("A3").Offset(irow, 0).Value = nm.Name
                wsDest.Range("A3",wsDest.Cells(3,nm.RefersToRange.Columns.Count+1)).Offset(irow, 1).Value = nm.RefersToRange.Value
                irow = irow + 1     

            ' rem If the named region is several rows tall, then squish it into one row by taking SUM of each column
            elseif  nm.RefersToRange.Row.Count > 1 Then
                wsDest.Range("A3").Offset(irow, 0).Value = nm.Name
                ???????????????????????????????????
                irow = irow + 1                     

            End If      
        End If  
    End if
Next
Tommy O'Dell
  • 7,019
  • 13
  • 56
  • 69

3 Answers3

2

You can update your code such that it adds all the cells in the given range (nm.RefersToRange), independently upon the number of cells:

Dim irow
irow = 0

'rem Loop through all names and copy over the valid ones
For Each nm in wbSource.Names

    'rem Dont copy any name that isnt visible
    If nm.Visible = True Then

        'rem Only copy valid references that start with "ByWeek"
        If InStr(1, nm.RefersTo, "#REF") = 0 And InStr(1, nm.Name, "ByWeek") > 0 Then
            If nm.RefersToRange.Rows.Count >= 1 Then
                wsDest.Range("A3").Offset(irow, 0).Value = nm.Name
                Dim totVal As Long: totVal = 0   'I assumed that target values are Long; update this to the proper type is required
                For Each cell In nm.RefersToRange.Cells
                    If (IsNumeric(cell.Value)) Then totVal = totVal + cell.Value
                Next
                wsDest.Range("A3", wsDest.Cells(3, nm.RefersToRange.Columns.Count + 1)).Offset(irow, 1).Value = totVal
                irow = irow + 1  
            End If  
        End If  
    End if
Next
varocarbas
  • 12,354
  • 4
  • 26
  • 37
  • Is that `For Each` collapsing all of cells within the whole region into one single cell? The output I'm looking for is a single row with the same number of columns as the source region. See the 2nd version of **Name2** in the question. – Tommy O'Dell Oct 28 '13 at 22:40
  • @TommyO'Dell this code works as yours but by adding up all the rows in the given range; whatever the number is: 1 or 1000. It will copy the resulting value to the given cell and thus, yes, it will output what you are showing in your edit. – varocarbas Oct 28 '13 at 22:47
  • Hi Varo, what I'm looking for is the SUM for each column in the range, not grand total of all cells. I intended to convey that requirement via the part of my question that says **So Name2 would be copied to the new workbook as...** with the result being a row of cells like `30 33 30 36 90 30 36 30 33 30 36 90 30 36`. – Tommy O'Dell Oct 29 '13 at 23:46
  • If you want to change your code to something that will add up each column in the range, I can tick your answer as correct. – Tommy O'Dell Oct 29 '13 at 23:56
  • @TommyO'Dell you are free to mark any answer as the right one (ideally, the one you feel solves your problem). But I am afraid that you haven't still understood what I proposed (and how Ranges work in Excel): my For Each cell just goes through the cells in the given range, that is, nm.RefersToRange, that is, the column in the given iteration and all the associated rows, nothing else (there is no difference between using For Each or For with the corresponding variables). If you would have tested it or just asked, you would have understood this point properly since the start. – varocarbas Oct 30 '13 at 08:30
  • Hi Varo, again your code does not work as I've specified. It most definitely does get the grand total. Using this named range (http://i.imgur.com/3zDjfM8.png), your VBA creates this output (http://i.imgur.com/d4vCFjF.png), whereas what I'm looking for is this (http://i.imgur.com/CIvRIF7.png) – Tommy O'Dell Oct 30 '13 at 13:28
  • @TommyO'Dell What you are saying does not have any sense. Where my code would take these values from?! Your code (the one you have accepted as the right answer) does EXACTLY THE SAME than mine (iterates through all the cells in nm.RefersToRange; you can do it by using For Each and the corresponding Cell Object or For and accessing the indices of the cells; there is difference). But completely up to you. – varocarbas Oct 30 '13 at 13:42
  • @TommyO'Dell PS: I am sure that Mehow will be really happy to see the results which his code deliver (something on the lines of: -11111 55657 basdfasf 888 isn't it?) – varocarbas Oct 30 '13 at 13:57
  • Varo, 570 is the `grand total` of all of the cells in the range. See here for the VBA that I've used (http://pastebin.com/N0YjmqCJ), which I copied from your answer. And here's a copy of the .xlsx file that I used (https://www.dropbox.com/s/aaz3t7o4xxv7dd5/VaroAnswer.xlsx). Simply open this file, insert the vba, and you'll see that it outputs the screenshots that I've shown in comments above. – Tommy O'Dell Oct 30 '13 at 13:59
  • @TommyO'Dell I am not magician and I am not about your testing conditions; as said, I assumed that your original code was doing what was supposed to be doing (getting the corresponding range and checking the number of rows); my code has updated it by adding all the corresponding rows. You are talking about a grand total I don't know what is about; but if the code in your answer delivers what you want, my code should deliver exactly the same under the same conditions as far as For Each and For (as in both codes applied to the same values) have to deliver exactly the same -> – varocarbas Oct 30 '13 at 14:04
  • @TommyO'Dell -> this is the kind of lack of clarity we have been talking about; and the kind of it works/does not work (when you are testing them under the conditions you want) which, IMO, does not make any sense (does not help you neither any future reader). But everything clear (at least, for me): keep having a so excellent behaviour and be sure that people like me will help you as much as possible. – varocarbas Oct 30 '13 at 14:06
1

there is no best way as everyone might think their way is the best.

I would suggest using arrays instead of working with the range objects directly as arrays would have been much faster.

Consider

enter image description here

Now running the code

Option Explicit

Sub Main()

    Dim lastRow As Long
    Dim lastCol As Long

    lastRow = Range("A" & Rows.Count).End(xlUp).Row
    lastCol = Cells(1, Columns.Count).End(xlToLeft).Column

    Dim arr As Variant
    arr = Range(Cells(1, 1), Cells(lastRow, lastCol))

    ReDim sumArr(UBound(arr, 2)) As Variant
    Dim i As Long
    Dim j As Long
    Dim colSum As Long

    For i = LBound(arr, 1) To UBound(arr, 2)
        For j = LBound(arr, 1) To UBound(arr, 1)
            colSum = colSum + arr(j, i)
        Next j
        sumArr(i) = colSum
        colSum = 0
    Next i

    ReDim finalArray(UBound(sumArr) - 1) As Variant
    For i = 1 To UBound(sumArr)
        finalArray(i - 1) = sumArr(i)
    Next i

    Range("A10").Resize(1, UBound(finalArray, 1) + 1) = finalArray

End Sub

Results in

enter image description here


The idea to use arrays is taken from here

And all you need to do is modify the range you want to reprint the array to

Range("A10").Resize(1, UBound(finalArray, 1) + 1) = finalArray

So if you use the above code I think all you'll need to change will be

wsDest.Range("A3").Resize(1, UBound(finalArray, 1) + 1) = finalArray
Community
  • 1
  • 1
  • Hi Mehow, there are literally thousands of ranges in dozens of worksheets in my workbook. And they often have data in cells between those regions (data that I don't want), so anything using `.End(xlUp)` would break out its region and into other data. – Tommy O'Dell Oct 28 '13 at 22:50
  • Sorry @TommyO'Dell but you are being unclear then because youve got 3 answers and none of them seems to be satisfactory to you. Maybe you should consider better explaining what's needed –  Oct 29 '13 at 08:01
  • 1
    I haven't tested your code but working with arrays is always a quicker option and, thus, independently upon the exact OP's requirements, this is a worthy answer. Additionally, don't like when OPs don't communicate clearly, don't understand the proposed answers and come up with their own answer which, in this case for example, is an exact copy of mine. For all that: +1. – varocarbas Oct 29 '13 at 08:53
  • Hi Mehow, can you explain which parts of the question you find unclear? I'm happy to edit to improve clarity. – Tommy O'Dell Oct 29 '13 at 23:39
  • `What's the best way to fill in the question marks below?` - so I feel that my answer provides you a solution - have you tested it? –  Oct 30 '13 at 08:04
0

Here's the code I ended using: It loops through each column for the defined named range. It isn't fast, but it works well enough, as 90% of my ranges are just one row tall.

I've just inserted this code where where it says ????...???? in my question above, :

                        For j = 1 To nm.RefersToRange.Columns.Count
                            colsum  = 0
                            For i = 1 To nm.RefersToRange.Rows.Count
                              If IsNumeric(nm.RefersToRange.Cells(i, j).Value) Then                  
                                    colsum = colsum + nm.RefersToRange.Cells(i, j).Value
                              End If                  
                            Next
                            wsDest.Range("A3").Offset(irow, j).Value = colsum
                        Next  
Tommy O'Dell
  • 7,019
  • 13
  • 56
  • 69
  • 1
    Actually, I have realised about a small bit which had to be corrected in my code (now it is perfectly adapted to your conditions). I guess that, next time, when you ask something and people try to help, you should intend to communicate to this people what you want (if it wasn't clear). Actually, you might even ask whether what you tried is the best approach to the problem and we might have suggested something. But well... you are free to do whatever you want (although help might not come next time). – varocarbas Oct 29 '13 at 08:42
  • 1
    I fully agree with Mehow: your conditions are very unclear; I have realised that my original version was right (and moved back to it). You just want to copy all the cells in nm.RefersToRange (which is always one column width and might have 1 or more rows); thus the original version of my code was fine: it iterates through all the cells in the given range exactly as your two nested loops do (bear in mind that my For Each loop will take care of any number of columns). I insist in my previous comment: if you think that this is the right way to help help... – varocarbas Oct 29 '13 at 08:49
  • Varo, I'm happy to edit the question for clarity. You need only ask, and to point out which parts are unclear. – Tommy O'Dell Oct 30 '13 at 00:02
  • You have just (now and in any future question) to make clear your requirements and help people answering to understand why their answers do not meet your requirements; in order to do that, you would need to understand the answers (test them or ask the answerers). Bear in mind that your exact input data and the performance from your code are not included in your question; all what we have is your code and hoping that it delivers what you say (-> this is not the case with quite a few askers: they claim certain performance from his code which is not true) – varocarbas Oct 30 '13 at 08:34
  • In summary: if you have certain problem, need help to solve it and you do get help; just maximise this help: test the answers, ask the answerers about any point which is not clear, help the answerers to understand your requirements perfectly, etc. But what is the point of getting two answers, not testing them and coming up with your own one? I mean... you are encouraged to write your own code and this is what we expect from you but before doing that, you have to understand the answers and bear in mind that all this is expected to be used by others (let your code for yourself :)) – varocarbas Oct 30 '13 at 08:40
  • Hi Varo - What you're asking of me is exactly what happened. I asked a question (that I thought was well explained), got two answers that didn't quite hit the mark (and I explained why in a comment below each). I waited a little while for a response, but because I needed to get this done for work, had to go and figure it out myself. – Tommy O'Dell Oct 30 '13 at 13:04
  • But that said, if you can change your code to something that will do column sums instead of a grand sum, I'll mark your answer correct. – Tommy O'Dell Oct 30 '13 at 13:06
  • I have repeated this various times: my original code performs the action you wanted since the start (= adds all the rows (1 or more) in the given column, as defined by your range); same thing does Mehow's code (I presume). But you have tested our codes and didn't understand our explanations; I have repeated you quite a few times that your code does exactly the same than mine but by replacing For Each with For. As a consequence of your lack of clarity I did change my code for a short time, until I realised that I was fine since the start (and changed it back) – varocarbas Oct 30 '13 at 13:10
  • PS: you can mark any answer you want (or don't mark any); you don't need to refer this as a reward at all. You should better focus on understanding what we proposed. In any case, if you are happy with your current knowledge and you think that all this has helped you to create what you need (but none of our answers is the solution) you are completely free to think/act in this way; but don't expect me to think like you do. I have explained my position pretty clearly (both our answers should be fine and you don't even know what they deliver) and thus don't see the point of continue repeating it. – varocarbas Oct 30 '13 at 13:13