Forum Discussion

Jn12345's avatar
Jn12345
Brass Contributor
Sep 23, 2024

Filter Function to be optional depending on blank and non blank values

Hello,

 

I've tried a ton of different functions and it seems that I simply have to make a huge nested IF function for this but I will see if any of you smart excel folk have any answers first.

 

I have a category in my list where there are 4 columns and 4 rows (however each row is independent so its fine to consider it as one)

 

The column headers are properties of our equipment (equipment type, serial number, thickness etc.)

 

Im looking to have each cell in each column show all available option when all cells in the row are blank and for each cell that has data input into it, the filters start adding up.

 

e.g. if i add an option for equipment type then the serial numbers and thicknesses filter down to include that. and if i add a thickness then the serial numbers would include both filters.

 

I have made it work when i input data from left to right on the row but ideally i would like to make it also work if people start adding information in a random order to start filtering their options for the remaining blank cells in the row based on whichever cells have data in them.

 

Is there an easy IF OR solution for this or will i just have to make a nested if function for the entire list of options for what cells are blank or filled in?

 

Thanks for your help 

  • joelb95's avatar
    joelb95
    Brass Contributor

    Jn12345 

    Without seeing your data structure, it is hard to say, but here is a general approach that I refer to as "masking."  Rather than using filter like filter(target_array, filter_array=filter_value), you do let(filter_mask, filter_array=filter_value, filter(target_array, filter_mask).  In this way, you are giving the filter function an array of boolean values (true or false) rather than asking it to evaluate whether some comparison/operation is true or false.

    The reason this is useful is because in your let formula, you can pick and name each of your three (or four or five) filter columns, pick and name each of your filter values, and then use boolean functions to combine them.  So  (f_array_1 = "blue")*(f_array_2>5) is the same thing as saying "tell me which rows in these two arrays have "blue" in the first array and a number greater than 5 in the second.  If you want to combine the filters with "and", but you haven't yet decided what the filter value should be, you can just multiply your f_array_1 mask by TRUE to have it return true for all rows satisfying your one filter.  The formula below basically says, if there is a value in any one of these three columns in the current row, use that value for a filter mask on the specified column, and return all values in the target_array where it satisfies all of the required filters.  This means that the first, second, or third filter values can be blank or have a value and the filtered array returned will be everything (if no values supplied) all the way down to nothing (if no value meets all of the criteria you have specified).  So you can fill in the third value first, last, or second - it makes no difference.  Your filtered list will always represent the values that meet all of the specified criteria.

    If you prefer that your filter be that the return list simply meets one of the filter criteria (i.e. f1 or f2 or f3), then you can add the filter masks instead of multiplying them.  You can also do whatever other boolean algebra you want on them.

     

    You can change the names to your preference or add/remove filter columns and values.  You must, however, properly reference the cells where your filter values are, the ranges where your filter columns and target columns are, and your operators for each filter (=, <>, >, <, etc.).  The isblank can be temperamental if the cell referred to is not truly blank (has no value whatever) but is something like an empty string, i.e. =""   

     

    let(
        value_array_for_list, SERIAL_NUMBERS_REF,
        first_column_to_filter_by, PRODUCT_TYPE_COLUMN_REF,
        second_column_to_filter_by, PRODUCT_STYLE_COLUMN_REF,
        third_column_to_filter_by, PRODUCT_COLOR_COLUMN_REF,
        value_to_filter_first_column_by, [@[type]],
        value_to_filter_second_column_by, [@[sytle]],
        value_to_filter_third_column_by, [@[color]],
        first_column_mask, if(isblank(value_to_filter_first_column_by), TRUE, value_to_filter_first_column_by = first_column_to_filter_by),
        second_column_mask, if(isblank(value_to_filter_second_column_by), TRUE, value_to_filter_second_column_by = second_column_to_filter_by),
        third_column_mask, if(isblank(value_to_filter_third_column_by), TRUE, value_to_filter_third_column_by = third_column_to_filter_by),
        combined_mask, first_column_mask*second_column_mask*third_column_mask,
        filtered_serial_numbers, filter(value_array_for_list, combined_mask, "No matching serial numbers"),
        filtered_serial_numbers
    )

     

     

    • Jn12345's avatar
      Jn12345
      Brass Contributor

      joelb95 

      Hey, Thanks for the response. Basically what i have are different tables that will have various pieces of equipment in them. See attached. just for an example the headers for calibration information are "Equipment type" "Curvature" "Thickness" "Serial Number" "Material" and then i have four rows so that i can add 4 separate pieces of equipment.

      I have a picture of how i created the spill ranged in a hidden sheet where each of the values seen in the formula reference a master list of equipment with similar headings. please see attached.

      Basically i have them all blank at the start aside from the "Equipment type" column. then when they put that info in, they then have filtered options where the curvature column list is now available to select from based on equipment filtered by the equipment type, then when they put a curvature the thickness options are available based on filtering by selected Equip type, and curvature. Then the serial number based on those etc. Basically I would like the used to be able to do things out of order for flexibility. so if they type in the thickness first, all other columns will be updated to filter for only those thicknesses, then say they pick from the curvature, then they rest of the column lists will filter in the fact that they are looking for what they are supposed to be looking for based on those two filter constraints etc. Does this make sense? im probably doing a terrible job explaining something that should be pretty simple

      • joelb95's avatar
        joelb95
        Brass Contributor

        Jn12345 

         

        I spent way more time on this than I should have, but I think there isn't a great answer due to validation limitations. My final suggestion to you is in some ways a poor second to one that PeterBartholomew1 appears to have come up with involving using the cell() formula to return the address of the last active cell for purposes of setting your filters and generating your validation arrays.  What I would have done is created a helper column that requires you to check the row that you want to validate in order to update your filter arrays.

        In any event, here is a file that has some of what I tried for you.  You could, theoretically, adapt my formulas to your purpose relatively easily and expand them as necessary.

        validation_filter=lambda(
        choice, equip_val, make_val, model_val, serial_val,
        
        =LET(
            equip_col, tbl_equipment_list[Equipment Type],
            make_col, tbl_equipment_list[Make],
            model_col, tbl_equipment_list[Model],
            serial_col, tbl_equipment_list[Serial Number],
            mask_func, LAMBDA(value, column,
                IF(ISBLANK(value), EXPAND(TRUE, ROWS(column), 1, TRUE), value = column)
            ),
            equip_mask, mask_func(equip_val, equip_col),
            make_mask, mask_func(make_val, make_col),
            model_mask, mask_func(model_val, model_col),
            serial_mask, mask_func(serial_val, serial_col),
            combined_mask, equip_mask * make_mask * model_mask * serial_mask,
            no_box_checked, IFERROR(
                AND(NOT(equip_val), NOT(make_val), NOT(model_val), NOT(serial_val)),
                FALSE
            ),
            equip_choices, UNIQUE(FILTER(equip_col, combined_mask, "")),
            make_choices, UNIQUE(FILTER(make_col, combined_mask, "")),
            model_choices, UNIQUE(FILTER(model_col, combined_mask, "")),
            serial_choices, UNIQUE(FILTER(serial_col, combined_mask, "")),
            pick_choice_list, LAMBDA(choice,
                CHOOSE(choice, equip_choices, make_choices, model_choices, serial_choices)
            ),
            return_all, LAMBDA(choice, CHOOSE(choice, equip_col, make_col, model_col, serial_col)),
            result, IF(no_box_checked, return_all(choice), pick_choice_list(choice)),
            result
        ))
        
        
        valid_makes=XLOOKUP("x", tbl_results[filter_row], tbl_results[make], FALSE)
        
        valid_model=XLOOKUP("x", tbl_results[filter_row], tbl_results[model], FALSE)
        
        valid_serial_numbers=XLOOKUP("x", tbl_results[filter_row], tbl_results[serial_number], FALSE)
        
        valid_types=XLOOKUP("x", tbl_results[filter_row], tbl_results[type], FALSE)






Resources