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

Get inspired! Check out the entries from the Power BI DataViz World Championships preliminary rounds and give kudos to your favorites. View the vizzies.

Reply
mb769
Helper I
Helper I

Number of days without overlap: absent employees without substitute

I couldn't find an answer for this problem about overlapping date ranges.

Goal: a measure counting the number of days for which NO substitution for an absent employee has been found.

(nb: dates are in dd-MM-YYYY format)

 

I have 2 (relevant) tables :

Absence : (an employee can be absent/substituded for only a part of their job, indicated by the Job column)

Absence_IDJobEmployeeStartDateEndData
1AA.B.01-09-202430-09-2024
1BA.B.01-09-202430-09-2024
2AC.D.05-09-202406-09-2024
3AE.F.10-10-202431-10-2024
3CE.F.01-11-202431-12-2024
4CG.H.11-11-202415-11-2024

 

Substitution

Substitution_IDEmployee_IDSubstitutedAbsenceStartDateEndDate
1I.J.110-09-202430-09-2024
2K.L.101-09-202405-09-2024
3M.N.315-10-202415-10-2024
4O.P.301-11-202410-11-2024
5O.P.411-11-202415-11-2024

 

These two tables are related with a many-to-many relationship

Abscence_ID *:* SubstitudedAbsence

 

I need a measure which can determine, for each absence ID, the number of days without any subsitution.
Result table of the above sample data would look like this:

Absence_IDDays without substitution
14
22
370
40

 

I was able to calculate this by creating additional tables (a row for each date for each absence, joining with the substution table which I also split by day, and then counting rows), but this was too heavy on memory. Can anyone help me with finding a way how to calculate this?

1 ACCEPTED SOLUTION

Hi @mb769 

 

Please try to change the measure [TotalSubstitutionDays]:

 

TotalSubstitutionDays = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR SubstitutionPeriods = 
        FILTER(
            Substitution,
            Substitution[SubstitutedAbsence] = EARLIER(Absence[Absence_ID]) &&
            Substitution[StartDate] <= AbsenceEnd &&
            Substitution[EndDate] >= AbsenceStart
        )
    VAR CombinedPeriods = 
        ADDCOLUMNS(
            GENERATE(
                SubstitutionPeriods,
                VAR CurrentStart = MAX(Substitution[StartDate], AbsenceStart)
                VAR CurrentEnd = MIN(Substitution[EndDate], AbsenceEnd)
                RETURN
                SELECTCOLUMNS(
                    GENERATESERIES(CurrentStart, CurrentEnd, 1),
                    "Date", [Value]
                )
            ),
            "IsSubstituted", 1
        )
    VAR DistinctDates = 
        DISTINCT(
            SELECTCOLUMNS(
                CombinedPeriods,
                "Date", [Date]
            )
        )
    RETURN COUNTROWS(DistinctDates)
)

 

vxianjtanmsft_0-1728543621601.png

 

If only weekdays are considered, use the WEEKDAY() function in these meaures to filter out weekends.

1. Create a measure [TotalAbsenceDays(workdays)]:

 

TotalAbsenceDays(workdays) = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR AbsenceDates = 
        ADDCOLUMNS(
            GENERATESERIES(AbsenceStart, AbsenceEnd, 1),
            "IsAbsence", 1
        )
    VAR Workdays = 
        FILTER(
            AbsenceDates,
            WEEKDAY([Value], 2) < 6
        )
    RETURN COUNTROWS(Workdays)
)

 

2. Create a measure [TotalSubstitutionDays(workdays)]:

 

TotalSubstitutionDays(workdays) = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR SubstitutionPeriods = 
        FILTER(
            Substitution,
            Substitution[SubstitutedAbsence] = EARLIER(Absence[Absence_ID]) &&
            Substitution[StartDate] <= AbsenceEnd &&
            Substitution[EndDate] >= AbsenceStart
        )
    VAR CombinedPeriods = 
        ADDCOLUMNS(
            GENERATE(
                SubstitutionPeriods,
                VAR CurrentStart = MAX(Substitution[StartDate], AbsenceStart)
                VAR CurrentEnd = MIN(Substitution[EndDate], AbsenceEnd)
                RETURN
                SELECTCOLUMNS(
                    GENERATESERIES(CurrentStart, CurrentEnd, 1),
                    "Date", [Value]
                )
            ),
            "IsSubstituted", 1
        )
    VAR DistinctDates = 
        DISTINCT(
            SELECTCOLUMNS(
                CombinedPeriods,
                "Date", [Date]
            )
        )
    VAR Workdays = 
        FILTER(
            DistinctDates,
            WEEKDAY([Date], 2) < 6  
        )
    RETURN COUNTROWS(Workdays)
)

 

3. Create a summary table

 

ResultTable(workdays) = 
SUMMARIZE(
    Absence,
    Absence[Absence_ID],
    "Days without substitution", 
    [TotalAbsenceDays(workdays)] - [TotalSubstitutionDays(workdays)]
)

 

vxianjtanmsft_1-1728543990945.png

Best Regards,
Jarvis Tang
If this post helps, then please consider Accept it as the solution to help the other members find it more quickly.

View solution in original post

5 REPLIES 5
v-xianjtan-msft
Community Support
Community Support

Hi @mb769 

 

According to your sample data, should the result of “Days without substitution” with Abscence_ID = 3 be 72 instead of 70?

 

Try the following steps:

1. Create a measure to calculate the number of days absent for each Absence_ID:

TotalAbsenceDays = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    RETURN DATEDIFF(AbsenceStart, AbsenceEnd, DAY) + 1
)

2. Create a measure to calculate the number of substitution days for each Absence_ID:

TotalSubstitutionDays = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR SubstitutionPeriods = 
        FILTER(
            Substitution,
            Substitution[SubstitutedAbsence] = EARLIER(Absence[Absence_ID]) &&
            Substitution[StartDate] <= AbsenceEnd &&
            Substitution[EndDate] >= AbsenceStart
        )
    VAR SubstitutionDays = 
        SUMX(
            SubstitutionPeriods,
            DATEDIFF(
                MAX(Substitution[StartDate], AbsenceStart),
                MIN(Substitution[EndDate], AbsenceEnd),
                DAY
            ) + 1
        )
    RETURN SubstitutionDays
)

3. Create a measure to count the number of days without a substitution:

DaysWithoutSubstitution = Absence[TotalAbsenceDays] - Absence[TotalSubstitutionDays]

4. Create a summary table to show the number of days each Absence_ID had no substitutions:

ResultTable = 
SUMMARIZE(
    Absence,
    Absence[Absence_ID],
    "Days without substitution", [DaysWithoutSubstitution]
)

 

Here is the final result:

vxianjtanmsft_0-1728454852587.png

 

Best Regards,
Jarvis Tang
If this post helps, then please consider Accept it as the solution to help the other members find it more quickly.

Thank you, @v-xianjtan-msft , this works for a part.

But the difficulty (which I unfortunately didn't include in my sample data) is when 2 subsititution periods for the same absence overlap (which is possible, because it is theoretically for a different "job").

 

Let's change the substitution table to the following: (change is for Substitution_ID 2)

Substitution_IDEmployee_IDSubstitutedAbsenceStartDateEndDate
1I.J.110/09/202430/09/2024
2K.L.18/09/202415/09/2024
3M.N.315/10/202415/10/2024
4O.P.31/11/202410/11/2024
5O.P.411/11/202415/11/2024


The correct [DayswithoutSubsititution] would be 7 (1/9/2024-7/9/2024), but your measure would show less, because it counts the overlapping days (10/9/2024-14-9/2025) twice.

 

Any suggestion on how to solve that?

 

(To complicate matters even further, the follow-up question - but not urgent/necessary for the users - is: only weekdays should be counted as 'not substituted'... This is the reason why I initially created other tables in which I counted day by day.)

I'm thinking towards using the function GENERATESERIES. 

 

Hi @mb769 

 

Please try to change the measure [TotalSubstitutionDays]:

 

TotalSubstitutionDays = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR SubstitutionPeriods = 
        FILTER(
            Substitution,
            Substitution[SubstitutedAbsence] = EARLIER(Absence[Absence_ID]) &&
            Substitution[StartDate] <= AbsenceEnd &&
            Substitution[EndDate] >= AbsenceStart
        )
    VAR CombinedPeriods = 
        ADDCOLUMNS(
            GENERATE(
                SubstitutionPeriods,
                VAR CurrentStart = MAX(Substitution[StartDate], AbsenceStart)
                VAR CurrentEnd = MIN(Substitution[EndDate], AbsenceEnd)
                RETURN
                SELECTCOLUMNS(
                    GENERATESERIES(CurrentStart, CurrentEnd, 1),
                    "Date", [Value]
                )
            ),
            "IsSubstituted", 1
        )
    VAR DistinctDates = 
        DISTINCT(
            SELECTCOLUMNS(
                CombinedPeriods,
                "Date", [Date]
            )
        )
    RETURN COUNTROWS(DistinctDates)
)

 

vxianjtanmsft_0-1728543621601.png

 

If only weekdays are considered, use the WEEKDAY() function in these meaures to filter out weekends.

1. Create a measure [TotalAbsenceDays(workdays)]:

 

TotalAbsenceDays(workdays) = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR AbsenceDates = 
        ADDCOLUMNS(
            GENERATESERIES(AbsenceStart, AbsenceEnd, 1),
            "IsAbsence", 1
        )
    VAR Workdays = 
        FILTER(
            AbsenceDates,
            WEEKDAY([Value], 2) < 6
        )
    RETURN COUNTROWS(Workdays)
)

 

2. Create a measure [TotalSubstitutionDays(workdays)]:

 

TotalSubstitutionDays(workdays) = 
SUMX(
    VALUES(Absence[Absence_ID]),
    VAR AbsenceStart = MINX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[StartDate])
    VAR AbsenceEnd = MAXX(FILTER(Absence, Absence[Absence_ID] = EARLIER(Absence[Absence_ID])), Absence[EndDate])
    VAR SubstitutionPeriods = 
        FILTER(
            Substitution,
            Substitution[SubstitutedAbsence] = EARLIER(Absence[Absence_ID]) &&
            Substitution[StartDate] <= AbsenceEnd &&
            Substitution[EndDate] >= AbsenceStart
        )
    VAR CombinedPeriods = 
        ADDCOLUMNS(
            GENERATE(
                SubstitutionPeriods,
                VAR CurrentStart = MAX(Substitution[StartDate], AbsenceStart)
                VAR CurrentEnd = MIN(Substitution[EndDate], AbsenceEnd)
                RETURN
                SELECTCOLUMNS(
                    GENERATESERIES(CurrentStart, CurrentEnd, 1),
                    "Date", [Value]
                )
            ),
            "IsSubstituted", 1
        )
    VAR DistinctDates = 
        DISTINCT(
            SELECTCOLUMNS(
                CombinedPeriods,
                "Date", [Date]
            )
        )
    VAR Workdays = 
        FILTER(
            DistinctDates,
            WEEKDAY([Date], 2) < 6  
        )
    RETURN COUNTROWS(Workdays)
)

 

3. Create a summary table

 

ResultTable(workdays) = 
SUMMARIZE(
    Absence,
    Absence[Absence_ID],
    "Days without substitution", 
    [TotalAbsenceDays(workdays)] - [TotalSubstitutionDays(workdays)]
)

 

vxianjtanmsft_1-1728543990945.png

Best Regards,
Jarvis Tang
If this post helps, then please consider Accept it as the solution to help the other members find it more quickly.

Perfect!

Thank you so much!

lbendlin
Super User
Super User

an employee can be absent/substituded for only a part of their job, indicated by the Job column

Yeah, no - that is too ambiguous.  You'll have to drop that requirement to make it work.

Helpful resources

Announcements
Las Vegas 2025

Join us at the Microsoft Fabric Community Conference

March 31 - April 2, 2025, in Las Vegas, Nevada. Use code FABINSIDER for a $400 discount!

FebPBI_Carousel

Power BI Monthly Update - February 2025

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

March2025 Carousel

Fabric Community Update - March 2025

Find out what's new and trending in the Fabric community.

Top Solution Authors