Forum Discussion
PowerBI Model time dependant
- 1 year ago
Hi PGG79 ,
Thank you for reaching out to Microsoft Fabric Community.
Thank you lbendlin and SundarRaj for the response.
I have worked on your sample data and attached the PBIX file with the results. Please review it and let us know if it is helpful. If you have any further questions, please provide more details.
If this post helps, then please consider Accepting as solution to help the other members find it more quickly, don't forget to give a "Kudos" – I’d truly appreciate it!
Thank you!!
Your sample data is not clear to me. What would be the expected outcome based on the sample data you provided?
Hi lbendlin
When I posted the question it was with a short example, I try to explain it a bit better. I also add another table I have.
- I have Technicians (TechniciansDetails). The information of the Technicians can be updated at any time. The information is: Organization to where they belong to, the job position, the costarea to where they belong....
For each change in the information of a Technician, there is a record with "start" and "end date" in the table. The active one is the one with "end date = 31/12/999".
- The Technicians can work based on different Working Schedules (TechnicianWorkingSchedule). The working schedule of the Technicians can be updated at any time. Each time a Technician changes Working Schedule, there is a record with "start" and "end date". The active one is the one with "end date = 31/12/999".
- The Technicians record the times they spent per working day in each topic (7 hours of work, 1 hour of travel, 1 hour of lunch,...) (TechnicianHoursRecorded). For each working day the technicians record: their hours, in which project, ... For each time recorded, there is an entry in the table with the date when the entry was done.
--> It could be that for each working day, the technician have a different job position or a different calendar.
I need to have a model that helps me to report by working day, the recording hours of the technicians and more important: what working schedule they have at that day (please note that working schedule can change and have a start and end date) and what details (eg. job position) they had.
Thank you.
(I thought i could add documents but i cannot, so i put here the tables)
TechnicianDetails
| Technician | End Date | Start Date | Organization | CostArea | JobPosition | Technician name |
| Tech1 | 9999-12-31 | 2023-01-01 | Organization1 | costarea1 | JobPosition3 | Tech1 |
| Tech2 | 9999-12-31 | 2023-01-15 | Organization1 | costarea2 | JobPosition1 | Tech2 |
| Tech2 | 2023-01-14 | 2022-12-04 | Organization3 | costarea3 | JobPosition2 | Tech2 |
| Tech3 | 9999-12-31 | 2023-01-01 | Organization2 | costarea2 | JobPosition3 | Tech3 |
| Tech4 | 9999-12-31 | 2023-02-01 | Organization2 | costarea3 | JobPosition4 | Tech4 |
WorkingSchedule
| Technician | End Date | Start Date | WorkingSchedule | MoHrs | Wk.hrs. | Hrs/Day | WkDys | AnnualHrs |
| Tech1 | 9999-12-31 | 2023-02-01 | Calendar1 | 164 | 40 | 8 | 5 | 1.968,00 |
| Tech1 | 2023-01-31 | 2023-01-01 | Calendar2 | 102,5 | 25 | 5 | 5 | 1.230,00 |
| Tech2 | 9999-12-31 | 2023-01-02 | Calendar2 | 102,5 | 25 | 5 | 5 | 1.230,00 |
| Tech2 | 2023-01-01 | 2022-12-04 | Calendar3 | 98,4 | 24 | 6 | 4 | 1.180,80 |
| Tech3 | 9999-12-31 | 2023-02-15 | Calendar1 | 164 | 40 | 8 | 5 | 1.968,00 |
| Tech3 | 2023-02-14 | 2023-01-01 | Calendar3 | 98,4 | 24 | 6 | 4 | 1.180,80 |
| Tech4 | 9999-12-31 | 2023-02-01 | Calendar1 | 164 | 40 | 8 | 5 | 1.968,00 |
RecordedHours
| Technician | Recording Date | Hours | HourType | project | Registered on |
| Tech2 | 09/12/2022 | 3 | Work | project1 | 23/12/2022 |
| Tech2 | 22/12/2022 | 1 | Travel | project4 | 23/12/2022 |
| Tech1 | 09/01/2023 | 1 | Work | project3 | 20/01/2023 |
| Tech1 | 10/01/2023 | 1 | Work | project4 | 20/01/2023 |
| Tech1 | 11/01/2023 | 1 | Travel | project5 | 20/01/2023 |
| Tech1 | 03/02/2023 | 8,5 | Work | project3 | 15/02/2023 |
| Tech1 | 21/02/2023 | 3,5 | Work | project4 | 15/02/2023 |
| Tech3 | 26/01/2023 | 2 | Lunch | project2 | 22/01/2023 |
| Tech3 | 03/01/2023 | 8,5 | Work | project2 | 22/01/2023 |
| Tech3 | 04/01/2023 | 8,5 | Work | project3 | 22/01/2023 |
| Tech3 | 20/02/2023 | 8,5 | Work | project3 | 15/02/2023 |
| Tech4 | 13/02/2023 | 5 | Work | project1 | 17/02/2023 |
| Tech4 | 14/02/2023 | 5 | Work | project3 | 17/02/2023 |
- lbendlin1 year agoSuper User
Your WorkingSchedule table is inconclusive. You need to specify which days of the week , and what the business hours are. Same for the RecordedHours - needs a start and end time. Otherwise you don't know when they worked outside of the agreed days/hours.
- PGG791 year agoFrequent Visitor
Hi lbendlin
that's the way the information is coming from the source system. The assumption is that the recording is done in working days within business hours. For the report we don't care.
for me the main problem is how I can do the model to know the workingschedule valid for the day the hours were recorded. Thank you!
- lbendlin1 year agoSuper User
I cannot assist you with that in a meaningful way.I hope someone else can help you further.
- MarkLaf1 year agoSuper User
When thinking of how to model the data you've shared, I would identify your distinct dimensions, pull them out, and construct the applicable bridge tables needed to define their interrletionships. Doing so would most likely result in a constellation model (star schema with multiple facts).
Taking your original data tables, I constructed the below model (Dates is marked as a date table, all top tables are dimensions, bottom tables are facts):
Which is a compoiste of the below three star schemas:
With the above model, something like the below is relatively easy:
Where the only DAX we have to write is for the measure:
Tech Hours = CALCULATE( SUM( TechHours[Hours] ), TechOrgDates, TechCalDates )If you have any many-to many relationships (e.g., a tech could have two active calendars or be in two positions at the same time), then you should probably stick to a model like this.
If, though, for any given tech and date, you will only have one associated calendar and one associated org/position, then you can simplify the model by moving the foreign keys of your dimensions directly onto your main fact (TechHours). You can then directly relate all your dimensions to the one fact, like so:
With that set up, then you would no even need to write any DAX for the visual example I used before, the implicit sum on TechHours[Hours] works:
- MarkLaf1 year agoSuper User
Follow up, to show more details of how exactly I transformed your original tables into the model(s) I shared. I did all the transformation/construction in Power Query. It would be possible to do all this in DAX, too, but then you'll have duplicated data loaded into your model.
Here is all the M for the seven tables above.
Calendars
let Source = WorkingSchedule, SelectCols = Table.SelectColumns( Source, {"WorkingSchedule", " MoHrs", "Wk.hrs.", "Hrs/Day", "WkDys", "AnnualHrs"} ), Distinct = Table.Distinct(SelectCols, {"WorkingSchedule"}), AddId = Table.AddIndexColumn(Distinct, "ID", 1, 1, Int64.Type), MoveIdFirst = Table.ReorderColumns( AddId, {"ID", "WorkingSchedule", " MoHrs", "Wk.hrs.", "Hrs/Day", "WkDys", "AnnualHrs"} ) in MoveIdFirstDates
let Source = RecordedHours[Recording Date], MinDate = List.Min(Source), MaxDate = List.Max(Source), Dates = List.Dates( Date.StartOfYear(MinDate), Duration.Days(Date.EndOfYear(MaxDate) - Date.StartOfYear(MinDate)) + 1, #duration(1, 0, 0, 0) ), ToTable = Table.FromColumns( {Dates}, Type.AddTableKey(type table [Date = date], {"Date"}, true) ), AddYear = Table.AddColumn(ToTable, "Year", each Date.Year([Date]), Int64.Type), AddQuarter = Table.AddColumn(AddYear, "Quarter", each Date.QuarterOfYear([Date]), Int64.Type), AddMonthNo = Table.AddColumn(AddQuarter, "MonthNo", each Date.Month([Date]), Int64.Type), AddMonth = Table.AddColumn(AddMonthNo, "Month", each Date.MonthName([Date]), type text), AddYearMonthNo = Table.AddColumn( AddMonth, "YearMonthNo", each [Year] * 100 + [MonthNo], Int64.Type ), AddYearMonth = Table.AddColumn( AddYearMonthNo, "YearMonth", each Text.From([Year]) & "-" & Text.Start([Month], 3), type text ) in AddYearMonthTechnicians
let Source = TechnicianDetails, SelectTechCols = Table.SelectColumns(Source,{"Technician", "Technician name"}), DistinctTech = Table.Distinct(SelectTechCols, {"Technician"}), AddId = Table.AddIndexColumn(DistinctTech, "ID", 1, 1, Int64.Type), MoveIdFirst = Table.ReorderColumns(AddId,{"ID", "Technician", "Technician name"}) in MoveIdFirstOrgPositions
let Source = TechnicianDetails, SelectOrgCols = Table.SelectColumns(Source,{"Organization", "JobPosition", "CostArea"}), Distinct = Table.Distinct(SelectOrgCols), AddId = Table.AddIndexColumn(Distinct, "ID", 1, 1, Int64.Type), MoveIdFirst = Table.ReorderColumns(AddId,{"ID", "Organization", "JobPosition", "CostArea"}) in MoveIdFirstTechCalDates
let Source = WorkingSchedule, MergeTechs = Table.NestedJoin( Source, {"Technician"}, Technicians, {"Technician"}, "Technicians", JoinKind.Inner ), MergeCals = Table.NestedJoin( MergeTechs, {"WorkingSchedule"}, Calendars, {"WorkingSchedule"}, "Calendars", JoinKind.Inner ), MaxDate = List.Max(Dates[Date]), AddDates = Table.AddColumn( MergeCals, "Dates", each List.Dates( [Start Date], Duration.Days(List.Min({MaxDate, [End Date]}) - [Start Date]) + 1, #duration(1, 0, 0, 0) ), type {date} ), SelectCols = Table.SelectColumns(AddDates, {"Technicians", "Calendars", "Dates"}), ExpandTechID = Table.ExpandTableColumn(SelectCols, "Technicians", {"ID"}, {"TechID"}), ExpandCalID = Table.ExpandTableColumn(ExpandTechID, "Calendars", {"ID"}, {"CalID"}), ExpandDates = Table.ExpandListColumn(ExpandCalID, "Dates") in ExpandDatesTechHours
let Source = RecordedHours, MergeTechs = Table.NestedJoin( Source, {"Technician"}, Technicians, {"Technician"}, "Technicians", JoinKind.Inner ), SelectCols = Table.SelectColumns( MergeTechs, {"Technicians", "Recording Date", "Hours", "HourType", "project", "Registered on"} ), ExpandTechID = Table.ExpandTableColumn(SelectCols, "Technicians", {"ID"}, {"TechID"}) in ExpandTechIDTechOrgDates
let Source = TechnicianDetails, MergeTechs = Table.NestedJoin( Source, {"Technician"}, Technicians, {"Technician"}, "Technicians", JoinKind.Inner ), MergeOrgPos = Table.NestedJoin( MergeTechs, {"Organization", "JobPosition", "CostArea"}, OrgPositions, {"Organization", "JobPosition", "CostArea"}, "OrgPositions", JoinKind.Inner ), MaxDate = List.Max(Dates[Date]), AddDates = Table.AddColumn( MergeOrgPos, "Dates", each List.Dates( [Start Date], Duration.Days(List.Min({MaxDate, [End Date]}) - [Start Date]) + 1, #duration(1, 0, 0, 0) ), type {date} ), SelectCols = Table.SelectColumns(AddDates, {"Technicians", "OrgPositions", "Dates"}), ExpandTechID = Table.ExpandTableColumn(SelectCols, "Technicians", {"ID"}, {"TechID"}), ExpandOrgPosID = Table.ExpandTableColumn(ExpandTechID, "OrgPositions", {"ID"}, {"OrgPosID"}), ExpandDates = Table.ExpandListColumn(ExpandOrgPosID, "Dates") in ExpandDatesIf your business rules allow for the simplified model I described (no need to support M:M), then you can disable the load on the two bridge tables, TechCalDates & TechOrgDates, and use the below in TechHours to move the foreign keys over. (Note there are probably more efficient ways to get the FKs here without first building out the bridge tables):
let Source = RecordedHours, MergeTechs = Table.NestedJoin( Source, {"Technician"}, Technicians, {"Technician"}, "Technicians", JoinKind.Inner ), SelectCols = Table.SelectColumns( MergeTechs, {"Technicians", "Recording Date", "Hours", "HourType", "project", "Registered on"} ), ExpandTechID = Table.ExpandTableColumn(SelectCols, "Technicians", {"ID"}, {"TechID"}), MergeCals = Table.NestedJoin( ExpandTechID, {"TechID", "Recording Date"}, TechCalDates, {"TechID", "Dates"}, "TechCalDates", JoinKind.Inner ), ExpandCalID = Table.ExpandTableColumn(MergeCals, "TechCalDates", {"CalID"}, {"CalID"}), MergeOrgs = Table.NestedJoin( ExpandCalID, {"TechID", "Recording Date"}, TechOrgDates, {"TechID", "Dates"}, "TechOrgDates", JoinKind.Inner ), ExpandOrgID = Table.ExpandTableColumn(MergeOrgs, "TechOrgDates", {"OrgPosID"}, {"OrgPosID"}) in ExpandOrgID