druhá část.
(dotaz na ladění. Funkce je v podstatě stejná.)
Možná se někomu něco může hodit.
Platí, že vše se dá dělat mnoha způsoby.
let
FileName = Excel.CurrentWorkbook(){[Name="tblDirPath"]}[Content]{0}[Directory path] & "\AUT_0032_32211010.TXT",
// Načteme po řádcich a rozdělíme na části
// Lines.FromBinary načte soubor "po řádcích" do seznamu
// Hodí se to při práci s podobnými soubory
File = Lines.FromBinary(File.Contents(FileName), QuoteStyle.None, false, 65001 ),
// Začátky a konce jednotlivých částí
// Připravíme si seznam s počátky oddílů a koncem souboru
#"Oddíl" = List.Generate(
() => [L = {"=", "Sterilisation", "Date", "End of cycle"}, // Texty, které jsou na začátku jednotlivých oddílů (parametry, data, konec souboru)
i=0, // proměnná cyklu
r = 0], // číslo řádku
each <= List.Count([L]), // podmínka pro pokračování cyklu
each [ // Funkce, která realizuje tělo cyklu
L = [L], // Zachová hledané texty
r = List.PositionOf(File, List.FindText(File,[L]{}){0}?), // Zkusí text najít.
i = + 1 // posune řídící proměnnou cyklu
],
each if [r] >= 0 then [r] else List.Count(File) // Vrácená hodnota kroku. Pokud je r < 0, vrátí poslední řádek
),
[code]
// Funkce vrátí seznam {0, 0, 3, 22, 708}
// Připravíme funkci na vybrání odpovídajících řádků oddílu
// Funkce načte jen data, bez hlavičky
DataOddilu = (o) => List.Range(File, #"Oddíl"{o}+1, #"Oddíl"{o+1} - #"Oddíl"{o} - 1),
// Načteme si sloupce ve správném pořadí
// V podstatě zbytečné
Hlavicka = Sloupce[Zkratka],
// -----------------------------------------------------------------------------------------
// první část - batch
Batch = [Batch = Text.AfterDelimiter( File{Oddíl{1}}, "_", {0, RelativePosition.FromEnd})],
// Funkce Table.FromList vyžaduje funkci, která vrátí seznam s buňkou
// each je jen jiný zápis pro deklaraci funkce (_) =>
ParametryData = Table.FromList(DataOddilu(2), each {_}, {"Param"}),
// funkce Splitter.SplitTextByWhitespace při dělení textu bere více mezer za sebou jako jednoho dělitele
#"Rozdělit parametry" = Table.SplitColumn(ParametryData, "Param", Splitter.SplitTextByWhitespace(QuoteStyle.Csv), {"Param.1", "Param", "Value"}),
#"Odebrané sloupce" = Table.RemoveColumns(#"Rozdělit parametry",{"Param.1"}),
#"Změněný typ s národním prostředím" = Table.TransformColumnTypes(#"Odebrané sloupce", {{"Value", type number}}, "en-150"),
#"Záhlaví se zvýšenou úrovní" = Table.PromoteHeaders(
Table.Transpose(#"Změněný typ s národním prostředím"), [PromoteAllScalars=true]),
// Table.SelectColumns vybere sloupce, které jsou v seznamu Hlavicka a seřadí je.
// V seznamu Hlavička jsou ale další sloupce. Paremetr MissingField.Ignore zajistí, že tyto sloupce budou ignorovné
Parametry = Table.First(Table.SelectColumns(#"Záhlaví se zvýšenou úrovní",Hlavicka, MissingField.Ignore)),
// -----------------------------------------------------------------------------------------
// třetí část
// data mají pevnou pozici
// vybereme data ze 3. oddílu, spojíme je do souvislého textu a předáme funkci Csv.Document ke zpracování.
// Předpokládám pevnou strukturu, data načtem od 2. pozice
FileData = Csv.Document(Lines.ToText(DataOddilu(3)),13,{2,12,21,26,33,39,45,51,57,62,69,75,81},ExtraValues.Ignore,65001),
// Pojmenujeme sloupce
// Výraz List.RemoveNulls(Text.Split(File{#"Oddíl"{3}}, " "))
// Text.Split(File{#"Oddíl"{3}} rozdělí řádek s hlavičkou v načtených datech v každé mezeře a vrátí seznam
// List.RemoveNulls( .. ) tento seznam vyčistí od prázdných znaků, zůstanou nám je jména sloupců
// Funkce List.Zip spojí dva (a více) seznamy do seznamu seznamů, které potřebuje funkce Table.RenameColumns
// a dostaneme
// { {"Column1", "Date"}, {"Column2", "Hour"}, ....}
#"Přejmenované sloupce" = Table.RenameColumns(FileData,
List.Zip(
{
Table.ColumnNames(FileData),
List.RemoveNulls(Text.Split(File{#"Oddíl"{3}}, " "))
})),
#"Odebrané sloupce1" = Table.RemoveColumns(#"Přejmenované sloupce",{"PT04(C)", "PT05(C)", "Operator", "PR01(kPa)", "PT01(C)", "PT03(C)", "Circulator(ON/OFF)"}),
OdebranéPrázdnéŘádky = Table.SelectRows(#"Odebrané sloupce1", each ([Date] <> "")),
// Některé příkazy mění pořadí a z dat původní pořadí nejde určit, přidáme index
#"Přidané: Index" = Table.AddIndexColumn(OdebranéPrázdnéŘádky, "Index", 0, 1, Int64.Type),
DateHour = Table.CombineColumns(#"Přidané: Index",{"Date", "Hour"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"DateHour"),
// Vyčistíme mezery
// Phase - mohou být jen na začátku a na konci.
// Ostatní čísla - jako o pár řádků výše. Rozdělíme podle mezer, odstraníme null prvky ze seznamu a výsledek zase spojíme
// do textu bez mezer
#"Vyčištěný text" = Table.TransformColumns(DateHour,{
{"Phase", Text.Trim, type text},
{"PR03(kPa)", each Text.Combine(List.RemoveNulls(Text.Split(_, " ")),"")},
{"PT02(C)", each Text.Combine(List.RemoveNulls(Text.Split(_, " ")),"")},
{"Humidity(%)", each Text.Combine(List.RemoveNulls(Text.Split(_, " ")),"")}
}),
#"Změna typu - DateHour" = Table.TransformColumnTypes(#"Vyčištěný text",{{"DateHour", type datetime}}),
Data = Table.TransformColumnTypes(#"Změna typu - DateHour", {{"Phase", type text}, {"PR03(kPa)", type number}, {"PT02(C)", type number}, {"Humidity(%)", type number}}, "en-US"),
// Dále budeme potřebovat souhrny za phase a první řádek každé phase
// Data seskupíme a vyberemo potřebná. V menu Seskupit podle do sloupce Táb zvolíme vybrat všechny řádky.
// Pak si vybereme seskupení podle přání (min, max, průměr, ...)
// Vygenerovaná funkce Table.Group seskupuje podle seznamu, kde je vždy
// jméno výsledného sloupce
// Funkce, která počítá souhrn
// Typ sloupce
// Parametrem funkce počítající souhrn je TABULKA obsahující všechny řádky příslušné skupiny.
// Pokud tedy chceme získat první řádek skupiny stačí vybrat řádek s nejmenší hodnotou indexu.
#"Seskupené řádky" = Table.Group(Data, {"Phase"}, {
{"Tab",
each Table.SelectRows(_, (r) => r[Index] = List.Min([Index])),
type table [DateHour=nullable datetime, Phase=nullable text, #"PR03(kPa)"=nullable number, #"PT02(C)"=nullable number, #"Humidity(%)"=nullable number, Index=number]
},
{"Počet řádků",
each Table.RowCount(_),
Int64.Type},
// Tady přidáme průměry, ...
{"PT02 (Max)",
each List.Max([#"PT02(C)"]),
type number},
{"PT02 (Min)",
each List.Min([#"PT02(C)"]),
type number},
{"PT02 (Prům)",
each List.Average([#"PT02(C)"]),
type number}
}),
#"Rozbalené Tab" = Table.ExpandTableColumn(#"Seskupené řádky", "Tab", {"DateHour", "PR03(kPa)", "PT02(C)", "Humidity(%)"}, {"DateHour", "PR03(kPa)", "PT02(C)", "Humidity(%)"}),
// Nalezenou tabulku vložíme do bufferu. Zrychlí to přístup k datům. Nejspíš to v tomto objemu dat není nutné.
GroupData = Table.Buffer(Table.AddKey(#"Rozbalené Tab", {"Phase"}, true)),
// Máme připravené souhrny
// teď výpočty - připravíme funkce
// Výpočty mohou být umístěny přímo do recordu.
// Jen mi to připadlo o něco přehlenější a umožňuje pojmenovat základní hodnoty a ty pak skládat do rozdílů
// Parametrem funkcí je seskupená tabulka GroupData, zase mi použití parametru místo celého jména připadlo čitelnější
//
// Funkce jednoduše vybírají hodnoty z tabulky
// Výraz _{[Phase="2.9"]}? vybere z tabulky řádek, ve kterém je ve sloupci Phase hodnota "2.9". Pokud takový neexistuje, neohlásí chybu, ale vrátí null
// Výraz _[#"PR03(kPa)"]? vybere sloupec tabulky nebo pole recordu se jménem "PR03(kPa)", Otazník zajistí v případě, že sloupec/pole neexistuje, vrácení hodnoty null
//
D1 = (_) => _{[Phase="2.9"]}?[#"PR03(kPa)"]?,
D5 = (_) => _{[Phase="3.9"]}?[#"PR03(kPa)"]?, // ? asi an
D8 = (_) => _{[Phase="4.9"]}?[#"PR03(kPa)"]?, // Co tam má být? 4.9 nebo 2.1
D11 = (_) => _{[Phase="6.9"]}?[#"PR03(kPa)"]?,
D13 = (_) => _{[Phase="8.9"]}?[#"PR03(kPa)"]?,
D3 = (_) => _{[Phase="3.9"]}?[#"Humidity(%)"]?,
D6 = (_) => _{[Phase="3.9"]}?[#"PT02(C)"]?, // Co tam má být? 2.9 nebo 3.9
D7 = (_) => _{[Phase="3.9"]}?[#"PR03(kPa)"]? - _{[Phase="2.9"]}?[#"PR03(kPa)"]?,
// nebo lze:
// D7 = (_) => D5(_) - D1(_),
D12 = (_) => _{[Phase="6.9"]}?[#"PR03(kPa)"]? - _{[Phase="4.9"]}?[#"PR03(kPa)"]?,
D2 = (_) => _{[Phase="2.9"]}?[#"DateHour"]? - _{[Phase="2.1"]}?[#"DateHour"]?,
D4 = (_) => _{[Phase="3.9"]}?[#"DateHour"]? - _{[Phase="2.9"]}?[#"DateHour"]?,
D9 = (_) => _{[Phase="4.9"]}?[#"DateHour"]? - _{[Phase="3.9"]}?[#"DateHour"]?,
D10 = (_) => _{[Phase="6.9"]}?[#"DateHour"]? - _{[Phase="4.9"]}?[#"DateHour"]?,
D14 = (_) => _{[Phase="10.9"]}?[#"Počet řádků"]?,
// přidaný vzorec pro průměr, ...
D15 = (_) => _{[Phase="6.9"]}?[#"PT02 (Prům)"]?,
D16 = (_) => _{[Phase="6.9"]}?[#"PT02 (Max)"]?,
D17 = (_) => _{[Phase="6.9"]}?[#"PT02 (Min)"]?,
// Oddělení vzorce a skládání výstupu mi připadalo přehlednější
// Je to ale věc názoru
// složíme record s vypočtenými hodnotami
CalculatedData =
[D1 = D1(GroupData)] &
[D2 = #time(0,0,0) + D2(GroupData)] & // převod na time
// (rozdíl časových údajů má typ duration.
// Proto k času 0 připočteme dobu trvání události a dostame zase time
[D3 = D3(GroupData)] &
[D4 = #time(0,0,0) + D4(GroupData)] & // převod na time
[D5 = D5(GroupData)] &
[D6 = D6(GroupData)] &
[D7 = D7(GroupData)] &
[D8 = D8(GroupData)] &
[D9 = #time(0,0,0) + D9(GroupData)] & // převod na time
[D10 = #time(0,0,0) + D10(GroupData)] & // převod na time
[D11 = D11(GroupData)] &
[D12 = D12(GroupData)] &
[D13 = D13(GroupData)] &
[D14 = D14(GroupData)]&
[D15 = D15(GroupData)]&
[D16 = D16(GroupData)]&
[D17 = D17(GroupData)]
,
// spojíme záznamy
CombineTables = Table.FromRecords({Batch & Parametry & CalculatedData}),
// Pro jistotu seřadíme sloupce a pokud by některý chyběl, tak ho doplníme (s hodnotou null)
#"Seřazení sloupců" = Table.SelectColumns(CombineTables, Hlavicka, MissingField.UseNull),
// Nastavení typu sloupců
// výraz
// Table.ToRows(Sloupce) tabulku Sloupce převede na seznam jehož prvky jsou řádky převedené na seznam
//
// Výraz
// List.Transform(Table.ToRows(Sloupce), each {""&_{0}&"", if _{2} = "text" then type text else if _{2} = "time" then type time else type number }))
// tento seznam převede na jiný seznam:
// {{"Batch", type text}, {....}}
// Typ je zadán ve 3. sloupci v tabulce Sloupce
// Výsledek transformace je použit k nastavení typu sloupců
#"Zmenený typ" = Table.TransformColumnTypes(#"Seřazení sloupců",
List.Transform(Table.ToRows(Sloupce), each {""&_{0}&"", if _{2} = "text" then type text else if _{2} = "time" then type time else type number }))
in
#"Zmenený typ"
[/code]