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!Get Fabric Certified for FREE during Fabric Data Days. Don't miss your chance! Request now
Consider the following dataset where placed time goes from 4am to 8pm and fulfillment time goes from 7am to 8pm as well.
I calculated the Running Sum, resulting in this matrix
For every timeslot in the y axis, I can only fulfill 100 orders. I want to calculate a matrix in the same format as above that tells me the remaining orders to fulfill every hour after that 100 runs out. This is the expected result for 4:00 am:
Which I calculated using the following formula:
Column = max(Table'[Running Total]-100,0)
The issue is that I want every row to pick back up with the first non-zero value from the previous row. In this case, that value would be 40. In other words, since I can only fulfill 100 orders in an hour, at 5 am I am tackling the 40 orders that I couldn't get to at 4 am + the ones that were placed at 5 am. Does anyone have any suggestions for my requierement?
Solved! Go to Solution.
I had alredy added the comments in the function
FillShift = (tbl, slot) => let
// buffer the table
buf = Table.Buffer(tbl),
// save away already processed entries
prc = Table.SelectRows(buf,each [Processed]<> null),
// items that need processing. consider only placed orders.
src=Table.SelectRows(buf,each [Processed] = null and [Placed Time] <= #time(slot,0,0)),
// defer (orders were placed later)
def = Table.SelectRows(buf,each [Processed] = null and [Placed Time] > #time(slot,0,0)),
// sort by fulfilled, then placed
srt = Table.Sort(src,{{"Fulfillment Time", Order.Ascending}, {"Placed Time", Order.Ascending}}),
// add index
idx = Table.AddIndexColumn(srt, "Index", 0, 1, Int64.Type),
// tag lines to process
rep = Table.ReplaceValue(idx,null,each if [Placed Time]<=#time(slot,0,0) and [Index]<140 then #time(slot,0,0) else null,Replacer.ReplaceValue,{"Processed"}),
// drop index
res = Table.RemoveColumns(rep,{"Index"})
in
//concatenate result with saved portions
prc & res & def
The function expects a table with the four required columns Placed Time, Fulfillment Time, Order identifier and Processed. (The order identifier is generated when exploding the number of received orders into a list)
The function also expects a time slot for which the computation needs to happen . That is the hour number.
First step is to buffer the entire table to improve query performance
Next step is to save away the portion of the orders (prc) that have already been allocated (that have a Processed value)
Then we identify all orders that can be processed - meaning the Placed Time is before or equal to the time slot
Then we save away (defer) all orders that cannot yet be processed as the Placed Time is after the time slot
Next we sort the eligible orders by fulfillment time and placed time
Then we tag the first 140 orders (or less) with the processed time stamp of the current slot
And finally we combine the three parts back together, ready for the next timeslot.
The function is then called for each available timeslot (from 4 to 19) sequentially.
When does production actually start? and once it starts, will you always handle 140 orders per hour, even orders that are due later?
Yes, once it starts we can only take care of 140 per hour. Thats why production starts that early; bulk of orders are due between 4 and 6 pm
So you could do some basic math - 140 * 14 = 1960. You can handle 1960 orders per day between 4am and 6pm. Even your full day total is far below that. The spike in due order volume between 3pm and 5pm can be absorbed in the slots before.
Yup. Just looking for an hour by hour breakdown
Production starts at 4am. You can sort of picture it as a bakery operation if that helps make sense of the lead times.
is there a penalty for completing an order early? is there a penalty for completing an order late?
No penalty for early. We shouldn't complete an order late, but thats the info the matrix will tell us; at what point does the system break
"at what point does the system break" That's simple - take the cumulative of all due orders for a given hour slot, then compare that to 140 times the number of elapsed hours.
This is good for the overall picture!
I'm looking for the hour by hour breakdown for the specifics. This doesn't tell me at what time I'll be done handling 4 pm orders, or how many production timeslots it took me to get there. That's why it matters
Understand. DAX is definitely unable to do that. Still tinkering with Power Query but so far haven't gotten the logic pudding nailed to the wall.
Appreciate this!!
I got it.
Instead of sorting by placed time first, I am sorting by fulfillment time first. Then I expand the number of orders for each slot for easier counting. After that it is a simple integer division by 140.
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("bZdLkiMhDAXv4nUvLMRPc5WOuf81ZlGIqafIFeEMTKG0oJ5/fz/9z/f7+fmsZ2jx+ftz4T6DvWGcL3zf0L7POGWq2TOOKbQ9Y29C/dAutNO+bDyj67rzPG0Jzcp0Zzv3IvTU5m/YTmlPvUN0CdvAzoLjzYwWTFUmsBF0gp3WHASPpSaQ6jEqKBUJVEUTFE1RZG8WMC8VycRU5AIbQac1O80cBLORBC6auelBVJEqWqBoQRctUeRvpoqWKmpDaLaRznWkx9IUeBtK6LUtNOsQeKrqAuPdiYs8bfC0wdOGVtrqqQs0mtlE3i6altB7Pwm950HobRah4mmrJ90BldXk5w/wFOApwFPQrRTqaQqUIxeqyZfQvEO30NQ0hOZJ0XXzqAhMCQLj/UMHacoaxVPCTTAIqqpLDWl7G7zU3wovzRuq4ME4fS3FeZC64jw2SoP2oWcw61oEN8EgaLhokWYqrVAXlabSutLizNRZxXm3N8X3ClEctI8irZG0RtIaSWsoraG0htKaSis0s5zSm0UU3zeg4lNbL0vfe1OxSmsq7Xh3kuYkzUmaozRHaY7SHKW5Siv0JjDFU126SmsF54WmIsrxdJTWSVonaZ2kdZTWUVpHaR2ldZRWAnyhN/0pxtJun23F970hOJ0dk5TijWK8DXKGQd4GOhvobKAzDPOGad5KnC90qcmS6LvS7LImuLQZpXqbpIxyvU1UNlHZRGWY7W2isonKJior+b5QCWRWEn4XerPGOdsU8m/qIxgEi7GFxhYaW2hsobGFxhYaW2isRP1CJevfW1BPJaX9m1sIBsEiDBP//7//SB1pRzqQTqRYmWFphrXdFjuYcn/CTTAIFmOBxgKNBRoLNBZoLNBYoLFAY4HGyh+Ah+anRXATDIKGi6qwSxtSR9qRDqQTKVZmWJphbS9hf/8B", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Placed Time" = _t, #"Fulfillment Time" = _t, Orders = _t]),
#"Filtered Rows" = Table.SelectRows(Source, each ([Orders] <> "0")),
#"Changed Type" = Table.TransformColumnTypes(#"Filtered Rows",{{"Placed Time", type time}, {"Fulfillment Time", type time}, {"Orders", Int64.Type}}),
#"Sorted Rows" = Table.Sort(#"Changed Type",{{"Fulfillment Time", Order.Ascending},{"Placed Time", Order.Ascending}}),
#"Added Custom" = Table.AddColumn(#"Sorted Rows", "Order Number", each {1..[Orders]}),
#"Expanded Custom" = Table.ExpandListColumn(#"Added Custom", "Order Number"),
#"Added Index" = Table.AddIndexColumn(#"Expanded Custom", "Index", 0, 1, Int64.Type),
#"Added Custom1" = Table.AddColumn(#"Added Index", "Processed", each (4 + Number.IntegerDivide([Index],140) )/24),
#"Changed Type1" = Table.TransformColumnTypes(#"Added Custom1",{{"Processed", type time}}),
#"Removed Other Columns" = Table.SelectColumns(#"Changed Type1",{"Placed Time", "Fulfillment Time", "Order Number", "Processed"})
in
#"Removed Other Columns"
If you like you can add a flag that indicates when the "actually processed" time is after the "fulfillment time" (ie when the process breaks). Your sample data can be processed without that flag being triggered.
Thank you! This is still quite not there but we're close.
I can't fulfill an order that hasn't been placed yet. I'm finishing production at 2 pm according to your graph, but about half of orders due at 7 pm haven't been placed then. This goes back to one of your questions; and i correct that it does matter when the order was placed.
@Anonymous I got it ! (again...) This time the algorithm is based on "First Come First Serve" Meaning orders are processed in the order they are received, including downtime when there are fewer orders than capacity.
I also included a "Late" flag (when the processed time is later than the expected fulfillment time ) but your sample data doesn't raise that flag.
The query is a monster, but it has to be as neither Power BI nor Power Query have a concept of "variables" (they may call it variables but all of theirs are immutable).
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("bZdLkiMhDAXv4nUvLMRPc5WOuf81ZlGIqafIFeEMTKG0oJ5/fz/9z/f7+fmsZ2jx+ftz4T6DvWGcL3zf0L7POGWq2TOOKbQ9Y29C/dAutNO+bDyj67rzPG0Jzcp0Zzv3IvTU5m/YTmlPvUN0CdvAzoLjzYwWTFUmsBF0gp3WHASPpSaQ6jEqKBUJVEUTFE1RZG8WMC8VycRU5AIbQac1O80cBLORBC6auelBVJEqWqBoQRctUeRvpoqWKmpDaLaRznWkx9IUeBtK6LUtNOsQeKrqAuPdiYs8bfC0wdOGVtrqqQs0mtlE3i6altB7Pwm950HobRah4mmrJ90BldXk5w/wFOApwFPQrRTqaQqUIxeqyZfQvEO30NQ0hOZJ0XXzqAhMCQLj/UMHacoaxVPCTTAIqqpLDWl7G7zU3wovzRuq4ME4fS3FeZC64jw2SoP2oWcw61oEN8EgaLhokWYqrVAXlabSutLizNRZxXm3N8X3ClEctI8irZG0RtIaSWsoraG0htKaSis0s5zSm0UU3zeg4lNbL0vfe1OxSmsq7Xh3kuYkzUmaozRHaY7SHKW5Siv0JjDFU126SmsF54WmIsrxdJTWSVonaZ2kdZTWUVpHaR2ldZRWAnyhN/0pxtJun23F970hOJ0dk5TijWK8DXKGQd4GOhvobKAzDPOGad5KnC90qcmS6LvS7LImuLQZpXqbpIxyvU1UNlHZRGWY7W2isonKJior+b5QCWRWEn4XerPGOdsU8m/qIxgEi7GFxhYaW2hsobGFxhYaW2isRP1CJevfW1BPJaX9m1sIBsEiDBP//7//SB1pRzqQTqRYmWFphrXdFjuYcn/CTTAIFmOBxgKNBRoLNBZoLNBYoLFAY4HGyh+Ah+anRXATDIKGi6qwSxtSR9qRDqQTKVZmWJphbS9hf/8B", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Placed Time" = _t, #"Fulfillment Time" = _t, Orders = _t]),
#"Filtered Rows" = Table.SelectRows(Source, each ([Orders] <> "0")),
#"Changed Type" = Table.TransformColumnTypes(#"Filtered Rows",{{"Placed Time", type time}, {"Fulfillment Time", type time}, {"Orders", Int64.Type}}),
#"Sorted Rows" = Table.Sort(#"Changed Type",{{"Placed Time", Order.Ascending},{"Fulfillment Time", Order.Ascending}}),
#"Added Custom" = Table.AddColumn(#"Sorted Rows", "Order Number", each {1..[Orders]}),
#"Expanded Order Number" = Table.ExpandListColumn(#"Added Custom", "Order Number"),
#"Removed Other Columns" = Table.SelectColumns(#"Expanded Order Number",{"Placed Time", "Fulfillment Time", "Order Number"}),
#"Added Index" = Table.AddIndexColumn(#"Removed Other Columns", "Index", 0, 1, Int64.Type),
#"Added Custom1" = Table.AddColumn(#"Added Index", "Processed", each null),
FillShift = (tbl, slot) => let
rep = Table.ReplaceValue(tbl,null,each if [Processed] = null and [Placed Time]<=#time(slot,0,0) and [Index]<140 then #time(slot,0,0) else null,Replacer.ReplaceValue,{"Processed"}),
NextIndex=Table.SelectRows(rep,each [Processed] = null)[Index]{0}
in
Table.ReplaceValue(rep,each [Index],each if [Processed] = null then [Index]-NextIndex else null,Replacer.ReplaceValue,{"Index"}),
Filled = List.Accumulate({4..19},#"Added Custom1",(state,current)=>FillShift(state,current)),
#"Changed Type1" = Table.TransformColumnTypes(#"Filled",{{"Processed", type time}})
in
#"Changed Type1"
@lbendlin You're awesome!!!!! I modified the Late flag logic to this.
Late = if('140 Cap'[Processed]>='140 Cap'[Fulfillment Time],1,0)
Can we modify the "first come first served" logic to a "First come first served" unless there's orders due sooner that we haven't processed yet. Meaning, at 10am, I don't want to prioritize an order due at 4pm if I have an 11 am order that we haven't processed yet.
No, that is definitely outside of what Power Query can do. As I mentioned, neither Power BI nor Power Query has any memory or concept of variables. You would need variables for that unless you come up with a more clever way of (re)sorting the incoming orders.
This is what the queue looks like if you simply switch the sort order (fulfilment time , then placed time)
How about scratching the first come first served logic and instead just prioritize orders with the smallest fulfillment time as they come in?
see response 32. If you were to do the allocations manually, how would you do them and why?
Check out the November 2025 Power BI update to learn about new features.
Advance your Data & AI career with 50 days of live learning, contests, hands-on challenges, study groups & certifications and more!