Convert numbers to Excel Column Letters in Power Query - powerquery

I would like to transform Numbers to Excel Column Letters.
There are several threads on the topic but it seems that it has not been answered yet for M language.
I managed to address it with the following code but I am sure someone will have a much more efficient code to propose:
= Table.AddColumn(Source, "Column Letter", each Text.Combine({ if (Number.IntegerDivide(
Number.IntegerDivide(
Number.IntegerDivide([Column1]-1,1),26)-1,26))<1 then "" else Character.FromNumber( 64+Number.IntegerDivide(Number.IntegerDivide(Number.IntegerDivide([Column1]-1,1),26)-1,26)),
(if Number.IntegerDivide([Column1]-1,26)-
Number.Abs(
Number.IntegerDivide(
Number.IntegerDivide([Column1]-1,26)
-1,26)*26)<1 then "" else
Character.FromNumber(64+
Number.IntegerDivide([Column1]-1,26)-Number.Abs(
Number.IntegerDivide(
Number.IntegerDivide([Column1]-1,26)-1,26)*26))),
(if [Column1]-Number.Abs(
Number.IntegerDivide([Column1]-1,26)*26)<1 then "" else Character.FromNumber(64+[Column1]-Number.Abs(
Number.IntegerDivide([Column1]-1,26)*26)))}))
Thank you!

I think this custom function will work also:
Edit*Formula for b corrected as per #VassilisAgapitos note below
(n as number)=>
let
//make sure it is an integer
a = Number.IntegerDivide(n,1),
b = Number.IntegerDivide(Number.IntegerDivide(a-1,26)-1,26),
L1 = if b = 0 then null else Character.FromNumber(b+64),
c = Number.IntegerDivide(a-b*26*26-1,26),
L2 = if c = 0 then null else Character.FromNumber(c+64),
d = a-b*26*26-c*26,
L3 = Character.FromNumber(d+64)
in
Text.Combine({L1,L2,L3})
You can use it as a Table.AddColumn argument:
#"Added Custom" = Table.AddColumn(#"Changed Type", "Column Letter", each fnNumberToExcelColumn([Column Number]), type text)

An uglier version than Ron's, just for fun
=(if [Column1] <703 then "" else Character.FromNumber(64+Number.IntegerDivide([Column1]-27,26*26)))
&( if [Column1] >26 then Replacer.ReplaceText(Character.FromNumber(64+ Number.Mod( Number.IntegerDivide([Column1]-1,26) ,26) ), "#", "Z") else "" )
&(Character.FromNumber(65+Number.Mod([Column1]-1,26)))

Related

Pause every 25th URL being scraped using Power Query

I'm scraping a website with different URLs (about 1000) and only able to get 25 query due to website limitation (of course!). I have used Function.InvokeAfter with Number.RandomBetween but no luck.
However, I found that if I wait/pause about an hour or so after the 25th URL, I could query again. Now, I would like to automate that part.
I created a column called Wait with a value on every 25th row on the URL_List table with a number between 60-90 so that every 25th URL, I can have it wait 60 to 90 mins but I don't know how to implement. If I could replace Number.RandomBetween(10,20) with Number.RandomBetween(10,[Wait]). Or also open to other ideas.
Here's what I have done so far:
Function for fGetResults:
let Scrape=(URL) =>
let
Source = Web.Page(Web.Contents(URL)),
Data0 = ()=>Source{0}[Data],
#"Transposed Table" = Table.Transpose(Function.InvokeAfter(Data0, #duration(0,0,0,Number.RandomBetween(10,20))))
in
#"Transposed Table"
in Scrape
Query:
let
Source = Excel.CurrentWorkbook(){[Name="URL_Table"]}[Content],
#"Added Custom" = Table.AddColumn(Source, "Custom", each fGetResults([URL_List]))
in
#"Added Custom"
Merci beaucoup...
#############
UPDATE
#############
I did some more digging and found this and this. Instead of a Wait column with a number from 60-90mins after 25th URL or so, I just place a "yes".
WaitMinutes function:
let
Wait = (minutes as number, action as function) =>
if (List.Count(List.Generate(() => DateTimeZone.LocalNow() + #duration(0,0,minutes,0), (x) => DateTimeZone.LocalNow() < x, (x) => x)) = 0) then null else action()
in
Wait
Now, I tried it but is now giving me an error Token Comma expected at the then statement on the New Query, any thoughts?
New Query:
let
Source = Excel.CurrentWorkbook(){[Name="URL_Table"]}[Content],
#"Added Custom" = Table.AddColumn(Source, "Custom", each [Wait] = "yes" then WaitMinutes(Number.RandomBetween(60,90), () => fGetResults([URL_List])) else fGetResults([URL_List]))
in
#"Added Custom"
#############
UPDATE2
#############
I think I found the issue. Simply forgot the "if" on the if-then-else statement. The only thing is, there maybe a more elegant solution for this but this works for now. Let me know if you have a more efficient solution. Thank you.
let
Source = Excel.CurrentWorkbook(){[Name="URL_Table"]}[Content],
#"Added Custom" = Table.AddColumn(Source, "Custom", each if [Wait] = "yes" then WaitMinutes(Number.RandomBetween(60,90), () => fGetResults([URL_List])) else fGetResults([URL_List]))
in
#"Added Custom"
#############
UPDATE3: SOLUTION
#############
More problems, I think my usage for minutes is not correct but followed exactly what the website said and used seconds and removed the random function on the Query.
WaitSeconds function:
let
Wait = (seconds as number, action as function) =>
if (List.Count(List.Generate(() => DateTimeZone.LocalNow() + #duration(0,0,0,seconds), (x) => DateTimeZone.LocalNow() < x, (x) => x)) = 0) then null else action()
in
Wait
Query (15mins seems to work which is same as 900sec):
let
Source = Excel.CurrentWorkbook(){[Name="URL_Table"]}[Content],
#"Added Custom" = Table.AddColumn(Source, "Custom", each if [Wait] = "yes" then WaitSeconds(900, () => fGetResults([URL_List])) else fGetResults([URL_List]))
in
#"Added Custom"

RPGLE End Field Name with an Ascending Variable

I have a simple question that involves numbering the end of field names with a numeric variable.
Exp: FIELD,X = FIELD01, FIELD,X = FIELD02, ETC....
Z-ADD 1 X 2.0
DOU X = 10
FIELD,X IFEQ *BLANK
MOVE FIELDREAD FIELD,X
ENDIF
ADD 1 X
ENDDO
I could do this in RPG but I'm not sure how to do this in RPGLE. When I try to I get this error: Entry contains data that is not valid; only valid data is used.
Thanks!!
The syntax for array indexes in RPGLE is ARR(X).
FIELD(X) IFEQ *BLANK
If you're not sure about the syntax for RPGLE, try writing a little bit of code in RPG, and then use CVTRPGSRC to convert it to RPGLE.
fSomeFile if e k disk
D ArMax c 10
D Key1 S like(KeyField)
D Field S dim(ArMax) like(FieldRead)
Exsr $Sample1;
*inlr = *on;
return;
Begsr $Sample1;
c z-add 1 X 3 0
setll (key1) SomeFile
dou x = 10;
if Field(x) = *blank;
reade (key1) SomeFile;
if %found(SomeFile);
Field(x) = FieldRead;
endif;
endif;
X = X +1;
enddo;
Endsr;

Divisor is equal to zero, Need guidance with case statement

This is my query & I'm getting error divisor is equal to zero, I know i need to build this as a case statement just tried a few things and cant get it to work, thanks in advance.
NVL(ROUND(((SELECT PC.BUCKET_ACCUM_COST
FROM PART_CB PC WHERE
PART_CB_NO = '201'
AND PC.PART_NO = I.PART_NO
AND
PC.CONTRACT = P.CONTRACT
AND
PC.TOP_LEVEL_PART_NO || '' =
Z_BEL_FINANCE_API.GET_PART_COST_TOP_PART_NO(P.CONTRACT, P.PART_NO,
P.COST_SET, P.ALTERNATIVE_NO,
P.ROUTING_ALTERNATIVE_NO)
AND
PC.COST_SET = P.COST_SET
AND
PC.COST_BUCKET_ID != 'SYS'
AND
PC.TOP_ALTERNATIVE_NO =
Z_BEL_FINANCE_API.GET_PART_COST_TOP_ALT_NO(P.CONTRACT, P.PART_NO,
P.COST_SET, P.ALTERNATIVE_NO,
P.ROUTING_ALTERNATIVE_NO)
AND
PC.TOP_ROUTING_NO =
Z_BEL_FINANCE_API.GET_PART_COST_TOP_ROUTING_NO(P.CONTRACT, P.PART_NO,
P.COST_SET, P.ALTERNATIVE_NO,
P.ROUTING_ALTERNATIVE_NO)
AND
PC.BUCKET_SEQ = Z_BEL_FINANCE_API.GET_PART_COST_BUCKET_SEQ(P.CONTRACT,
P.PART_NO, P.COST_SET, P.ALTERNATIVE_NO,
P.ROUTING_ALTERNATIVE_NO)) /
(SELECT WC_RATE
FROM WCT
WHERE WORK_CENTER_NO = 'COST1'
AND COST_SET = '1'
AND CONTRACT = P.CONTRACT)), 4), 0) MACHINE_SETUP_TIME,
The only dividing in this mess is here:
/ (SELECT WC_RATE FROM WCT ...)
If you don't want to divide with zero, you'll have to handle it.
For example, use DECODE (or CASE) and - if you want to get 0 as the result, divide with a very large number (e.g. 1E99)

DAX in calculated column

I have two tables as below.
Table1
CaseId
66787
Table2
PrimaryKey CaseId SeqNo Status Primary Code CodeCareNo
85248 66787 6 Active N 876 8775568
70728 66787 1 Inactive N 876 3661794
79008 66787 5 Active Y 876 3766066
86868 66787 7 Active Y 876 3287735
Table 1 has one to many relationships with Table2 and the relating column in CaseId.
I have a requirement to create a calculated column in Table1 in my model project. The calculated column should display a text like (Code) CodeCareNo (eg: (876) 3766066) for each CaseId in Table1 with the values from Code and CodeCareNo columns of Table2 which has Primary = “Y” and Status = “Active” with Seq No as the minimum value among the primary active code numbers for the CaseId. Also if the Code or CodeCareNo is null the calculated column should show blank value. I am able to get the desired result with below query but I feel it bit messy. Can someone help me in simplifying the same?
=IF("(" & LOOKUPVALUE(Table2[Code], Table2[CaseId], Table1[CaseId],
Table2[Primary], "Y", Table2[Status], "Active", Table2[SeqNo],
MINX(FILTER(Table2, ( Table2[Primary] = "Y" && Table2[Status] = "Active" &&
Table2[CaseId] = Table1[CaseId])), Table2[SeqNo])) & ") " &
LOOKUPVALUE(Table2[CodeCareNo], Table2[CaseId], Table1[CaseId],
Table2[Primary], "Y", Table2[Status], "Active", Table2[SeqNo],
MINX(FILTER(Table2, ( Table2[Primary] = "Y" && Table2[Status] = "Active" &&
Table2[CaseId] = Table1[CaseId])), Table2[SeqNo])) = "() ", BLANK(), "(" &
LOOKUPVALUE(Table2[Code], Table2[CaseId], Table1[CaseId], Table2[Primary],
"Y", Table2[Status], "Active", Table2[SeqNo], MINX(FILTER(Table2, (
Table2[Primary] = "Y" && Table2[Status] = "Active" && Table2[CaseId] =
Table1[CaseId])), Table2[SeqNo])) & ") " & LOOKUPVALUE(Table2[CodeCareNo],
Table2[CaseId], Table1[CaseId], Table2[Primary], "Y", Table2[Status],
"Active", Table2[SeqNo], MINX(FILTER(Table2, ( Table2[Primary] = "Y" &&
Table2[Status] = "Active" && Table2[CaseId] = Table1[CaseId])),
Table2[SeqNo])) )
That's quite the mess. Try this formulation:
CodeCase =
VAR SeqNo = CALCULATE(MIN(Table2[SeqNo]), Table2[Primary] = "Y", Table2[Status] = "Active")
VAR Code = LOOKUPVALUE(Table2[Code], Table2[SeqNo], SeqNo)
VAR CodeCareNo = LOOKUPVALUE(Table2[CodeCareNo], Table2[SeqNo], SeqNo)
RETURN IF(ISBLANK(Code) || ISBLANK(CodeCareNo), BLANK(), "(" & Code & ") " & CodeCareNo)
(I like to use variables for computational efficiency and readability.)

Is it possible to sort a groups of lines in vim?

As far as I know vim's :sort method will sort each line. I have some code that is in groups of 3 lines. How can I sort this? Please ignore the shitty code, it's a legacy app :'(
I would like to sort by the case 'AF' line but ignore (group) the country and break line
case 'AF':
country = 'Afghanistan';
break;,
case 'AL':
country = 'Albania';
break;,
case 'DZ':
country = 'Algeria';
break;,
case 'AS':
country = 'American Samoa';
break;,
case 'AD':
country = 'Andorra';
break;,
case 'AO':
country = 'Angola';
break;,
case 'AI':
country = 'Anguilla';
break;,
case 'AQ':
country = 'Antarctica';
break;,
case 'AG':
country = 'Antigua And Barbuda';
break;,
case 'AR':
country = 'Argentina';
break;,
case 'AM':
country = 'Armenia';
break;,
case 'AW':
country = 'Aruba';
break;,
case 'AU':
country = 'Australia';
break;,
case 'AT':
country = 'Austria';
break;,
case 'AZ':
country = 'Azerbaijan';
break;,
case 'BS':
country = 'Bahamas';
break;,
case 'BH':
country = 'Bahrain';
break;,
case 'BD':
country = 'Bangladesh';
break;,
case 'BB':
country = 'Barbados';
break;,
case 'BY':
country = 'Belarus';
break;
A buzzword-compliant version of the solution suggested by #Halst:
mark lines
join them on a character that doesn't appear in code:
:'<,'>s/[:;]\zs\n/#/
mark lines again
sort them:
:'<,'>sort
mark lines one last time
split them on #:
:'<,'>s/#/\r/g
You'll need to fix the last term manually. No need for indent, sort, or any other external program.
You can also avoid marking lines if you move the relevant code to a scrap buffer and re-format it there.
My AdvancedSorters plugin implements the algorithm suggested by #Halst and #SatoKatsura (joining, sorting, then unjoining) as a simple custom command:
:SortRangesByHeader /^case/
The plugin implements various other sorting methods, e.g. by folds, ranges, etc.
One way to do this is to collapse the statements into one line like this:
case 'AG': country = 'Antigua And Barbuda'; break;
By doing a visual select and replacing newline+indent with space:
:'<,'>s/\n / /g
Then sorting the selection:
:'<,'>!sort
Then running the region of code through some pretty-printer, for example, GNU indent:
https://www.gnu.org/software/indent/manual/indent.html
This is one advantage of adopting machine-formatted code—you can run a messy transformation (for example a regex) and then fix it all up automatically.
I created a command for this :SortGroup
" :[range]SortGroup[!] [n|f|o|b|x] /{pattern}/
" e.g. :SortGroup /^header/
" e.g. :SortGroup n /^header/
" See :h :sort for details
This means you can do :SortGroup /case/
Implementation below:
function! s:sort_by_header(bang, pat) range
let pat = a:pat
let opts = ""
if pat =~ '^\s*[nfxbo]\s'
let opts = matchstr(pat, '^\s*\zs[nfxbo]')
let pat = matchstr(pat, '^\s*[nfxbo]\s*\zs.*')
endif
let pat = substitute(pat, '^\s*', '', '')
let pat = substitute(pat, '\s*$', '', '')
let sep = '/'
if len(pat) > 0 && pat[0] == matchstr(pat, '.$') && pat[0] =~ '\W'
let [sep, pat] = [pat[0], pat[1:-2]]
endif
if pat == ''
let pat = #/
endif
let ranges = []
execute a:firstline . ',' . a:lastline . 'g' . sep . pat . sep . 'call add(ranges, line("."))'
let converters = {
\ 'n': {s-> str2nr(matchstr(s, '-\?\d\+.*'))},
\ 'x': {s-> str2nr(matchstr(s, '-\?\%(0[xX]\)\?\x\+.*'), 16)},
\ 'o': {s-> str2nr(matchstr(s, '-\?\%(0\)\?\x\+.*'), 8)},
\ 'b': {s-> str2nr(matchstr(s, '-\?\%(0[bB]\)\?\x\+.*'), 2)},
\ 'f': {s-> str2float(matchstr(s, '-\?\d\+.*'))},
\ }
let arr = []
for i in range(len(ranges))
let end = max([get(ranges, i+1, a:lastline+1) - 1, ranges[i]])
let line = getline(ranges[i])
let d = {}
let d.key = call(get(converters, opts, {s->s}), [strpart(line, match(line, pat))])
let d.group = getline(ranges[i], end)
call add(arr, d)
endfor
call sort(arr, {a,b -> a.key == b.key ? 0 : (a.key < b.key ? -1 : 1)})
if a:bang
call reverse(arr)
endif
let lines = []
call map(arr, 'extend(lines, v:val.group)')
let start = max([a:firstline, get(ranges, 0, 0)])
call setline(start, lines)
call setpos("'[", start)
call setpos("']", start+len(lines)-1)
endfunction
command! -range=% -bang -nargs=+ SortGroup <line1>,<line2>call <SID>sort_by_header(<bang>0, <q-args>)
Note: this requires Vim 8+ due to use of lambdas

Resources