Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Join us at FabCon Atlanta from March 16 - 20, 2026, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM. Register now.

Reply
CSpina
Helper III
Helper III

How to create a customer active table using log

Hi, people.

 

I have a table with customer logs, where ID Cliente = "Customer ID", Start = "Ínicio" and End = "Término". 

 

CSpina_0-1629741187565.png

 

I must organize the information in periods: 

CSpina_1-1629741300136.png

 

Any clues? Thanks in advance.

 

 

1 ACCEPTED SOLUTION

Sure thing - here you are.  There is the query + a custom function.

 

Query:

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("fc5BCsAgDATAr5SchSRrUmi/Iv7/G4YqHooKe8osZEshiAglUlaGQC/RVz0SN6ppurH6sRABY1Tsq+Dn99KtO3YDhuftAJsP8ulB5Fm6d/flgNoA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Client ID" = _t, DateTime1 = _t, DateTime2 = _t]),
    ChangeTypes = Table.TransformColumnTypes(Source,{{"Client ID", Int64.Type}, {"DateTime1", type datetime}, {"DateTime2", type datetime}}),
    Unpivot = Table.UnpivotOtherColumns(ChangeTypes, {"Client ID"}, "Attribute", "Value"),
    Sort = Table.Sort(Unpivot,{{"Client ID", Order.Ascending}, {"Value", Order.Ascending}}),
    fnResult = fnTable_ReferenceDifferentRow(Sort, 1, {"Value"}, null, ".next", null),
    Filter = Table.SelectRows(fnResult, each ([Attribute] = "DateTime1")),
    RemoveColumn = Table.RemoveColumns(Filter,{"Attribute"})
in
    RemoveColumn

 

Custom Function: fnTable_ReferenceDifferentRow

 

let
  Source = let
    fnTable_ReferenceDifferentRow = (Table as table, optional Step as number, optional SelectedColumns, optional GroupByColumns, optional Suffix as text, optional Buffer as any) =>
    // Source: https://www.thebiccountant.com/2018/07/12/fast-and-easy-way-to-reference-previous-or-next-rows-in-power-query-or-power-bi/
    // The only mandatory parameter is your table and then it will return a table with the previous rows values of all columns. So fnTable_ReferenceDifferentRow(MyTable) will return the result from above.
    // The default-value for this parameter is set to -1 to return the previous row if you omit it. If you want the values from the next row instead, fill in 1. 2 will return the overnext and -2 the pre-previous row.
    // You probably just need one or a few columns/fields from the previous row: In the 3rd parameter you can enter a list of column names to be returned:   fnTable_ReferenceDifferentRow(MyTable, null, {"Value"})
    // Quite often the previous values shall only be returned within a group of rows. (That’s when you use [MyColumn] = EARLIER([MyColumn]) in DAX). You can enter a list of group-columns in the 4th parameter of this function: fnTable_ReferenceDifferentRow(MyTable, null, null, {"Product"})
    // By default, the suffix “.Prev” will be added to the new column names. Use the 5th parameter to change if needed. In this example, I reference the row below using “1” for the 2nd parameter: fnTable_ReferenceDifferentRow(MyTable, 1, null, null, "Next")
    // If performance is still too bad, you can try to buffer the input table. Any value in the 6th parameter will do that for you (although I haven’t seen a performance improvement for my test cases).
    let
    // Steps to prepare the (optional) parameters for the nested function "fnFetchNextRow"
    Source = if Buffer = null then Table else Table.Buffer(Table),
    Step0 = if Step = null then -1 else Step,
    Step_ = if Step = null then 1 else Number.Abs(Step),
    Suffix = if Suffix = null then ".Prev" else Suffix,
    GroupByColumns = if GroupByColumns = null then null else GroupByColumns,
    ShiftFunction = if Step0 < 0 then Table.RemoveLastN else Table.RemoveFirstN,
    ColNames = List.Buffer(Table.ColumnNames(Source)),
    NewColNames = if SelectedColumns = null then ColNames else SelectedColumns,
    CountNewCols = List.Count(NewColNames),
    // Core function that retrieves values from previous or next rows (depending on sign of parameter "Step")
    fnFetchNextRow = (Table_ as table, optional Step as number, optional SelectedColumns, optional Suffix as text, optional Buffer as any) =>
    let
    MergeTable = if SelectedColumns = null then Table_ else Table.SelectColumns(Table_, SelectedColumns),
    Shift = if Step0 > 0 then ShiftFunction(MergeTable, Step_) & #table(NewColNames, List.Repeat({List.Repeat({null}, CountNewCols)}, Step_))
    else #table(NewColNames, List.Repeat({List.Repeat({null}, CountNewCols)}, Step_)) & ShiftFunction(MergeTable, Step_),
    Reassemble = Table.ToColumns(Table_) & Table.ToColumns(Shift),
    Custom1 = Table.FromColumns( Reassemble,  Table.ColumnNames(Source) & List.Transform(NewColNames, each _&Suffix ) )
in
    Custom1,
    // optional grouping on certain columns
    #"Grouped Rows" = Table.Group(Source, GroupByColumns, {{"All", each _}}, GroupKind.Local),
    #"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", each fnFetchNextRow([All], Step0, SelectedColumns, Suffix, Buffer)),
    #"Removed Columns" = Table.Combine(Table.RemoveColumns(#"Added Custom", GroupByColumns & {"All"})[Custom]),
    // case no grouping
    NoGroup = fnFetchNextRow(Source, Step0, SelectedColumns, Suffix, Buffer),
    // select case grouping
    Result = if GroupByColumns = null then NoGroup else #"Removed Columns"
in
    Result,
    documentation = [
    Documentation.Name =  " Table.ReferenceDifferentRow ",
    Documentation.Description = " Adds columns to a <code>Table</code> with values from previous or next rows (according to the <code>Step</code>-index in the 2nd parameter) ",
    Documentation.LongDescription = " Adds columns to a <code>Table</code> with values from previous or next rows (according to the <code>Step</code>-index in the 2nd parameter) ",
    Documentation.Category = " Table ",
    Documentation.Source = "  ",
    Documentation.Version = " 1.0 ",
    Documentation.Author = " Imke Feldmann (www.TheBIccountant.com ) ",
    Documentation.Examples = {[Description =  "  ",
    Code = " fnTable_ReferenceDifferentRow( #table( {""Product"", ""Value""}, List.Zip( { {""A"" ,""A"" ,""B"" ,""B"" ,""B""}, {""1"" ,""2"" ,""3"" ,""4"" ,""5""} } ) ) ) ",
    Result = " #table( {""Product"", ""Value"", ""Product.Prev"", ""Value.Prev""}, List.Zip( { {""A"" ,""A"" ,""B"" ,""B"" ,""B""}, {""1"" ,""2"" ,""3"" ,""4"" ,""5""}, {null ,""A"" ,""A"" ,""B"" ,""B""}, {null ,""1"" ,""2"" ,""3"" ,""4""} } ) ) "]}]
in
    Value.ReplaceType(fnTable_ReferenceDifferentRow, Value.ReplaceMetadata(Value.Type(fnTable_ReferenceDifferentRow), documentation))
in
  Source

 

Result:

jennratten_0-1629767179066.png

 

 

View solution in original post

6 REPLIES 6
Syndicate_Admin
Administrator
Administrator

Hello - here you are.

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("fc5BCsAgDATAr5SchSRrUmi/Iv7/G4YqHooKe8osZEshiAglUlaGQC/RVz0SN6ppurH6sRABY1Tsq+Dn99KtO3YDhuftAJsP8ulB5Fm6d/flgNoA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Client ID" = _t, DateTime1 = _t, DateTime2 = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Client ID", Int64.Type}, {"DateTime1", type datetime}, {"DateTime2", type datetime}}),
    #"Grouped Rows" = Table.Group(#"Changed Type", {"Client ID"}, {{"MaxStart", each List.Max([DateTime1]), type nullable datetime}, {"MaxEnd", each List.Max([DateTime2]), type nullable datetime}})
in
    #"Grouped Rows"

 

Start:

 

jennratten_0-1629743916988.png

 

 

End

 

jennratten_1-1629743933646.png

 

jennratten
Super User
Super User

Hello - here you are.

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("fc5BCsAgDATAr5SchSRrUmi/Iv7/G4YqHooKe8osZEshiAglUlaGQC/RVz0SN6ppurH6sRABY1Tsq+Dn99KtO3YDhuftAJsP8ulB5Fm6d/flgNoA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Client ID" = _t, DateTime1 = _t, DateTime2 = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Client ID", Int64.Type}, {"DateTime1", type datetime}, {"DateTime2", type datetime}}),
    #"Grouped Rows" = Table.Group(#"Changed Type", {"Client ID"}, {{"MaxStart", each List.Max([DateTime1]), type nullable datetime}, {"MaxEnd", each List.Max([DateTime2]), type nullable datetime}})
in
    #"Grouped Rows"

 

Start:

 

jennratten_0-1629743916988.png

 

 

End

 

jennratten_1-1629743933646.png

 

Jenn, thanks.

 

Thanks, but I need the 2 results for customer 4000 as it has been active in 2 different periods. In my example 2851.

Sure thing - here you are.  There is the query + a custom function.

 

Query:

 

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("fc5BCsAgDATAr5SchSRrUmi/Iv7/G4YqHooKe8osZEshiAglUlaGQC/RVz0SN6ppurH6sRABY1Tsq+Dn99KtO3YDhuftAJsP8ulB5Fm6d/flgNoA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Client ID" = _t, DateTime1 = _t, DateTime2 = _t]),
    ChangeTypes = Table.TransformColumnTypes(Source,{{"Client ID", Int64.Type}, {"DateTime1", type datetime}, {"DateTime2", type datetime}}),
    Unpivot = Table.UnpivotOtherColumns(ChangeTypes, {"Client ID"}, "Attribute", "Value"),
    Sort = Table.Sort(Unpivot,{{"Client ID", Order.Ascending}, {"Value", Order.Ascending}}),
    fnResult = fnTable_ReferenceDifferentRow(Sort, 1, {"Value"}, null, ".next", null),
    Filter = Table.SelectRows(fnResult, each ([Attribute] = "DateTime1")),
    RemoveColumn = Table.RemoveColumns(Filter,{"Attribute"})
in
    RemoveColumn

 

Custom Function: fnTable_ReferenceDifferentRow

 

let
  Source = let
    fnTable_ReferenceDifferentRow = (Table as table, optional Step as number, optional SelectedColumns, optional GroupByColumns, optional Suffix as text, optional Buffer as any) =>
    // Source: https://www.thebiccountant.com/2018/07/12/fast-and-easy-way-to-reference-previous-or-next-rows-in-power-query-or-power-bi/
    // The only mandatory parameter is your table and then it will return a table with the previous rows values of all columns. So fnTable_ReferenceDifferentRow(MyTable) will return the result from above.
    // The default-value for this parameter is set to -1 to return the previous row if you omit it. If you want the values from the next row instead, fill in 1. 2 will return the overnext and -2 the pre-previous row.
    // You probably just need one or a few columns/fields from the previous row: In the 3rd parameter you can enter a list of column names to be returned:   fnTable_ReferenceDifferentRow(MyTable, null, {"Value"})
    // Quite often the previous values shall only be returned within a group of rows. (That’s when you use [MyColumn] = EARLIER([MyColumn]) in DAX). You can enter a list of group-columns in the 4th parameter of this function: fnTable_ReferenceDifferentRow(MyTable, null, null, {"Product"})
    // By default, the suffix “.Prev” will be added to the new column names. Use the 5th parameter to change if needed. In this example, I reference the row below using “1” for the 2nd parameter: fnTable_ReferenceDifferentRow(MyTable, 1, null, null, "Next")
    // If performance is still too bad, you can try to buffer the input table. Any value in the 6th parameter will do that for you (although I haven’t seen a performance improvement for my test cases).
    let
    // Steps to prepare the (optional) parameters for the nested function "fnFetchNextRow"
    Source = if Buffer = null then Table else Table.Buffer(Table),
    Step0 = if Step = null then -1 else Step,
    Step_ = if Step = null then 1 else Number.Abs(Step),
    Suffix = if Suffix = null then ".Prev" else Suffix,
    GroupByColumns = if GroupByColumns = null then null else GroupByColumns,
    ShiftFunction = if Step0 < 0 then Table.RemoveLastN else Table.RemoveFirstN,
    ColNames = List.Buffer(Table.ColumnNames(Source)),
    NewColNames = if SelectedColumns = null then ColNames else SelectedColumns,
    CountNewCols = List.Count(NewColNames),
    // Core function that retrieves values from previous or next rows (depending on sign of parameter "Step")
    fnFetchNextRow = (Table_ as table, optional Step as number, optional SelectedColumns, optional Suffix as text, optional Buffer as any) =>
    let
    MergeTable = if SelectedColumns = null then Table_ else Table.SelectColumns(Table_, SelectedColumns),
    Shift = if Step0 > 0 then ShiftFunction(MergeTable, Step_) & #table(NewColNames, List.Repeat({List.Repeat({null}, CountNewCols)}, Step_))
    else #table(NewColNames, List.Repeat({List.Repeat({null}, CountNewCols)}, Step_)) & ShiftFunction(MergeTable, Step_),
    Reassemble = Table.ToColumns(Table_) & Table.ToColumns(Shift),
    Custom1 = Table.FromColumns( Reassemble,  Table.ColumnNames(Source) & List.Transform(NewColNames, each _&Suffix ) )
in
    Custom1,
    // optional grouping on certain columns
    #"Grouped Rows" = Table.Group(Source, GroupByColumns, {{"All", each _}}, GroupKind.Local),
    #"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", each fnFetchNextRow([All], Step0, SelectedColumns, Suffix, Buffer)),
    #"Removed Columns" = Table.Combine(Table.RemoveColumns(#"Added Custom", GroupByColumns & {"All"})[Custom]),
    // case no grouping
    NoGroup = fnFetchNextRow(Source, Step0, SelectedColumns, Suffix, Buffer),
    // select case grouping
    Result = if GroupByColumns = null then NoGroup else #"Removed Columns"
in
    Result,
    documentation = [
    Documentation.Name =  " Table.ReferenceDifferentRow ",
    Documentation.Description = " Adds columns to a <code>Table</code> with values from previous or next rows (according to the <code>Step</code>-index in the 2nd parameter) ",
    Documentation.LongDescription = " Adds columns to a <code>Table</code> with values from previous or next rows (according to the <code>Step</code>-index in the 2nd parameter) ",
    Documentation.Category = " Table ",
    Documentation.Source = "  ",
    Documentation.Version = " 1.0 ",
    Documentation.Author = " Imke Feldmann (www.TheBIccountant.com ) ",
    Documentation.Examples = {[Description =  "  ",
    Code = " fnTable_ReferenceDifferentRow( #table( {""Product"", ""Value""}, List.Zip( { {""A"" ,""A"" ,""B"" ,""B"" ,""B""}, {""1"" ,""2"" ,""3"" ,""4"" ,""5""} } ) ) ) ",
    Result = " #table( {""Product"", ""Value"", ""Product.Prev"", ""Value.Prev""}, List.Zip( { {""A"" ,""A"" ,""B"" ,""B"" ,""B""}, {""1"" ,""2"" ,""3"" ,""4"" ,""5""}, {null ,""A"" ,""A"" ,""B"" ,""B""}, {null ,""1"" ,""2"" ,""3"" ,""4""} } ) ) "]}]
in
    Value.ReplaceType(fnTable_ReferenceDifferentRow, Value.ReplaceMetadata(Value.Type(fnTable_ReferenceDifferentRow), documentation))
in
  Source

 

Result:

jennratten_0-1629767179066.png

 

 

@jennratten Thank Very Much!

You're welcome!

Helpful resources

Announcements
FabCon Global Hackathon Carousel

FabCon Global Hackathon

Join the Fabric FabCon Global Hackathon—running virtually through Nov 3. Open to all skill levels. $10,000 in prizes!

October Power BI Update Carousel

Power BI Monthly Update - October 2025

Check out the October 2025 Power BI update to learn about new features.

FabCon Atlanta 2026 carousel

FabCon Atlanta 2026

Join us at FabCon Atlanta, March 16-20, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.

Top Kudoed Authors