I am using VBA to pull in a bunch of data from several workbooks into several worksheets and then process that data later.
WorkFlow: As I pull the data into my workbook, I first place all my data into arrays before outputting them into my worksheets.
Problem
While I am pulling in data, I noticed that some of the "Hours" or "Work Order Number" data are pulled into my arrays in the form of dates or datetime, when they should just be some number format.
e.g.:
I should be getting:
Number of Hours: 2; Work Order Number: 41235612
Instead, I am getting:
Number of Hours: 0-Jan-00 (equivalent to 1/0/1900, which was originally entered as "0")
Work Order Number: 0-Jan-00
I found that the issue lies in the source documents, where it looks like Excel may have automatically (although incorrectly) converted those cell values into a date type form, when first entered as a plain number.
Question
How can I pull data from the different documents into my arrays so
that all values in that column come out as what they should be (a
Number)?
Is there a way to format the value before placing it into the array?
Can I format the value into a number format like the "General" number format (or something that makes sense) while it is being pulled?
I would rather not make any changes within the source documents, and just format the values I am pulling as I pull them.
you can use Range().value2 instead of .value property
the .value2 dont accept Currency and Date formats, so it should read your hours values properly.
for example cell A1 has value 2 and saved with Date format.
so Range("A1").value will return date value "1/2/1900"
Range("A1").value2 will return numeric value 2 you can save in your array.
You can format the values with Clng() or CDbl().
for example:
CDbl(Range("A1").Value)
But if the cell has non numeric value, for example text, code throws an error!
And you can format the cells (source or destination) with .NumberFormat property
Range("A1").NumberFormat = "General"
Related
I have a bank register on the left. I want a code on the right to tell me sum all the times the description "Rent" is paid in that month. I've tried index, sumproduct and sumif. I can't find the correct way to have it search by month, year and text.
It isn't clear what format your tables are in.
SUMIFS is the easiest solution but it will only work if the bank register uses the Excel date format. It gets a little complicated if those dates are formatted as text.
Building a SUMIFS formula begins with the SUM RANGE. This is a welcome change away from the backwards construction and wrongly named SUMIF (looks more like an IFSUM if you ask me)
=SUMIFS(SUM RANGE, CRITERIA 1 RANGE, CRITERIA 1, CRITERIA 2 RANGE, CRITERIA 2, CRITERIA 3 RANGE, CRITERIA 3...)
Using your example:
SUM RANGE, the 'Debit' column of the bank register (BankRegisterDebitRange)
CRITERIA 1 RANGE, the 'Description' column of the bank register (BankRegisterDescriptionRange)
Criteria 1, the string "Rent"
Criteria 2 Range, the 'Date' column of the bank register (BankRegisterDateRange)
Criteria 2, this formula string ">="&EOMONTH(RentTableDate,-1)+1
Criteria 3 Range, the 'Date' column of the bank register (BankRegisterDateRange)
Critera 3, this formula string "<="&EOMONTH(RentTableDate,0)
Putting it together:
=SUMIFS(BankRegisterDebitRange, BankRegisterDescriptionRange, "Rent", BankRegisterDateRange, "=>"&EOMONTH(RentTableDate, -1)+1, BankRegisterDateRange, "<="&EOMONTH(RentTableDate, 0))
If your data is fornatted as text then you need to decide if you want to use helper column or not or if you want a single formula. A helper column may be desirable if you want to use SUMIFS or perform additional analysis with simple formulas. A different array based formula may be desirable if you don't want a helper column, for example SUMPRODUCT or SUM.
There's already great answer showing how to use SUMPRODUCT so here is an example of how to build an array formula with SUM when your data is fornatted as text.
=SUM((BankRegisterDebitRange)*(BankRegisterDescriptionRange="Rent")*(DATEVALUE(BankRegisterDateRange)=>(EOMONTH(RentTableDate,-1)+1))*(DATEVALUE(BankRegisterDateRange)<=MONTH(RentTableDate,0)))
This is a true array formula and must be entered with Ctrl + Shift + Enter
I was surprised by some of my results during testing. Mainly how Excel was still treating text as dates in some cases but not others. For example, DATEVALUE was needed on the bank register side but that wasn't the situation with the rent table because EOMONTH worked just fine without it. I believe it is related to another odd behavior: when I create a text formatted cell in A1 and then enter a date, regardless if it is preceded by an apostrophe or not, then if I enter =A1+1 in any other cell, that cell becomes formatted as text and displays Excel's numerical value of the day after the date in A1. What I expected was a #Value! error. I suspect this is what keeps EOMONTH from bonking and the mysterious nature of arrays somehow preventing that behavior from carrying over to SUMIFS... but I really do not know why this is happening.
Suppose you have the following named ranges:
BankDate being the date column in your bank register table;
Desc being the description column in your bank register table;
Dr being the debit column in your bank register table.
If the look up date are text, you can use the following formula:
=SUMPRODUCT((Desc="Rent")*(TEXT(BankDate,"mmm yyyy")=G2)*Dr)
If the look up date are date, you can use the following formula:
=SUMPRODUCT((Desc="Rent")*(TEXT(BankDate,"mmm yyyy")=TEXT(G4,"mmm yyyy"))*Dr)
Change G2 or G4 in the above formulas to suit your actual case.
The logic is to use TEXT function to convert the BankDate into the same format as your look up date, and then use SUMPRODUCT function to return the rent by month.
Ps. using SUMPRODUCT may be an overkill, as SUMIFS suggested by #ProfoundlyOblivious is actually faster in excel calculation.
Ps2. as pointed out by #ProfoundlyOblivious, there is an interesting behavior in Excel in terms of treating text date as 'real' date in certain scenarios. Although it may not be relevant to the question of this post, I'd like to share some of my test results here for anyone interested:
I am using a flat file data provider in SSIS to import data from an external system. I don't have any control over the file, it is pushed out on a weekly basis, and I pick it up from a common folder.
The first two columns of the CSV are dates. Part of the way through the file, the date format has changed from date to numeric as follows:
Service_Date, Event_Datetime
2018-04-30,2018-04-30 21:18
43220,43220.92412
As you can see, the format changed from date to numeric. Other date columns not shown here also have changed.
Obviously, this is breaking the data flow task.
Aside from going into Excel and saving the CSV with the proper column format, is there any way within SSIS can convert on the fly so that the job doesn't fail and require manual intervention?
These data values 43220,43220.92412 are called date serials, you can get the date value in many approaches:
(1) Using A derived Column
You can convert this column to float then to datetime using a derived column:
(DT_DATE)(DT_R8)[dateColumn]
References
convert Excel Date Serial Number to Regular Date
Is there a better way to parse [Integer].[Integer] style dates in SSIS?
(2) Using a script component
You can use DateTime.FromOADAte() function, as example: (code in VB.NET)
If Row.ServiceDate_IsNull = False AndAlso String.IsnullOrEmpty(Row.ServiceDate) Then
Dim dblTemp as Double
If Double.TryParse(Row.ServiceDatemdblTemp) Then
Row.OutputDate = DateTime.FromOADate(dblTemp)
Else
Row.OutputDate = Date.Parse(Row.ServiceDatemdblTemp)
End
End If
Reference
SSIS Script Task - VB - Date is extracting as INT instead of date string
I was able to solve the problem using a variation of the derived column. This expression would catch the column obviously formatted as a date, and convert it to a date, otherwise it converts the date serial to a float first, then to a date
FINDSTRING(Date_Service,"-",1) != 0 ? (DT_DATE)Date_Service : (DT_DATE)(DT_R8)Date_Service
I have a table in a sheet called "DATA" with the following headers:
Country, Code, Series, 2000, 2001, 2002, 2003, 2004, 2005, 2006.
In each row I have data for all columns always, except for years. Some rows have data for some years only, others all years.
In sheet "DATA AVAILABILITY" I want to build a formula which returns the most recent year for which there is available information in sheet "DATA", given a certain country and code. The relevant country and codes are in cells E2 and A3 of "DATA AVAILABILITY". Let's say, for argument's sake, that these are Country: Angola; Code: 3.
I have first built an array MATCH formula with two criteria:
={MATCH(1,('DATA AVAILABILITY'!E$2=Data!$B$1:$B$104701)*('DATA AVAILABILITY'!$A3=Data!$D$1:$D$104701),0)}
This has successfully given me the row in "DATA" in which there is information for Angola and code 3, which is row 1776.
Now I would like to get the header for the last non-empty cell of row 1776 in sheet "DATA". For this, I started by building a formula that would give me the column number of that cell:
=LOOKUP(2,1/(Data!1776:1776<>""),COLUMN(Data!1776:1776))
It successfully returned the number 53 which, after verifying on sheet "Data" is the correct number. I then added to the formula so that it would return the header, i.e., the year, instead of the column number:
=INDEX(Data!$A$1:$BE$104701,1,LOOKUP(2,1/(Data!1776:1776<>""),COLUMN(Data!1776:1776)))
Finally, I would like to combine both formulas (the MATCH and the INDEX formulas) so that the final result would be returned with one formula only. However, when I try to do it, something goes wrong and an error comes up - I am not even able to enter the formula. When I click ENTER, Excel returns an error that says there is a problem with the formula. what I have tried to do is to replace, in the LOOKUP within the INDEX, "Data!1776:1776" for the array MATCH formula that returns the row in which the information is - in my example, row 1776. The final formula which is not working is as follows:
=INDEX(Data!$A$1:$BE$104701,1,LOOKUP(2,1/(MATCH(1,('DATA AVAILABILITY'!E$2=Data!$B$1:$B$104701)*('DATA AVAILABILITY'!$A3=Data!$D$1:$D$104701)<>""),COLUMN(MATCH(1,('DATA AVAILABILITY'!E$2=Data!$B$1:$B$104701)*('DATA AVAILABILITY'!$A3=Data!$D$1:$D$104701))))
What may I be doing wrong?
Thank you
Hard to tell what is going on without at least some sample data (as a table or linked workbook -- NOT as a screenshot), and I would do it a bit differently.
You can simplify your formula to get the Header of the column that contains the last data in row 1776:
=LOOKUP(2,1/(Data!1776:1776<>""),Data!$1:$1)
To return the column number:
=LOOKUP(2,1/(Data!1776:1776<>""),COLUMN(Data!$1:$1))
To return the Appropriate Row Number (enter with CSE):
=MAX(($E$2=Data!$B$1:$B$104701)*(A3=Data!$D$1:$D$104701)*ROW($A$1:$A$104701))
To return the last filled in value, in the row that matches Country and Code, we make use of the fact that using 0 for the column number in the INDEX function returns all the columns in the designated row:
=LOOKUP(2,1/(INDEX(Data!$B$1:$BE$104701,MAX(($E$2=Data!$B$1:$B$104701)*(A3=Data!$D$1:$D$104701)*ROW($A$1:$A$104701)),0)<>""),INDEX(Data!$B$1:$BE$104701,MAX(($E$2=Data!$B$1:$BE$104701)*(A3=Data!$D$1:$D$104701)*ROW($A$1:$A$104701)),0))
entered with CSE.
I'm using Excel 2013 and I have a sheet that gets data from SQL Server 2012.
The query has 4 parameters and gets their values from cells. The box is checked to Refresh Automatically when cell value changes. I want to avoid using VBA.
Data is returned when correct values are in the cells referenced by the parameters.
The issue is with the cells that are dates. In another cell I create a formula that checks if the date is valid, if it is valid then format the entered value as YYYY-MM-DD else format today's date. On the first change the data is updated, but on subsequent changes the data doesn't update. The cell with the formula is used as the value for the parameter.
The issue seems to be that cells used for query parameters can only contains data or a simple function, no nested functions, for a change event to be recognized.
In my case: Cell I4 formats the user entered start date using =TEXT(F4,"yyyy-mm-dd"). If it is a date it is converted to text as SQL expects it else it is the same as the input. Cell J4 contains function for first day of current month =TEXT(TODAY()-DAY(TODAY())+1, "yyyy-mm-dd"). The cell used for the parameter is H4 and it contains =IF(F4=I4, J4, I4).
The next row (5) is similar testing the end date and J5 provides the month end date: =TEXT(EOMONTH(TODAY(),0), "yyyy-mm-dd") . Then the parameter cell for the end date contains =IF(F5=I5,J5, I5).
My next step is to take the query to a stored procedure for better input validation. Since excel seems to only allow 1 parameter for stored procedures (unless ODBC is used) I will have to concatenate the inputs is a cell like this: =TRIM(CONCATENATE(H2, "■",F3, "■", H4, "■", H5)) . Then split the criteria in the SP. Hope this helps someone.
I have a SSRS report I'm working on. What I would like to do is get the value of one field from its own dataset and subtract the value of another field from a different dataset. I can do this; however, the values are grouped so rather than giving me an individual value it gives me: (sum of all completed) - (sum of all completed the previous year).
Here is my expression I am using for the column "Compared to last year"
=SUM(Fields!Completed.Value, "MTDSales") - SUM(Fields!Completed.Value, "MTDminus1")
"MTDSales" and "MTDMinus1" are 2 seperate datasets. MTDSales Dataset is the current months sales outcomes grouped by company MTDMinus1 dataset is last years figure for this current month as i am comparing the 2 months separately.
I had to do this in a report where I was pulling current data from one database and older data from a data warehouse and combining. You will need to do a few things:
1. Establish a match field
This can be as simple as a single column. If you need to match on multiple fields you will need to add a calculated field to each dataset that you can match on. Assuming you need to match on company and financial year and each dataset returns one year of data, this might look something like match_id (assuming numeric values - otherwise you might need to use | or something as a separator):
`="A" & Fields!fin_year.Value & "B" & Fields!cust_id.Value`
2. Retrieve the data to the source field.
In your tablix add a column as you have to hold the looked up value:
=Lookup(Fields!matchId.Value, Fields!matchId.Value, Fields!Completed.Value, "MTDminus1")
3. Use the data
Now you can aggregate the data or do whatever further calculations you wish as if the field was part of your original dataset.