© Tim Leung 2021
T. LeungBeginning Power Appshttps://doi.org/10.1007/978-1-4842-6683-0_8

8. Searching and Retrieving Data

Tim Leung1  
(1)
Reading, UK
 

The ability to retrieve and search data plays an important role in many apps. We’ve seen the basic search capabilities that the auto-generated apps offer. But what if we want to provide more sophisticated search capabilities, such as the ability to filter records by date or drop-down values? In this chapter, we’ll find out how to add these features to our apps.

Another important topic is the technical implementation of data retrieval. For performance reasons, Power Apps can limit the number of rows that it retrieves, and this can prevent us from retrieving the records that we expect. As a result of this, one of the most popular topics on Power Apps forums is delegation – the term that describes this behavior. With the help of this chapter, we’ll explore techniques to help minimize these limitations. Other key topics that we’ll cover in this chapter will include
  • How to build a typical search screen that includes the ability to enter multiple search criteria and how to initiate the search through the click of a button.

  • How to join tables of data. Taking the analogy of an order processing app with two tables, customers and orders, how could we join these tables to show the customer detail that is associated with an order? How can we show customers who have not made orders or show the distinct customers who have made orders? We’ll find out how in this chapter.

  • The search scenarios that are most problematic, including a bug with datetime searches that can catch us out. We will also cover how we can overcome some of these issues by using features outside of Power Apps, including SQL Server views and SharePoint lists.

Basic Behavior

Data row retrieval limits and the term “delegation” can be a headache for many app builders. They can prevent us from retrieving our desired data, particularly in cases where we want to sum or to count fields from a data source. However, these limits exist for good reason. They prevent us from building features or writing formulas that can disproportionately slow down our apps. But with proper understanding of how data retrieval works in Power Apps, we can overcome many of the typical challenges that app builders face, and we can end up building more performant systems. Let’s find out more.

Understanding Delegation

Query delegation is a feature that speeds up data retrieval. Not all data sources support delegation, but SharePoint, SQL Server, and Dataverse are the popular data sources that do support delegation.

What exactly does the term delegation mean? When we search data from a delegable source, the data source performs the search and returns the result. If we carry out the same operation against a non-delegable data source, the data source returns all the data, and Power Apps then carries out the search on the local device. The non-delegable search works inefficiently because Power Apps needs to download more data than is necessary. Because the network connection is often the slowest link in any computerized system, non-delegable searches perform far more slowly. Additionally, mobile devices generally contain slower hardware compared to servers, and therefore, filtering data on a mobile device will be slower. Figure 8-1 illustrates this concept in the form of a diagram.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig1_HTML.jpg
Figure 8-1

How delegation works

The two core functions that we use to search for data are the Filter and LookUp functions. Both these functions accept a data source and a formula that expresses the match condition. The operator we use in the expression determines whether the query is delegable. The common delegable operators that SharePoint, SQL Server, and Dataverse support include
  • Equals operator (=) – We can apply the equals operator against string, numeric, and Boolean data types.

  • Comparison operators (=, <>, >=, <=, >, <) – We can apply comparison operators, such as greater than or less than, against numeric fields.

  • StartsWith – We can utilize the StartsWith function to test for text fields that start with a string that we specify.

SQL Server and Dataverse provide delegation support for a wider range of operations. For example, they both support the EndsWith and the Search functions. The Search function enables us to search multiple columns for values that match an input string. With SharePoint, there is no support to carry out this type of “contains” search in a delegable way. Another benefit of SQL Server and Dataverse is that there is delegation support for aggregate functions that include Sum, Average, Min, and Max.

Because it’s preferable to write a formula that is delegable, the designer shows a warning when we write data access formulas that are non-delegable. To demonstrate the type of warning we would receive, let’s take the SQL Server table that’s shown in Figure 8-2.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig2_HTML.jpg
Figure 8-2

Table structure that we'll use in this chapter

Let’s suppose we want to show records where the country field matches the value United States or England. If we implement this search by calling the in function, the designer shows the warning in Figure 8-3.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig3_HTML.jpg
Figure 8-3

Delegation warning

Is there any way for us to avoid this problem? One way is to rewrite our formula using operators that are delegable. Here’s an example of how to express the same query with the || operator:
Filter('[dbo].[Tenant]', Country="England" || Country="United States")

With this expression, the designer will not show a warning.

Increasing the Data Row Limit

By default, the data row limit for non-delegable queries is 500 rows. We can increase this to 2,000 rows through the advanced settings of an app, as shown in Figure 8-4.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig4_HTML.jpg
Figure 8-4

Increasing the delegation limit

In cases where it isn’t possible to rewrite a query using non-delegable operators, we can retrieve more accurate results by increasing the limit to the maximum value of 2,000. Of course, this isn’t a complete fix, and the problem will still exist if our source data exceeds 2,000 rows.

It is useful to note that delegation support is a feature that Microsoft improves continually. It is likely that in the future, delegation support will extend to support a wider range of operators.

Tip

We can reduce the “Data row limit for non-delegable queries” to a low value (e.g., 1). This can help us more easily detect parts of our app that are affected by delegation limits during the development and test phases of an app.

How Do Controls Manage Large Quantities of Data?

Let’s suppose we add a gallery control to our screen and we attempt to display all records with a country that matches England or United States. In total, there are 10,000 records. This is a large quantity of data. What do we think will happen when we run our screen? Will there be a delay as Power Apps loads the data, or will this amount of data be too much for Power Apps to handle?

The actual experience feels very smooth and quick. The gallery control populates almost instantly with data. The reason for this is because Power Apps optimizes this process by retrieving only the first 100 records. If we scroll to the end of the gallery control, there will be a slight delay while Power Apps retrieves the next set of 100 records and appends those to the end of the list. This method of retrieving records in bite-size pieces provides a more responsive and fluid experience for the end user.

One benefit of using SQL Server is that we can use a free tool called SQL Server Profiler to monitor how Power Apps is accessing the database. Figure 8-5 shows the output from a SQL Server trace.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig5_HTML.jpg
Figure 8-5

The output from a SQL Server Profiler trace

Although the content of this trace may look obscure, the important point is that it verifies that Power Apps retrieves data in 100 record chunks, and it also demonstrates how we can use this tool to diagnose and optimize data retrieval.

As an example, let’s suppose that we attempt to collect all 10,000 records into a collection with the following function:
ClearCollect(colTenants,
              Filter('[dbo].[Tenant]',
                     Country="England" || Country="United States"
              )
)

Will this function succeed in retrieving all 10,000 records? Unfortunately, the answer is no. The ClearCollect function will collect only up to the maximum number of records that is specified in the “Data row limit for non-delegable queries” setting, even if we specify a data source and query condition that supports delegation.

Searching Data

An important requirement in many apps is the ability for users to search for data. In this section, we’ll look at how to build this functionality. To illustrate a typical scenario, we’ll develop a custom search screen that enables users to filter data more precisely by search criteria. Figure 8-6 shows the screen that we’ll build in this section. This screen enables users to search issue records by a drop-down list of tenants. The user can also filter the records by date.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig6_HTML.jpg
Figure 8-6

The example screen that we'll build

Developing these types of screen isn’t as easy as it first appears. There are several complexities that we need to cover. The first is to configure the search feature to not filter by date if the user leaves the date field blank. Additionally, there is also a difficulty that can arise when we attempt to apply date filters. If the underlying data field contains both date and time components and we want to filter just by the date component, we need to customize our search formula to take this into account.

The user can also choose not to filter by tenants by selecting the “Show all tenants” option from the drop-down; therefore, we need to build the formula that applies a wildcard search if the user chooses this option. The final challenge is to initiate the search operation from a search button.

To demonstrate these topics, this section uses a SQL Server table called '[dbo].[Issue]', shown in Figure 8-7.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig7_HTML.jpg
Figure 8-7

Issue table ([dbo].[Issue])

If you don’t have access to SQL Server, you can easily substitute this with any other data source of your choice. The benefit of using SQL Server in this chapter is that it enables us to explore some of the performance optimization techniques that are available with SQL Server.

Basic Search Functions

Let’s review some of the search functions that enable us to build a search screen. The key functions that we can call include Search, Filter, and LookUp. These functions all sound very similar, so what are the differences? Here’s a brief overview:
  • Search  – This function matches input text against the data in one or more columns.

  • Filter  – This function matches records based on a formula that we provide.

  • LookUp  – This function matches records based on a formula that we provide and returns the first record only.

In the sections that follow, we’ll examine how to use these functions in greater detail.

Filtering by Drop-Down Values

The core parts that make up our search screen are a gallery control and a combo box. Figure 8-8 shows how our screen is set up.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig8_HTML.jpg
Figure 8-8

The layout of our search screen

There are two steps required to build our basic search screen. The first step is to add a combo box and to configure the data source for this control. The second step is to configure a gallery control so that it filters by the selected item in the combo box.

As Figure 8-8 shows, we add a combo box called cmbTenant, and we set the Items property to the following formula:
AddColumns('[dbo].[Tenant]', "FullName", Firstname & " " & Surname)
This technique sets up the combo box to show friendlier item values by combining the Firstname and Surname values. To set up our gallery control to display the filtered results, we set the Items property to the following formula:
Filter('[dbo].[Issue]', TenantID=cmbTenant.Selected.TenantID)

This formula shows how the Selected property of the combo box enables us to retrieve additional fields, in this example, the TenantID field . This example demonstrates the common practice of showing a friendly description in a combo box and the formula that we would use to retrieve the numeric key value that is associated with the selected item.

Providing a “Show All”/Wildcard Option
A typical way that a search screen behaves is that if a user chooses not to enter any search criteria, the search screen will return all results. To adapt our search screen to behave in this fashion, we would change the Items property of our gallery control to the following:
Filter('[dbo].[Issue]',
       CountRows(cmbTenant.SelectedItems)=0 ||
       TenantID=cmbTenant.Selected.TenantID
)

To explain this formula in more detail, the Filter function takes two arguments. The first argument defines the data source, and the second argument defines the expression for inclusion into the result. The Filter function evaluates this expression for each record in the data source and includes the record if the expression resolves to true.

The expression in this example evaluates the number of selected items in the combo box. If no items are selected, the expression resolves to true, and the item will appear in the result.

If the combo box contains a selected item, the second part of the “or” operator will be evaluated (the || keyword denotes the logical “or” operator). In this case, the record will be included if the tenant ID value matches the selected tenant ID in the combo box.

For this example to work correctly, we need to configure the combo box to allow only one selection by disabling the “Allow multiple selections” property. Another feature of the combo box control is that we can set the placeholder text that appears when the combo box is empty. We can use this to indicate that the search will return records for all tenants (Figure 8-9).
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig9_HTML.jpg
Figure 8-9

The InputTextPlaceholder property

Just for some background, the combo box control did not exist during the first edition of this book. In the initial release, we needed to manually append a blank row to the top of the combo box control to provide a wildcard search. The combo box control provides a great improvement because it includes a blank entry by default.

Searching by Date Criteria

Let’s now extend our search screen to enable users to additionally filter by create data. In doing so, we will also investigate a bug that often catches out app builders.

On our search screen, we’ll add a date picker control called dteSearchIssue . To default this control so that it shows a blank value, we can do so by clearing the DefaultDate property .

To highlight a slightly nontrivial example, the create date column in our issue table stores both the date and time. Therefore, to match a single input date against date and time values, we need to filter for records that fall between midnight and 23:59 of the target date. For most data sources, the formula we would add to the Items property of the gallery control is shown in the following:
Filter('[dbo].[Issue]',
       CreateDateTime >= dteSearchIssue.SelectedDate &&
       CreateDateTime < DateAdd(dteSearchIssue.SelectedDate, 1, Days)
)
However, if we were to run this formula against our SQL Server data source, we would discover that this formula returns zero records. This highlights a bug where the Power Apps connector fails to filter date and time fields properly. We can slightly improve the behavior by amending the formula like so:
Filter('[dbo].[Issue]',
       CreateDateTime >= dteSearchIssue.SelectedDate &&
       CreateDateTime < DateAdd(dteSearchIssue.SelectedDate, 1, Days &&
       Day(CreateDateTime) > 0
)

This introduces a non-delegable clause into the filter operation that forces Power Apps to carry out the query locally. Although this amendment will return some rows compared to none, the unfortunate effect is that we’ll experience the problems of a non-delegable query, which include slower performance and inaccurate results if the source data exceeds the maximum 2000-row limit.

The preferred way to fix this problem is to create a SQL Server view that casts to a DATETIMEOFFSET data type. We’ll cover this technique later in this chapter.

Caution

The date filtering bug applies to SQL Server and SharePoint data sources. You can find out more about this problem here:

https://powerusers.microsoft.com/t5/PowerApps-Forum/Filtering-on-prem-SQL-data-source-by-date/m-p/6151

Searching by Numeric Criteria

To filter a numeric column against a value that a user enters on the screen, a key step is to convert the input string to a numeric value by calling the Value function . Suppose we want to return issue records where the IssueID value matches the value that the user enters into a text input control. Here’s the formula that we would use:
Filter('[dbo].[Issue]', IssueID = Value(txtIssueID.Text))

With text input controls, a useful tip is that we can set the Format property to “Number.” This provides a simple way to validate numeric input.

Searching by Text Criteria

The Search function provides a simple way to search columns that match an input string. Here’s how to return records where characters in the description and comments fields match the value that a user enters into a textbox:
Search('[dbo].[Issue]', txtSearchDesc.Text, "Description",  "Comments")

The Search function accepts three or more arguments. The first argument defines the data source. The second argument defines the search string, and the following arguments define the target columns to search against. As shown in this example, a nice feature of this function is that it’s possible to specify multiple columns to search against.

The Search function matches records that contain the exact search phrase that we provide. That is, if the search phrase were to contain multiple words, the Search function would not return records that contain only one of the input words.

By default, the Search function carries out case-insensitive searches. With SQL Server, however, the behavior depends on the collation sequence of the database. If we search against a database with a case-sensitive collation sequence, any searches that we perform with the Search function will be case sensitive.

The main limitation of the Search function is that it works only against string column types. Also, it cannot search against SharePoint lookup fields. Although this function works fine against SQL Server data sources, a major limitation is that with SharePoint data sources, the Search function is not delegable.

Therefore, with SharePoint, the closest way that we can search text fields is to use the StartsWith operator, as shown here:
Filter(Issues, StartsWith(Title, txtSearchDesc.Text))

Adding a Search Button

The implementation of our current search screen connects the gallery control directly to the data entry controls. Because of this, the gallery control refreshes as soon as a user enters any data into any of the controls. This behavior is not ideal because the gallery control will refresh more than it needs to, particularly if the user wants to enter multiple search criteria. To address this issue, here’s how to add a search button to the screen to trigger the search operation.

To build this feature, we would amend the Items property of the gallery control so that it filters the data by variables, rather than control values. We would then set the variable values on the click of the search button. Figure 8-10 illustrates the formulas we would use.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig10_HTML.jpg
Figure 8-10

Initiating a search through the click of a button

Tip

To build a search screen where users can enter multiple pieces of search criteria, we can provide a better user experience by triggering the search from a button.

Sorting Data by Multiple Columns

To help users more easily find data, we can sort the items in the gallery and other data controls by multiple fields. The browse screen in an auto-generated app sorts the records by a single field, as shown in Figure 8-11.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig11_HTML.jpg
Figure 8-11

The default sort formula in an auto-generated app

We can sort by additional fields by providing additional sets of field names and sort sequences to the SortByColumns function. Here’s the formula to sort the User table by surname, followed by first name:
SortByColumns(Filter('[dbo].[User]',
              StartsWith(Surname, TextSearchBox1.Text)),
              "Surname", If(SortDescending1, Descending, Ascending),
              "Firstname",If(SortDescending1, Descending, Ascending)
)

In addition to the SortByColumns function , there is also a function called Sort that provides similar functionality. The difference between SortByColumns and Sort is that with Sort, we provide a formula to sort by rather than a string column name. Why is this useful? One reason is to resolve a problem that some app builders encounter when they attempt to sort by numbers that are stored in a text field.

Let’s suppose our table includes a text field and that there are two records. The first record contains the value 10, and the second record contains the value 1. When we sort by text, 10 occurs before 1, and this would result in a set of data that is not in strict numerical order. By calling the Sort function, we can convert the text values to numbers, and this will result in the expected sort sequence. Here’s an example of how we would call this function:
Sort ('[dbo].[User]',
      Value(IDField),
      Ascending
)

Joining Data

With apps that are based on a relational database structure or apps that are based on multiple SharePoint lists, a common requirement is to join and display data from multiple tables. There is no specific join command with Power Apps, so we’ll now explore some techniques that we can use to display related data.

Joining Records: Showing Related Records

One technique we can use to show related records is to apply the LookUp function . To highlight the use of this method, here’s how to set up the gallery control in our app to show a list of issue records combined with the associated tenant details. To accomplish this, we would set the Items property of the gallery control to the following formula:
AddColumns('[dbo].[Issue]',
           "TenantRecord",
           LookUp('[dbo].[Tenant]',
                  TenantID = '[dbo].[Issue]'[@TenantID]
           )
)
This formula calls the AddColumns function to append the associated tenant record to the issue data source. The interesting thing this demonstrates is how we can create a structure of nested tables. Figure 8-12 shows the output of this function.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig12_HTML.jpg
Figure 8-12

Joining records to create a nested data source

This formula also illustrates an important learning point. It demonstrates how to fully qualify field names when we nest data with functions such as AddColumns, Filter, and LookUp.

In this example, both the issue and tenant tables include a field called TenantID. In formula, how would we distinguish between these two fields? Specifically, from the inner LookUp function, we need some way to distinguish between the TenantID field in the issue table and the TenantID field in the tenant table. When we nest functions, any field name that we specify will refer to the innermost data source. In this example, therefore, when we specify TenantID from inside the LookUp function , TenantID will refer to the TenantID field in the tenant table. To refer to the TenantID field from the issue table, we need to prefix the TenantID field with the table name in the format TableName[@Fieldname]. Therefore, we would refer to the TenantID field in the issue table with the following syntax:
'[dbo].[Issue]'[@TenantID]

The name for the @ operator is the disambiguation operator. Another scenario where this operator applies is in situations where a variable, collection, or data source matches a field name. In this case, we would specify the variable, collection, or data source in the format [@ObjectName] to distinguish it from the field name.

Now that we’ve set the Items property for our gallery control, we can access all the fields in the tenant record for each issue record through the TenantRecord column, as shown in Figure 8-13.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig13_HTML.jpg
Figure 8-13

Showing related records

Aliasing Record Items

Rather than use the @ syntax, a friendlier way to resolve ambiguity is to use the As function. This function creates an alias for the current record and enables us to write a formula that is more intuitive to understand.

We can use the As function against record-scoped functions such as ForAll and Filter. We can also use it to alias expressions directly in the Items property of a gallery control.

To demonstrate, here’s how we would express our previous formula using the As function instead:
AddColumns('[dbo].[Issue]' As IssueRecord,
           "TenantRecord",
           LookUp('[dbo].[Tenant]',
                  TenantID = IssueRecord.TenantID
           )
)

Here, we call the As function to alias each record in the issue table with the name IssueRecord. From within the nested LookUp function, we can then refer to the TenantID field from the issue table with the identifier IssueRecord.TenantID .

Tip

The As function provides a great way to help resolve ambiguity in field names when we nest record-scoped functions.

Search Procedures That Can Cause Difficulties

Although Power Apps provides some powerful search capabilities, there are some areas where Power Apps doesn’t perform so well, especially in areas where there is a lack of delegable query support.

In this section, we’ll identify the areas where we need to apply caution. The purpose of this is to enable us to predetermine scenarios that can cause us difficulties at an earlier stage in the design process.

Fortunately, we can resolve many of these limitations through the use of SQL Server views. In the first release of Power Apps, there was no support for SQL Server views. We are very fortunate that Power Apps now supports views, because this enables us to query data in ways that were once impossible to accomplish in Power Apps.

Matching Against Lists of Data

The first type of query that causes difficulties is where we want to find records that match a user-supplied list. As an example, Figure 8-14 shows a screen where the user can select multiple tenants from a list box. The search feature returns all records that contain any of the selected tenants.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig14_HTML.jpg
Figure 8-14

The in function is not delegable

The function that we use here is the in function. This function can be particularly confusing because there are two versions of this function which carry out different tasks. We can call the in function to check if a string contains an input value, or we can call it to check if a table contains one or more values that we provide as an input.

For the latter use, the in function is not delegable. Therefore, this type of search will never work accurately against data sources that exceed the maximum 2000-row limit:
Filter('[dbo].[Issue]', TenantID in ListBoxTenants.SelectedItems.TenantID)

If we know that our source data will never exceed 2,000 rows, this will not be a problem. But in situations where it can, we need to find an alternate way to carry out the search.

One work-around is to loop through the items in the list box with the ForAll function, search the data source for each item row by row, and collect the results in a collection. This technique enables us to retrieve up to 2,000 issue records per tenant, rather than 2,000 records in total.

Here’s the formula that we can add to a button to populate a collection of the search results:
ForAll(RenameColumns(ListBoxTenants.SelectedItems,"TenantID","SourceTenantID"),
       Collect(SearchResults,
               Filter('[dbo].[Issue]', SourceTenantID=TenantID)
       )
)

This process requires us to nest two record-scoped functions – ForAll and Filter. To disambiguate the TenantID field from the source list box and the TenantID field from the issue table, we call the RenameColumns function to rename the TenantID column in the source list box to SourceTenantID.

The ForAll function loops over this data source, and for each selected tenant, we collect the issue records that match the tenant ID into a collection called SearchResults.

This example highlights another useful disambiguation technique that relies on renaming columns to remove any ambiguity.

At the time of writing, there is a bug that prevents the correct resolution of field values when we call the As function to alias a source and include a nested call to the Filter function to filter a SQL Server data source. Hopefully, this problem will be fixed in a future release; but until then, the rename technique provides an effective alternative.

Tip

We can rename columns to help resolve ambiguous field names when we nest record-scoped functions.

Using in to Match Substring Text

As we’ve just seen, the reason why the in operator can be confusing is because there are two usages for this function. Not only can it test that an input set of records belongs to a table or collection but it can also test for the existence of a string within another string. Figure 8-15 provides a simple way to illustrate this type of usage.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig15_HTML.jpg
Figure 8-15

Using the in function to match a substring

This example returns all records where the description field includes the characters “ing.” Although this formula works fine with SQL Server, a caveat is that with SharePoint, this formula is not delegable.

A closely related function is the exactin function . From a Power Apps perspective, the exactin function works the same way as the in function, except that the matches are case sensitive. Unfortunately, the usefulness of the exactin function is limited because it is not delegable for both SharePoint and SQL Server data sources.

When we call the in function with SQL Server to match a substring, Power Apps will delegate the query to SQL Server as we would expect.

The case sensitivity of queries with SQL Server depends on the collation sequence. If the collation sequence of the target database columns is set to a case-sensitive collation, any searches that we make with the in function will be case sensitive, as opposed to the standard behavior in Power Apps which is case insensitive.

Here’s an interesting way that we can take advantage of this behavior. Let’s take the example of an app that connects to a SQL Server instance with a case-insensitive collation sequence. If we want to carry out case-sensitive searches, we can create a SQL Server view that casts our target columns to a case-sensitive collation. Any queries that we carry out with functions such as Filter or Search will now be case sensitive. The section “SQL Server Views” later in this chapter provides the example SQL syntax that we can use.

The in and exactin functions can be difficult to comprehend, so to help summarize all the different usage types, Table 8-1 provides some example syntax.
Table 8-1

Examples of how to call the in and exactin functions

Function

Description

Filter('[dbo].[Tenant]',

            Postcode in

                 ["BA3 7KD" ," LN2 1IV"]

)

Usage type – belonging to a table (case insensitive).

This formula returns all records with a postcode that exactly matches the postcode “BA3 7KD” or “LN2 1IV”. The search is not case sensitive. Note that we cannot use this technique to find a partial match from within the Postcode column.

This query is not delegable with SQL Server and SharePoint.

Filter('[dbo].[Issue]',

            "smith" in Firstname

)

Usage type – substring test (case insensitive).

This formula returns all records that contain the string “smith” in the Firstname column.

This query is delegable with SQL Server, but not delegable with SharePoint.  With SQL Server, this search might be case sensitive, depending on the database collation sequence.

Filter('[dbo].[Tenant]',

            Postcode exactin

                ["BA3 7KD" ," LN2 1IV"]

)

Usage type – belonging to a table (case sensitive).

This formula returns all records with a postcode that exactly matches “BA3 7KD” or “LN2 1IV”.  This search is case sensitive.

This query is not delegable with SQL Server and SharePoint.

Filter('[dbo].[Issue]',

            "Smith" exactin Firstname

).

Substring test (case sensitive).

This formula returns all records that contain the string “Smith” in the Firstname column. This search is case sensitive and will not return records that contain the lowercase value “smith”.

This query is not delegable with SQL Server and SharePoint.

Checking for Nonexistence

An important task that we often need to carry out is to check for the nonexistence of a group of data within another dataset. As an example, traditional order processing systems will use this technique to find customers who have not placed any orders. To highlight this technique, here’s how to show the names of tenants that are not associated with any issue records.

To show this data, add a gallery control to a screen and set the Items property to the following formula:
Filter('[dbo].[Tenant]',
        Not(TenantID in ShowColumns('[dbo].[Issue]', "TenantID"))
)

This formula filters the tenant table to show records where a matching TenantID doesn’t exist in the issue table. Just like the previous example, the syntax we use here is not delegable; and therefore, the code here may not return all expected records.

Matching Blank Fields

Another common requirement is to search for records with null, empty, or blank fields. To demonstrate this topic, Figure 8-16 shows an excerpt from the issue table. We’ll use this to examine how to filter by empty or null values in the Description and CloseDateTime columns.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig16_HTML.jpg
Figure 8-16

Example data in the issue table

With this type of data structure, a typical requirement could be to return all records with a null CloseDateTime value . This would enable users to see all records that are in an open status.

The function that tests for null or empty values is called IsBlank. This function accepts an input value and returns true if the input is empty string or null.

To apply this function, we can set the Items property of a gallery control to the formula shown in the following:
Filter('[dbo].[Issue]', IsBlank(CloseDateTime))

This function would return all records with a null or empty date. However, a big limitation is that the IsBlank function is not delegable. If there are more than 2000 records in the issue table, the results will not include all matching records.

Let’s look at how the IsBlank function behaves against text fields. With SQL Server and other relational databases, text fields can store null (undefined) values or empty strings. The IsBlank function deals with these values slightly differently. Take the following example:
Filter('[dbo].[Issue]', IsBlank(Description))

This expression returns records with the IssueID values 1 and 2. In other words, the IsBlank function matches both null and empty string values.

Taking into consideration that the IsBlank function is not delegable, if our intention is to return only records where the issue field contains an empty string, an alternate approach is to use the formula shown in the following:
Filter('[dbo].[Issue]', Description="")

This formula would return the record for IssueID 2 only. Power Apps can delegate the operation of matching against an empty string, so we can use this method to help overcome delegation limitations.

The inverse operation is to return records that are not blank. To do this, we can use the delegable expression that’s shown in the following. This formula returns the records that match the IssueID values 3, 4, and 5:
Filter('[dbo].[Issue]', Description <> "")
Finally, the following expression returns only the records that are null, excluding records that contain an empty string. This expression will return the record for IssueID 1 only. Note that this expression is not delegable because it includes a call to the IsBlank function :
Filter('[dbo].[Issue]', IsBlank(Description) && Description <>"")

Returning Distinct Records

Sometimes, there may be the requirement to return a list of distinct records. To give an example, let’s suppose we want to return a list of distinct tenants who have raised issues.

To obtain this information, we would retrieve a list of distinct TenantID values from the issue table, and we can do this by calling the GroupBy function. The following formula returns a list of issue records grouped by TenantID:
GroupBy('[dbo].[Issue]', "TenantID", "IssueRecordsForUser")
Figure 8-17 shows how this data looks after the GroupBy operation .
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig17_HTML.jpg
Figure 8-17

Grouped data

The TenantID column shows the distinct data. Once we apply this formula to a gallery control, we can add a label and call the LookUp function to retrieve the user details that are associated with the TenantID, as shown in Figure 8-18.
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig18_HTML.jpg
Figure 8-18

Calling the LookUp function from a label

A typical business requirement is to show distinct records that have been created over a defined period. As an example, let’s suppose we want to show a list of distinct tenants who have raised issues in the current month. Theoretically, we could accomplish this by filtering our source data like so:
GroupBy(Filter('[dbo].[Issue]',
               StartDate >= Date(2017,6,1) &&
               StartDate < Date(2017,7,1)
        ),
        "TenantID",
        "IssueRecordsForUser"
)

However, with SQL Server and SharePoint data sources, the date filter bug prevents this formula from returning correct data. As we saw earlier in this chapter, filtering data sources by date will incorrectly return zero rows.

One way to overcome this problem is to add the criteria && Day(StartDate) >0 to force the filter operation to run on the local device. The caveat of this approach is that this expression is non-delegable and will not return accurate results with data sources that exceed 2000 records.

Resolving Delegation Issues

These delegation issues can be a headache because they prevent us from retrieving the accurate results that we want. What approaches can we use to work around these delegation issues?

A popular approach is to load all the contents of a data source into a local collection. We would use a delegable query to retrieve records in batches of 2000 records, and we would populate our local collection with the results.

Although this technique can help us retrieve more accurate results, it is not efficient. Where possible, it is far better to try to resolve delegation problems through the use of views in SQL Server and calculated columns in SharePoint.

SQL Server Views

With SQL Server, a great way to overcome delegation issues is to build a view. Because SQL Server is a large separate topic, we won’t cover this in too much detail. However, the important thing to take away from this section is that there is an effective technique that we can apply to overcome query delegation problems.

To provide a brief demonstration, let’s return to our earlier example. Let’s say we want to return a list of distinct tenants who have raised issues in a given month. We can use the following SQL to build a view that returns distinct issues by the tenant and the close date:
SELECT DISTINCT TenantID,
       MONTH(CloseDateTime) AS CloseDateMonth,
       YEAR(CloseDateTime) AS CloseDateYear
FROM
       dbo.Issue
Figure 8-19 shows the design of the view in Management Studio. The SQL in this view returns a numeric representation of the month and year of the CloseDateTime field by calling the SQL Month and Year functions . By converting datetime fields to integers, we can now query the data in a delegable way. From Power Apps, we can now return a distinct list of tenants who have raised issues in the current month with the following function:
Filter('[dbo].[viewDistinctTenants]',
 CloseDateMonth = Month(Now()) &&
        CloseDateYear = Year(Now())
)
../images/449557_2_En_8_Chapter/449557_2_En_8_Fig19_HTML.jpg
Figure 8-19

Building a view

This hopefully provides an overview of how we can incorporate SQL Server views into our apps. For additional reference, Listing 8-1 shows the most common snippets of SQL that we can use in views to overcome delegation issues.
-- 1 Extracting date components
SELECT CloseDateTime,
       DAY(CloseDateTime),
       MONTH(CloseDateTime),
       YEAR(CloseDateTime),
       DATEPART(HOUR, CloseDateTime),
       DATEPART(MINUTE, CloseDateTime),
       DATEPART(SECOND, CloseDateTime)
FROM dbo.Issue;
-- 2 Cast a datetime field to datetimeoffset
SELECT  CAST(CloseDateTime AS DATETIMEOFFSET)
FROM dbo.Issue
--2 Joining tables - getting tenants with issue records
SELECT dbo.Tenant.Firstname,
       dbo.Tenant.Surname
FROM dbo.Issue
    JOIN dbo.Tenant
        ON Tenant.TenantID = Issue.TenantID;
--3 Non existence - getting tenants without issue records
SELECT dbo.Tenant.Firstname,
       dbo.Tenant.Surname
FROM dbo.Tenant
WHERE TenantID NOT IN (
                          SELECT TenantID FROM dbo.Issue
                      );
--4 Null check - getting issues without close dates
SELECT *
FROM dbo.Issue
WHERE CloseDateTime IS NULL;
-- 5 Change the Description column to use a case sensitive and accent sensitive collation
SELECT
[Description] COLLATE SQL_Latin1_General_CP1_CS_AS AS [Description]
FROM dbo.Issue
Listing 8-1

Cheat sheet of SQL statements to use in views

Tip

The <, <=, >, and >= operators are not delegable against date fields in SQL Server. By using a view to convert the day, month, and year components to numbers, we can filter dates from Power Apps using these comparison operators.

SharePoint Columns

With SharePoint, the options to work around delegation issues are more limited. SharePoint calculated columns do not help because these are also not delegable.

If we need to query date columns, a good approach is to add an additional numeric column and to store copies of the dates in yyyymmdd format. We would modify any data entry forms or calls to functions such as Patch to also record a numeric version of the date. Here’s the formula that we would use to produce this numerical representation of a date:
Value(Text(dtpInputDate.SelectedDate, "yyyymmdd"))

By storing this date value in a number column, we can then filter the value using operators that include the greater than and less than operators.

If we were adding these new fields to an existing list, we could use a Power Automate flow to carry out the initial population.

Summary

This chapter focused on how to retrieve data and how to provide search capabilities for users. We covered one of the most challenging and widely discussed areas of Power Apps – delegation.

When we want to retrieve a filtered set of data, the ideal outcome is that the data source filters the records and returns the results to the app. This is the definition of a delegable query – Power Apps delegates the search query to the data source.

For more complex query conditions, Power Apps may not be able to delegate the search query to the data source. In this situation, the data source returns a maximum of 2000 records, and Power Apps executes the query on the local device. A non-delegable query misses records that fall outside this maximum 2000-record limit, and because of this, it may not return all results. This behavior presents a challenge for many app builders.

Data sources provide varying support for query delegation. Dataverse and SQL Server provide the best support, whereas SharePoint offers more limited support.

Areas where we are most likely to encounter problems with query delegation are queries where we use the IsBlank function to search for empty or null values, queries that call the in function to test that an input set of records belongs to a table or collection, and – by extension – queries that call the not and in functions to find records that do not match an input set of records. Another problem to be aware of is that there is a bug that prevents SQL Server and SharePoint data sources from filtering date columns using the greater than and less than operators.

With SQL Server as a data source, we can solve the majority of delegation problems by creating SQL Server views. The chapter provides a summary of the most common SQL statements that we can use to overcome delegation problems.

The three most common functions to query data are Search, Filter, and LookUp. The Search function matches input text against one or more text columns, the Filter function matches records based on a conditional formula, and the LookUp function returns the first record based on a conditional formula. The LookUp function is particularly useful because we use it to join or to show the details of a related record.

Another common user requirement is to return distinct records. We can return distinct records by grouping by the column where we want to show distinct values.

In the main part of the chapter, we walked through the process of building a custom search screen. This included the ability for users to filter records by a combo box control and a date picker. To build this type of search screen, we use the Filter function to filter our source data against the values that the user enters into the data entry controls. We also learned how to initiate a search from the click of a button by attaching a formula to the button to store the criteria values into variables and setting the Filter function to filter the data source by the variables.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.67.251