Error reading time cell in Excel using SQL (VBA) - sql

I have an excel worksheet with 7 tables (each placed in a different sheet) and I built a routine to select through them, consolidating the data in a single sheet.
All tables have an HOUR column and I have two types of select: a full time range, where I select every row in each table, and a part-time range, where I select only the data between 6:00:00 and 23:00:00.
For i = 2 To Sheets.Count
...
strSQL = "SELECT [APPLICATIONNAME], [DAT], [TRANSACTION_NAME], SUM([TOT]), AVG([PERFORM]), SUM([ERRO]), AVG([TOTAL_TIME]), AVG([PERCENTUAL_AJUSTADO]), [HOUR], LEFT([HORA],2) " & _
" FROM [" & Sheets(ind).Name & "$] " & _
" WHERE (LEFT([HOUR],2) >= 6 AND LEFT([HOUR],2) <= 23)" & _
" GROUP BY [APPLICATIONNAME], [DAT], [HOUR], [TRANSACTION_NAME]" & _
" ORDER BY [DAT], [HOUR], [TRANSACTION_NAME]"
...
Next i
The problem is that it works fine only when I read the first three tables. From the fourth table on, it seems that Excel is converting the time cell to a numeric value and the data becomes incomplete.
I believe it's not a problem of cell formatting, because if I change the order of the sheets, the SELECT continues reading the first three tables perfectly.
I made it print the time read and the LEFT([hour],2). The results for the first three are, for example:
hour = 22:00:00
LEFT([hour],2) = 22
From the fourth table on, the results are (always):
hour = 8,3333333333333
LEFT([hour],2) = 8,
What may be happening? Is there another way to make it work for all sheets?
Thanks in advance.
Ricardo Freire

Related

Index/Match and sum a range based on match

Good morning. Have tried asking this several times with no success.
I have written VBA code with several index/matches.
pasterOH = "=IFERROR(IF(RC[-3]=""Subtotal"","""",IF(RC[-4]="""",INDEX(" & stapler & OH1 & "'!" & "C" & (Month(Worksheets("SEL Onsite OH").Cells(6, 6).Value) + 6) & ",MATCH(RC[-3], " & stapler & OH1 & "'!C3,FALSE),1),INDEX(" & stapler & OH1 & "'!" & "C" & (Month(Worksheets("SEL Onsite OH").Cells(6, 6).Value) + 14) & ",MATCH(RC[-4], " & stapler & OH1 & "'!C2,FALSE),1))),""0"")"
The code works fine to look up a cell in the column that I have specified and return a dollar value if it is a match.
Here is my issue: I need to be able to sum all of the dollar values to the left of the match. For example, in the picture I posted a simple picture of a table with index/match. In the formula the target is column E:E, whereby "tom' returns "5". What I am trying to do is if excel can find and match "tom" in column 5, it would sum B2,C3,D4,E5. I could write this if I knew what column "tom" would be in every time. I need to have a VBA solution that goes along with the pasterOH from above that will allow me to sum the values of column 7 (January) picked by my (Month(Worksheets("SEL Onsite OH").Cells(6, 6).Value) + 6)through whatever column the month happens to be.
For example, In May, the column I am looking up and returning a match to is column 11. I would like the code to start with the matching entry (our "tom" in this case), and sum the results of Jan- May (columns 7-11) and return that as the answer instead of tom's result only in May.
Hope that is clear. I posted a couple of times and found this answer, but don't know if it will apply or how to edit my VBA formula.
Thanks for any assistance
Extending a conditional index-match to sum across a range
Sample table pic
The formula you want is
=SUM(INDEX(B:F,MATCH(C11,A:A,0),0))

Loop Through Columns to Insert SUM Formula

I have a program where I need to find the sum of columns 7 through 30 (G through AD). What I am trying to do is loop through and insert the formula (=SUM(Columns(i)2:Columns(i)1000)) but obviously Columns(i) is not the letter but the number. This is not complying with the format required by SUM so I'm wondering what I can do instead.
I have a program where old sheets will be deleted and new ones added holding configuration data for a product. This means formulas cannot be in the sheet inself or any other sheet referring to it. Prices will be held in columns G through P and U through AD which I need to find the total for and place it in row 1 above the corresponding data. When I try:
For i = 7 To 30
wsNewSheet.Cells(1, i).Value = "=SUM(" & Columns(i).Select & "2:" & Columns(i).Select & "1000)"
Next i
Columns(i) is returning as "True" for some reason. I have also tried to place the totals in a different sheet as the new sheets (wsNewSheet) were being created.
For i = 2 To 30
For f = 7 To 30
wsTotals.Cells(1, i).Value = "=SUM(" & wsNewSheet & "!" & Columns(f).Select & "2:" & Columns(f).Select & "1000)"
Next f
Next i
However, this did not work either. This statement returned "Run-time error '438.' Object doesn't support this property or method." I attemped to do research on this error, but could not fix my situation. Thanks for any help.
You don't need loop here, use single line instead:
Range("G1:AD1").Formula = "=SUM(G2:G1000)"
Excel automatically adjust formulas:
in G1 you'd have =SUM(G2:G1000)
in H1 you'd have =SUM(H2:H1000)
....
in AD1 you'd have =SUM(AD2:AD1000)

Using .formula to sum values in dynamic ranges

I created a macro that changes the layout of a raw excel file and displays some usefull information to the user. But I am stuck now.
I want to display the sum of some cells in a column, but the cell where the sum is and the column itself is set dynamically.
My layout looks like this:
Year1 Year2 Year3
Sum1 100
Sum2 50 48
Sum3 72 81
Sum4 26
------------------------------------------
Total
I want to display the sum of (sum1, sum2, sum3, sum4) in the Total row under each Year.
The thing is my years are set dynamically depending on the value of a parameter (i.e. I can have 2, 3 or 5 years).
I know I can do it using Range.WorksheetFunction.Sum but the problem is my values are subject to some changes after the macro has been used and if I use this function the values won't change afterward.
That's why I want to use the function Range.Formula to display these values as I can enter the sum like I did for the values already in the sheet.
But unlike these values (that are the sum of values in an unique column easy to select), I am not able to select the colum dynamically.
Here is what I tried to give you an idea of what I want to do if it is unclear to you:
For i=0 to Duration-1
Sheet2.Range("D" & RNumber + 7 + Duration).Offset(0,1+2*i).Formula="=SUM(Sheet2!" & Range("D" & RNumber + 6).Offset(0,1+2*i) & ":Sheet2!" & Range("D" & RNumber + 6 + Duration).Offset(0,1+2*i) & ")"
Next i
But it doesn't work as the sum doesn't recognize the range. I know I should have something like
"=SUM(Sheet2!E" & Firstrow & ":Sheet2!E" & Lastrow & ")"
but then i won't be able to select my colum dynamically.
It looks like your problem stems from the fact that you can't do things like "Sheet2!" & D+3+1 for columns the same way you can do "Sheet2!D" & 5+6+2 for rows. My opinion is that this is one of the many flaws created by the insane A1 reference style, and the best way to attack this problem at the root is to simply switch to R1C1 at the beginning of the macro.
With Application
If .ReferenceStyle = xlA1 Then
.ReferenceStyle = xlR1C1
End If
End With
Some day, I plan to start a non-profit effort to rid the world of the mis-begotten $A4:B$3 craziness altogether. But too many people are used to A1. And if you send your boss or co-worker a spreadsheet with numbers for columns, she might get upset or confused.
So the other solution is to introduce some ADDRESS() elements into your SUMs. The ADDRESS function takes only numbers (well, string for sheet reference) and will return a cell reference in whichever style you want.
For i=0 to Duration-1
Sheet2.Cells(RNumber+7+Duration, 5+2*i).Formula = _
"=SUM(INDIRECT(ADDRESS(" & RNumber + 6 & "," & 5+(2*i) & ",1,TRUE,""Sheet2""),1):" _
& "INDIRECT(ADDRESS(" & RNumber + 6 + Duration & "," 5+(2*i) & _
",1,TRUE,""Sheet2""),1))"
Next i
Or you can use Application.WorksheetFunction.Address() and avoid having the INDIRECT()s cluttering up your fomulas.

Group records into non-contiguous hour-long bins

I have event data of the form, where eventId is a long and time is Date/Time (shown below as just the time for simplicity, since all times will be on the same day):
eventId time
---------------
1 0712
2 0715
3 0801
4 0817
5 0916
6 1214
7 2255
I need to form groups containing an hour's worth of events, where the hour is measured from the first time in that group. Using the above data my groupings would be
Group 1 (0712 to 0811 inclusive): events 1, 2, 3
Group 2 (0817 to 0916 inclusive): events 4, 5
Group 3 (1214 to 1313 inclusive): event 6
Group 4 (2255 to 2354 inclusive): event 7
I've found plenty of examples of grouping data on predefined periods (e.g. every hour, day, 5 minutes) but nothing like what I'm trying to do. I suspect that it's not possible using straight SQL...it seems to be a chicken and egg problem where I need to put data into bins based on the data itself.
The cornerstone of this problem is coming up with the start time of each range but I can't come up with anything beyond the trivial case: the first start time.
The only way I can come up with is to do this programmatically (in VBA), where I SELECT the data into a temporary table and remove rows as I put them into bins. In other words, find the earliest time then grab that and all records within 1 hour of that, removing them from the table. Get the earliest time of the remaining records and repeat until the temp table is empty.
Ideally I'd like a SQL solution but I'm unable to come up with anything close on my own.
Some notes on a possible approach.
Dim rs As DAO.Recordset
Dim db As Database
Dim rsBins As DAO.Recordset
Dim qdf As QueryDef 'Demo
Set db = CurrentDb
'For demonstration, if the code is to be run frequently
'just empty the bins table
db.Execute "DROP TABLE Bins"
db.Execute "CREATE TABLE Bins (ID Counter, Bin1 Datetime, Bin2 Datetime)"
'Get min start times
db.Execute "INSERT INTO bins ( Bin1, Bin2 ) " _
& "SELECT Min([time]) AS Bin1, DateAdd('n',59,Min([time])) AS Bin2 " _
& "FROM events"
Set rsBins = db.OpenRecordset("Bins")
Do While True
Set rs = db.OpenRecordset("SELECT Min([time]) AS Bin1, " _
& "DateAdd('n',59,Min([time])) AS Bin2 FROM events " _
& "WHERE [time] > (SELECT Max(Bin2) FROM Bins)")
If IsNull(rs!Bin1) Then
Exit Do
Else
rsBins.AddNew
rsBins!Bin1 = rs!Bin1
rsBins!bin2 = rs!bin2
rsBins.Update
End If
Loop
''Demonstration of final query.
''This will only run once and then error becaue the query exists, but that is
''all you need after that, just open the now existing binned query
sSQL = "SELECT events.Time, bins.ID, bins.Bin1, bins.Bin2 " _
& "FROM events, bins " _
& "GROUP BY events.Time, bins.ID, bins.Bin1, bins.Bin2 " _
& "HAVING (((events.Time) Between [bin1] And [bin2]));"
Set qdf = db.CreateQueryDef("Binned", sSQL)
DoCmd.OpenQuery qdf.Name

Better Excel Formula for Complex Lookup

I am trying to improve a complex lookup procedure that I have inherited. The lookup was being generated through several UDF combined with some standard worksheet functions. However, the issue was that when the user updated some data in the source sheet, the re-calc time was unacceptable.
So, I took a look and thought I may be able to write a better Excel formula only solution. Well, I did find a solution, but it is too much for Excel to handle on large data sets, and it crashes (understandably so!) when my VBA runs the formulas against the dataset.
Now, I could implement this in VBA fully, but then the user would have to press a button or something to update after every change. What I would like is a more simpler approach, if there is one, using some of the advanced Excel 2007 formulas. Since I am not as well-versed on those formulas, I am reaching out for some help!
Okay, here is what I have to work with.
SourceSheet
Tid's, Settlement Dates, and month-end prices (layer periods identified by 1,2,3, etc) in columns like below
Tid SettleDate 1 2 3 4 5 6 7 8 9 10 ... n
FormulaSheet
Amongst other columns, I have the following columns
InitLayer LiqdLayer InstrClass Tid SettleDate InitPrice LiqdPrice Position
I also have the layer numbers in columns to the right of the entire data set, like this:
1 2 3 4 5 ... n
What I need to do is fill in the proper price changes in these columns based on some logic in the dataset by looking up the prices on the source sheet.
In psuedo-formula, this is what I need to happen for each layer column in the FormulaSheet
If Layer < InitLayer OR Layer > LiqdLayer Then Return "-"
ElseIf Layer = InitLayer Then (Layered Price - InitPrice) * Position
where Layered Price is obtained by finding the Intersect of the LayerNumber
Column and Tid Row in the SourceSheet
ElseIf Layer = LiqdLayer Then Previous Layered Price * Position
where Previous Layered Price is obtained by finding the Intersect of the Previous
LayerNumber Column and Tid Row in the SourceSheet
Else (LayeredPrice - Previous Layered Price) * 6
where Layered Price and Previous Layered Price are defined as above
End If
I did come up with this formula, which works well on small data sets, but its toooooooooo big and nasty for large data sets, or just too big and nasty period!
=IF(OR(CH$3<$AT6,CH$3>$AU6),"-",IF($AT6=CH$3,(HLOOKUP(CH$3,layered_prices,RIGHT(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4),LEN(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4))-1)-1,FALSE)-$AV6)*$C6,IF($AU6=CH$3,($AW6-HLOOKUP(CG$3,layered_prices,RIGHT(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4),LEN(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4))-1)-1,FALSE))*$C6,(HLOOKUP(CH$3,layered_prices,RIGHT(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4),LEN(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4))-1)-1,FALSE)-HLOOKUP(CG$3,layered_prices,RIGHT(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4),LEN(ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4))-1)-1,FALSE))*$C6)))
Formula Key
CH = Layer Number
CG = Previous Layer Number
AT = InitLayer
AU = LiqdLayer
AX = InstrClass (used to find a separate lookup for Currencies)
T = Tid
G = SettleDate (used to find a separate lookup for Currencies)
AV = InitPrice
AW = LiqPrice
C = Position
layered_prices = named range for the range of prices under the layer columns in SourceSheet
layered_tid = named range for tid rows in SourceSheet
layered_curtid = named range for currency tid rows in Source Sheet (just a separte lookup if InstrType = Currency, formula the same
Are there any other formulas, or combination of formulas that will allow me to get what I am seeking in a more efficient manner than the monstrosity I have created?
I agree with Kharoof's comment. You should break this formula into several columns. From my count, you need 4 more columns. The benefits are two-fold: 1) Your formula gets much shorter because you're not repeating the same function over and over and 2) You save memory because Excel will calculate it once instead of several times.
For instance, you call the exact same ADDRESS function four times. Excel doesn't "remember" what it was when evaluating a formula and so it calculates it anew each time. If you put it in it's own cell, then Excel will evaluate the cell before any cells that depend on it and store it as a value instead of the formula. When other cells reference it, Excel will provide the pre-evaluated result.
First, here's what your final formula should be: (The names in [brackets] indicate that a helper column fits there. It'll be some cell reference like CI$3 but I wasn't sure where you'd want to put it. You'll have to update those references based on where you add these columns.)
=IF(OR(CH$3<$AT6,CH$3>$AU6),"-",IF($AT6=CH$3,([LayerNumber]-$AV6)*$C6,IF($AU6=CH$3,($AW6-[PreviousLayerNumber])*$C6,([LayerNumber]-[PreviousLayerNumber])*$C6)))
And here are the four helper columns:
[ADDRESS] = ADDRESS(MATCH(IF($AX6="CUR",$T6 & " " & $G6,$T6),IF($AX6="CUR",layered_curtid,layered_tid),1),1,4)
[RIGHT] = RIGHT([ADDRESS],LEN([ADDRESS])-1)
[LayerNumber] = HLOOKUP(CH$3,layered_prices,[RIGHT]-1,FALSE)
[PreviousLayerNumber] = HLOOKUP(CG$3,layered_prices,[RIGHT]-1,FALSE)
By splitting it up, each step of the formula is easier to follow / debug as well as faster to process for Excel. If you want some quantitative improvement, the five formulas combined will be around 70% shorter than the single formula you have right now.