I have raw finance text files that I'm importing into Access 2010 and exporting in Excel format. These files contain several 14 character length fields which represent dollar values. I'm having issues converting these fields into currency because of the 14th character. The 14th character is a number represented by a bracket or letter. It also dictates whether the unique field is a positive or negative value.
Positive numbers 0 to 9 start with open bracket { being zero, A being one, B being two,...I being nine.
Negative numbers -0 to -9 (I know, -0 is a mathematical faux pas but stay with me. I don't know how else to explain it.) start with close bracket } being -0, J being -1,K being -2,...R being -9.
Example data (all belonging to the same field/column):
0000000003422{ converted is $342.20
0000000006245} converted is -$624.50
0000000000210N converted is -$21.05
0000000011468D converted is $1,146.84
Here's the query that I'm working with. Each time I execute it, the entire field is deleted though. I would prefer to stick to a SQL query if possible but I'm open to all methods of resolution.
SET FIELD_1 = Format(Left([FIELD_1],12) & "." & Mid([FIELD_1],13,1) & IIf(Right([FIELD_1],1)="{",0,IIf(Right([FIELD_1],1)="A",1,IIf(Right([FIELD_1],1)="B",2,IIf(Right([FIELD_1],1)="C",3,IIf(Right([FIELD_1],1)="D",4,IIf(Right([FIELD_1],1)="E",5,IIf(Right([FIELD_1],1)="F",6,IIf(Right([FIELD_1],1)="G",7,IIf(Right([FIELD_1],1)="H",8,IIf(Right([FIELD_1],1)="I",9,"")))))))))),"$##0.00"), IIf(Right([FIELD_1],1)="}",0,IIf(Right([FIELD_1],1)="J",1,IIf(Right([FIELD_1],1)="K",2,IIf(Right([FIELD_1],1)="L",3,IIf(Right([FIELD_1],1)="M",4,IIf(Right([FIELD_1],1)="N",5,IIf(Right([FIELD_1],1)="O",6,IIf(Right([FIELD_1],1)="P",7,IIf(Right([FIELD_1],1)="Q",8,IIf(Right([FIELD_1],1)="R",9,"")))))))))),"-$##0.00")
here is a function that you can call to convert an input string like the ones in your example into a string formatted as you desire.
Private Function ConvertCurrency(strCur As String) As String
Const DIGITS = "{ABCDEFGHI}JKLMNOPQR"
Dim strAlphaDgt As String
Dim intDgt As Integer, intSign As Integer
Dim f As Integer
Dim curConverted As Currency
strAlphaDgt = Right(strCur, 1) ' Extract 1st char from right
f = InStr(DIGITS, strAlphaDgt) ' Search char in DIGITS. Its position is related to digit value
intDgt = (f - 1) Mod 10 ' Converts position into value of the digit
intSign = 1 - 2 * Int((f - 1) / 10) ' If it's in the 1st half is positive, if in the 2nd half of DIGITS it's negative
curConverted = intSign * _
CCur(Left(strCur, Len(strCur) - 1) & _
Chr(intDgt + 48)) / 100 ' Rebuild a currency value with 2 decimal digits
ConvertCurrency = Format(curConverted, _
"$#,###.00") ' Format output
End Function
If you need to have a Currency as returned value, you can change the type returned from String to Currency and return the content of curConverted variable.
Bye.
Related
I'm a beginner to programming and to Progress, first post on StackOverflow, hope I'm posting in the right place!
I have a fillin field where I enter a time (hh:mm), character format. I also have two arrows, one pointing forward and one backward, and I want them to add / subtract 20 min respectively when pushed.
What would be a good way to write code for this? Turn the current time value to integer and seconds past midnight and then add /subtract 1200 sec? How would I get the result back to a hh:mm format to display in the fillin?
Any help greatly appreciated!
/Ellen
I think this will do what you need. Have the ON CHOOSE triggers on your arrow buttons run the changeMins procedure. Pass in the character time string from your fill-in and either "Add" or "Subtract". The output value will be the new adjusted time string. You can then set the screen value of your fill-in to that output value.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iHr AS INTEGER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
/* Split the time string into hours and minutes */
ASSIGN
iHr = INTEGER(ENTRY(1, pcTime, ":"))
iMn = INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR.
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Correct for boundaries */
IF iMn > 59 THEN
ASSIGN
iMn = iMn - 60
iHr = iHr + 1.
IF iMn < 0 THEN
ASSIGN
iMn = iMn + 60
iHr = iHr - 1.
IF iHr > 23 THEN iHr = iHr - 24.
IF iHr < 0 THEN iHr = iHr + 24.
/* Build the new time string */
pcTime = STRING(iHr, "99") + ":" + STRING(iMn, "99").
END PROCEDURE.
In this example, change the cTime string to different times and run it.
The Progress TIME function returns the integer number of seconds past midnight. So your idea of converting to such an integer is consistent with other uses within the 4gl which is a positive.
Personally I would keep the UI and the internal storage independent. So I would probably have a variable for the hour, another for the minute and a 3rd for seconds (if you need that). And I would use integers, rather than characters, for all 3.
I've no idea what version of Progress or what the environment that you are running in is but this quick and dirty little snippet might have some useful tidbits:
define variable hh as integer no-undo format ">9".
define variable mm as integer no-undo format "99".
define variable ss as integer no-undo format "99".
define variable myTime as integer no-undo.
form
hh mm ss
with
frame a
.
on value-changed of hh in frame a do:
if integer( self:screen-value ) > 23 then
do:
hh = 23.
display hh with frame a.
end.
end.
on value-changed of mm in frame a do:
if integer( self:screen-value ) > 59 then
do:
mm = 59.
display mm with frame a.
end.
end.
on value-changed of ss in frame a do:
if integer( self:screen-value ) > 59 then
do:
ss = 59.
display ss with frame a.
end.
end.
update hh mm ss with frame a.
myTime = (( hh * 3600 ) + ( mm * 60 ) + ss ).
display string( myTime, "hh:mm:ss am" ).
Deriving from TheDrooper's Code I would probably write something like the following code to fully utilize the built-in functions.
Shorter code that is still at least as easy to understand is often preferable.
Also consider that Progress does not have an optimizing compiler. If you can replace replace several simple statements with less statements (even if those are more complex and powerful than needed) the code is not only more maintainable but also faster.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
iMn = INTEGER(ENTRY(1, pcTime, ":")) * 60
+ INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR. /* Calculate minutes since midnight */
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Build the new time string */
pcTime = string(iMn * 60, 'HH:MM'). /* Convert minutes to seconds and convert the result to a string */
END PROCEDURE.
If you need to allow for higher hour values (eg. if the field doesn't represent a time of day but a time interval) you can't just convert the resulting integer with the string function. In that case you could write
pcTime = string(iMn / 60, '99') + ':' + string(iMn mod 60, '99').
(Both TheDrooper and Tom Bascom seem to assume time of day.)
I have an ID field that is just numbers but the ID's vary in length. If the number is 16 characters long then I need to display 'x' + last 4 of the ID, if not then just display the last 10 of the ID.
If your ID field is already a string:
CASE character_length([ID])
WHEN 16 THEN 'x' || substring([ID],character_length([ID]) - 3)
ELSE substring([ID],character_length([ID]) - 9)
END
If your ID is stored as an integer, I'd recommend creating a new data item that casts it as a varchar (say named 'ID String'):
CAST([ID],VARCHAR(16))
Then substitute the new value in the first expression:
CASE character_length([ID String])
WHEN 16 THEN 'x' || substring([ID String],character_length([ID String]) - 3)
ELSE substring([ID String],character_length([ID String]) - 9)
END
After searching the forum, I did not find a good solution for this question. If I missed it, please tell me.
I need to count the unique values in one column in EXCEL 2010.
The worksheet has 1 million rows and 10 columns. All cell values are string or numbers.
I used the solution at Count unique values in a column in Excel
=SUMPRODUCT((A2:A1000000<>"")/COUNTIF(A2:A100000,A2:A1000000&""))
But, it runs so long time that the EXCEL is almost frozen. And, it generates 25 processes in Win 7.
Are there more efficient ways to do it?
Also, in the column, all values have for format of
AX_Y
here, A is a character, X is an integer, Y is an integer from 1 to 10.
For example, A5389579_10
I need to cut off the part after (including) undersocre. for the example,
A5389579
This is what I need to count as unique values in all cells in one column.
For example, A5389579_10
A1543848_6
A5389579_8
Here, the unique value has 2 after removing the part after underscore.
How to do it in EXCEL VBA and R (if no efficient solution for EXCEL)?
If you want to do this by VBA, you can take advantage of the Collection object. Since collections can only contain unique values, trying to add all of your input data to a collection will result in an array of unique values. The code below takes all the variables in a selected range and then outputs an array with distinct values to an other sheet (in this case a sheet named Output).
Sub ReturnDistinct()
Dim Cell As Range
Dim i As Integer
Dim DistCol As New Collection
Dim DistArr()
Dim OutSht As Worksheet
Dim LookupVal As String
Set OutSht = ActiveWorkbook.Sheets("Output") '<~~ Define sheet to putput array
If TypeName(Selection) <> "Range" Then Exit Sub
'Add all distinct values to collection
For Each Cell In Selection
If InStr(Cell.Value, "_") > 0 Then
LookupVal = Mid(Cell.Value, 1, InStr(Cell.Value, "_") - 1)
Else
LookupVal = Cell.Value
End If
On Error Resume Next
DistCol.Add LookupVal, CStr(LookupVal)
On Error GoTo 0
Next Cell
'Write collection to array
ReDim DistArr(1 To DistCol.Count, 1 To 1)
For i = 1 To DistCol.Count Step 1
DistArr(i, 1) = DistCol.Item(i)
Next i
'Outputs distinct values
OutSht.Range("A1:A" & UBound(DistArr)).Value = DistArr
End Sub
Note that since this code writes all the distinct values to a single column in the OutSht-sheet, this will return an error if there are more than 1,048,576 distinct values in your dataset. In that case you would have to split the data to be filled into multiple output columns.
For your specific request to count, use the below in a formula like =COUNTA(GetUniques(LEFT("A1:A100000",FIND("_","A1:A100000")-1)) entered as an array formula with Ctrl+Shift+Enter.
It also accepts multiple ranges / values (e.g. GetUniques("A1:A10","B2:E4"))
Function GetUniques(ParamArray args())
Dim arg, ele, arr, i As Long
Dim c As Collection
Set c = New Collection
For Each arg In args
If TypeOf arg Is Range Then
If arg.Count = 1 Then
arr = array(arg.value)
Else
arr = arg.Value
End If
ElseIf VarType(arg) > vbArray Then
arr = arg
Else
arr = Array(arg)
End If
For Each ele In arr
On Error Resume Next
c.Add ele, VarType(ele) & "|" & CStr(ele)
On Error GoTo 0
Next ele
Next arg
If c.Count > 0 Then
ReDim arr(0 To c.Count - 1)
For i = 0 To UBound(arr)
arr(i) = c(i + 1)
Next i
Set c = Nothing
GetUniques = arr
End If
End Function
edit: added a performance optimisation for ranges (loads them at once into an array - much faster than enumerating through a range)
In R:
# sample data
df <- data.frame(x=1:1000000,
y=sample(1e6:(1e7-1),1e6,replace=T))
df$y <- paste0("A",df$y,"_",sample(1:10,1e6,replace=T))
# this does the work...
length(unique(sub("_[0-9]+","",df$y)))
# [1] 946442
# and it's fast...
system.time(length(unique(sub("_[0-9]+","",df$y))))
# user system elapsed
# 2.01 0.00 2.02
In excel 2010... in the next column add (if original data was in A:A add in B1)
= 1/COUNTIF(A:A,A1) and copy down col B to the bottom of your data. Depending on your PC it may chug away calculating for a long time, but it will work. Then copy col B & paste values over itself.
Then SUM col B
I'm extracting rows from a txt file.
This row contains values like this:
DESCRIPTION 1 1.234,00 15.980,00 [etc.]
I would like to extract these values (I mean only numeric values).
So I thought to find first comma, execute a for cycle backwards until first White space and execute a For cycle forward for decimals digits.
The I should go to the second comma and perform these cycles again.
Can you suggest some code that could be useful for my solution?
From your description, if you just need the decimal number before the comma, then you can do this with a pretty simple regex:
Dim s = "DESCRIPTION 1 1.234,00 15.980,00"
Dim pattern = "\d+(\.\d+)?,\d+"
Dim matches = System.Text.RegularExpressions.Regex.Matches(s, pattern)
For Each match in matches
Console.WriteLine(match.Value)
Next
'Outputs:
'
'1.234,00
'15.980,00
Here's a quick breakdown of the regex:
\d+ - \d is shorthand for [0-9], which just means "any numeric character". The + just indicates "one or more"
\. - this just matches a period character.
, - this just matches a comma.
( ... ) - parentheses just creates a group (think of it as a sub-regex)
? - question marks mean that the previous item is optional. In this case, that means that the group matching (\.\d+)? is optional, which allows you to match both 0.000,00 and 0,00
In that regex, if the comma and period are optional, then you can add a ? after them.
My Visual Basic knowledge is pretty limited, but can't you utilize the IsNumeric function available in VB.NET?
Someting like this:
' initial string/row/etc
Dim s As String = "DESCRIPTION 1 1.234,00 15.980,00"
' Split string based on spaces
Dim words As String() = s.Split(New Char() {" "c})
' Use For Each loop over split and display them
Dim word As String
For Each word In words
If IsNumeric(word) Then
Console.WriteLine(word & " is numeric")
Else
Console.WriteLine(word & " is not numeric")
End If
Next
I think you'll be needing to look at System.Text.Regex.
Match m = Regex.Match("DESCRIPTION 1 1.234,00 15.980,00", ".*?( [0-9]*?.(?'n1'[0-9]+),(?'n2'[0-9]+)))
While m.Success
System.Diagnostics.Debug.WriteLine(m.Groups["n1"].Value + " "+m.Groups["n2"].Value);
m = m.NextMatch()
End While
If the columns are fixed width, you can get the values like this:
Dim input As String = "DESCRIPTION 1 1.234,00 15.980,00"
Dim col1 As String = input.SubString(17, 12).Trim()
Dim col2 As String = input.SubString(29).Trim()
Can this equation be reworked to a correctly formatted string?
Dim Total = MyNumber / 100 * MyVAT
Produces error:
"Input string was not in a correct format"
try with this statement
Dim Total = Val(MyNumber) / 100 * Val(MyVAT)
You should really set Option Strict to on. Then this would never compile since MyNumber is a string(as you've commented).
You should first ensure that the input is numeric, for example with Decimal.TryParse:
Dim Total As Decimal
Dim num As Decimal
If Decimal.TryParse(MyNumber, num) Then
Total = num / 100 * MyVAT
End If
Now you can use Decimal.ToString(format) to convert it to a string with the desired format. For example(assuming you want two decimal places):
Dim output As String = Total.ToString("N2")
Standard Numeric Format Strings
use like below
Total.ToString("#")
More Detail:- http://msdn.microsoft.com/en-in/library/0c899ak8.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-2