Saved queries via API
FA allows you to list and run saved queries via GraphQL API. Saved queries are SQL queries that you can create, browse, and interact with in the Queries view in FA Back. You can use the queries to access most of your data in the FA Platform.
FA does not have APIs for creating or modifying saved queries. To create or modify saved queries, see Queries in FA Back reference.
Access to saved queries via API
Saved query endpoints are only available to users with broad access to data in the FA Platform, meaning users who can view most data types. Users with limited visibility and extended limited visibility can’t use saved query endpoints for security reasons since saved queries also provide access to highly sensitive data.
The saved query endpoints have similar restrictions as the Queries view in FA Back, meaning users can only view queries they created, queries shared to one of their user roles, and queries shared to all users.
Query parameters
Queries in the Queries view in FA Back support user-provided parameters (see Query parameters in FA Back reference). You can also run queries via the GraphQL endpoint with the following parameters:
$P(contactId)
$P(portfolioId)
$P(portfolioIdList)
$P(securityId)
$P(startDate)
$P(endDate)
$P(customParameter)
*
*To use parameters when running a query, include a placeholder for that parameter's value in the query. Parameters without a corresponding placeholder in the query itself have no effect. To check what parameters a query supports, request the "parameters" property from SavedQueryDTO
.
Custom parameters
The standard parameters listed above are special because Queries view in FA Back provides input components that you can use to make them convenient in the UI. Still, these parameters might not be sufficient. For example, you might want to provide a list of transaction type codes or contact IDs. You also might want to use any other text or number value that corresponds to a column you want to filter by. With custom parameters, you can provide arbitrary text or number arguments to your custom query.
Custom parameters in our GraphQL endpoints work differently to those in FA Back queries. In FA Back, you can append custom parameters to queries as they are. In GraphQL endpoints, custom parameters have limitations and the values are sanitized before being used in the query.
You can provide custom parameters as numbers, text, column references, keywords, or lists of numbers or text. Text parameters are automatically passed to the query as text input. The FA system allows the following characters in custom parameters: unicode letters, numbers, dashes (—), underscores (_), at signs (@), dots (.), percentage signs (%), and spaces. Other characters are blocked.
Column references and keywords are special cases: You can append them to the query as they are, but their content is restricted. Column references must be alphanumerics surrounded by backticks (`), and we only allow the following keywords: AND, OR, IN, =, ASC, DESC. Since keywords are passed as they are, you can't use them as normal text parameters.
For an example custom parameter, see Paginated transaction list.
Advanced data retrieval with GraphQL queries
You can save SQL queries that return a list of contact, portfolio, security, transaction, or trade order IDs. When making such queries via the query endpoint, you can request the underlying objects themselves, and request any underlying properties of those objects. This allows you to make hierarchical queries that are difficult to construct otherwise.
This feature can be used by writing an SQL query that returns one or several of the following columns:
portfolioDbId
contactDbId
securityDbId
transactionDbId
tradeOrderDbId
id
*
*The id
column can be used to return any of the other object types (portfolios, contacts, securities, transactions, or trade orders). Because of this, your query doesn't need to assign an alias to the ID column. However, we generally recommend using the above aliases for the ID column.
To run a query, request the queryResult
. For queries that contain one or more of the above columns, you can further request queryResultContacts
, queryResultPortfolios
, queryResultSecurities
, queryResultTradeOrders
, or queryResultTransactions
to receive a list of contacts, portfolios, securities, trade orders, or transactions, respectively.
When you run such a query, you can request the corresponding objects from the queryResult
of your SavedQueryDTO
and fetch additional information related to them.
When to use saved queries
Saved queries can be useful considering the following:
Performance – Efficient queries can provide superior performance in comparison to non-query-based GraphQL API calls. This is especially relevant when fetching several objects at once using a hierarchical query. For more information, see Examples.
Gaps in GraphQL API coverage – Certain things can’t be accomplished using non-query-based APIs. For example, we don't currently offer paginated endpoints to list data in the FA Platform. Our data filtering endpoints also limit the searches you can make; You can't freely define what parameters should be queried with "AND" as opposed to "OR". For more information, see Examples.
Choosing between object linkages and direct SQL data fetching
In some cases, returning object IDs and constructing hierarchical queries based on them allows you to fetch data that you can’t fetch with a pure SQL query. The following are examples of such cases:
List documents. Impossible via SQL because our documents are not stored in the SQL database.
Run an Analytics+ analysis on portfolios. Requires extensive data processing which SQL isn’t suited for.
Fetch projected positions for a portfolio. Possible, but very difficult to achieve with an SQL query.
Fetching nested objects with a hierarchical GraphQL query is simpler than using pure SQL when the query involves joining multiple database tables. However, hierarchical GraphQL queries are less efficient than pure SQL queries when fetching a large number of results or making queries with a high frequency. The choice between object linkages and direct SQL fetching depends on the complexity and performance needs of your queries. For more information, see Improving performance with saved queries.
Listing and running queries
The following SQL query can be used to export information about contact representatives:
SELECT c.id AS contactDbId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId FROM pm2_contact c LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id LEFT JOIN pm2_contact rep ON reps.representatives = rep.id
Once this query is saved in the Queries view in FA Back and shared with a suitable role, you can find it using the GraphQL endpoint that lists queries:
query{ savedQueries{ id name userId groupCode parameters } }
The GraphQL query returns the list of saved SQL queries you can run along with useful information about them: which parameters the query supports, which user saved the query, and which user roles the query is shared to. Example:
{ "id": 1716983261054, "name": "representativesPerContact", "userId": "fa-user-colin", "groupCode": null, "parameters": [] }
Once you have the query ID, run it as follows:
query{ savedQuery(queryId:1716983261054){ queryResult{ queryResultJson } } }
The query returns a response as follows:
{ "data": { "savedQuery": { "queryResult": { "queryResultJson": [ { "contactDbId": 15, "representativeDbId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, { "contactDbId": 28, "representativeDbId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, (omitted rest of the results) ] } } } }
Note that the query result is returned as a JSON object which is a list of non-nested objects that each have the same set of attributes. When transferring large datasets this way, you should use short column names because they are repeated on each row of the query result.
For convenience during development, you can run the query without knowing its ID, only based on its name, as follows:
query{ savedQuery(queryName:"representativesPerContact"){ queryResult{ queryResultJson } } }
We don’t recommend doing this for production usage because the query names are not indexed or guaranteed to be unique. If someone changes the query name or saves another query with the same name, the API call will not work. Finding a query based on its name rather than its ID is also less efficient.
Examples
This section demonstrates the various features of the query endpoints in FA.
Using query parameters
Perhaps you want to only return the representative information of a specific contact or to only return information about contacts with the status “Closed”. You can adjust the parameters of the query shown in Listing and running queries to achieve this:
SELECT c.id AS contacDbtId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId
FROM pm2_contact c
LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id
LEFT JOIN pm2_contact rep ON reps.representatives = rep.id
WHERE c.id = $P(contactId)
Next, provide the ID of a specific contact for which the query should return the representatives:
query{ savedQuery(queryId:1716983261054){ queryResult(queryParameters:{contactId:1234}){ queryResultJson } } }
You could also limit the query based on status by adjusting the query as follows:
SELECT c.id AS contactDbId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId
FROM pm2_contact c
LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id
LEFT JOIN pm2_contact rep ON reps.representatives = rep.id
WHERE c.status = $P(status)
Now, run the query with a custom parameter:
query{ savedQuery(queryId:1716983261054){ queryResult(queryParameters:{customParameters:{status:"P"}}){ queryResultJson } } }
You could further filter the results for multiple statuses, for example, both passive and closed contacts. For this, you could adjust the query as follows:
SELECT c.id AS contactDbId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId
FROM pm2_contact c
LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id
LEFT JOIN pm2_contact rep ON reps.representatives = rep.id
WHERE c.status IN $P(statusList)
Again, run the query with a custom parameter:
query{ savedQuery(queryId:1716983261054){ queryResult(queryParameters:{customParameters:{statusList:["P", “C”]}}){ queryResultJson } } }
Note that you must provide every parameter in your query also when you run it via the queryResult
endpoint. FA doesn’t directly support default parameter values for saved queries.
You can also treat a query parameter as optional. For example, you can adjust the previous query to contain optional "juridical form" and status list conditions:
SELECT c.id AS contactDbId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId FROM pm2_contact c LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id LEFT JOIN pm2_contact rep ON reps.representatives = rep.id WHERE (-1 IN $P(statusList) OR c.status IN $P(statusList)) AND ($P(juridicalForm) = -1 OR c.juridical = $P(juridicalForm))
Now the user can only provide a list of statuses and a juridical form. If the user wants to filter based on status or juridical form, they can simply pass the other parameter as -1 (or [-1] for the list parameter), in which case the condition always passes.
Note that standard parameters work differently when there are empty parameter values. Missing standard parameters are read as null values, meaning an optional date range selection looks as follows:
SELECT id AS transactionDbId FROM pm2_transaction WHERE ($P(startDate) IS NULL OR $P(startDate) <= transaction_date) AND ($P(endDate) IS NULL OR $P(endDate) >= transaction_date)
In this case, startDate
and/or endDate
can be omitted entirely from the GraphQL request.
Accessing objects returned by a query
The Advanced data retrieval with GraphQL queries section describes combining SQL queries with objects in the GraphQL context. To do this, you could adjust one of the previous examples as follows:
SELECT c.id AS contactDbId, rep.id AS representativeDbId, rep.name AS representativeName, rep.contact_id AS representativeContactId FROM pm2_contact c LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id LEFT JOIN pm2_contact rep ON reps.representatives = rep.id WHERE c.id IN $P(contactDbIds)
With this adjusted query, you could fetch the representatives for a list of contactDbId
fields. You could also run nested GraphQL query operations on the contactDbIds
, for example, to fetch the total value of their current assets under FA management. This updated query is run as follows:
query{ savedQuery(queryId:1716983261054){ queryResult(queryParameters:{customParameters:{contactDbIds:[15, 28, 17]}}){ queryResultJson, queryResultContacts{ id, name, portfolioReport{ marketValue } } } } }
The response is as follows:
{ "data": { "savedQuery": { "queryResult": { "queryResultJson": [ { "contactDbId": 15, "representativeDbId": 90, "representativeName": "Aaron Advisor", "representativeContactId": "aaron" }, { "contactDbId": 28, "representativeDbId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, { "contactDbId": 17, "representativeDbId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, { "contactDbId": 15, "representativeDbId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" } ], "queryResultContacts": [ { "id": 17, "name": "Colin Customer", "portfolioReport": null }, { "id": 28, "name": "Carla Customer", "portfolioReport": { "marketValue": 20754.212 } }, { "id": 15, "name": "John Doe", "portfolioReport": { "marketValue": 2765472.97 } } ] } } } }
If you choose to fetch all the information based on the linked object, you don’t need to return queryResultJson
. In the example case above, however, including the representative information in the SQL query itself is more efficient than fetching it from the linked object (see Improving performance with saved queries).
In other cases, performance improvements are not significant, and you can build your API call solely on the objects whose IDs the SQL query returns. For this approach, you can simplify the SQL query to only return the contactDbId
column or a similar column for other data types.
Improving performance with saved queries
Some types of GraphQL queries can suffer from poor performance. In general, performance decreases when using GraphQL to list a large number of objects and drilling into those objects to load additional data located in a different database table.
You might want to use the contacts endpoint to list contacts, their names, and the names of their representatives. You can do this easily with GraphQL, but increasing the number of returned contacts makes the query increasingly inefficient. This is because N+1 separate queries are made to the database. First, a single query finds all contacts (fast). Then, the server runs an additional query for each returned contact to find their representatives (slow with a significant number of contacts). No additional queries are needed for the properties of a contact (for example, their name), but the representative’s name doesn’t belong to the contact itself. The same pattern applies to most queries that combine data from separate objects. The following is an example query that works but suffers from the N+1 problem:
query{ contacts(status:"A"){ id name contactId representatives{ id name contactId } } }
Such a query only takes about a second with a few hundred contacts, but the time increases linearly as the number of contacts grows. With a thousand contacts, such a GraphQL query takes too long for most synchronous operations.
Instead, the same information can be fetched with a direct SQL query which shortens the response time to a fraction of a second when dealing with tens of thousands of contacts. You could use an SQL query as follows:
SELECT c.id AS contactDbId, c.contact_id AS contactId, c.name AS contactName, rep.id AS representativeId, rep.name AS representativeName, rep.contact_id AS representativeContactId FROM pm2_contact c LEFT JOIN pm2_contact_representatives reps ON reps.pm2_contact = c.id LEFT JOIN pm2_contact rep ON reps.representatives = rep.id WHERE c.status = "A"
Next, run the query (assuming 12345 is the saved query ID):
query{ savedQuery(queryId:12345){ queryResult{ queryResultJson } } }
The response is as follows:
{ "data": { "savedQuery": { "queryResult": { "queryResultJson": [ { "contactDbId": 10551484, "contactId": "STEWIE19-23066", "contactName": "Stewart Downing 23066", "representativeId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, { "contactDbId": 10551483, "contactId": "STEWIE19-23065", "contactName": "Stewart Downing 23065", "representativeId": 10527108, "representativeName": "Adam Advisor", "representativeContactId": "advisor" }, … (omitted) ] } } } }
Paginated transaction list
You might want to give your users a paginated view of their transactions for increased efficiency in transaction loading, even for portfolios with a long transaction history. The GraphQL APIs in FA don’t support this, but you can achieve it with the query API. First, you could use the following query to return a list of transactions for the given portfolios:
SELECT id AS transactionDbId FROM pm2_transaction WHERE pf_id IN $P(portfolioId) ORDER BY $P(orderByParam) $P(orderByDirection) LIMIT $P(limitParam) OFFSET $P(offsetParam);
Next, run the query as follows:
query{ savedQuery(queryName:"paginatedTransactions"){ queryResult(queryParameters:{ customParameters:{ orderByParam:"`transaction_date`", orderByDirection: "DESC", limitParam:10, offsetParam:0, }, portfolioIdList:[353809, 353810], }){ queryResultTransactions{ transactionDate id, typeName, amount, securityName, tradeAmount } } } }
Note that in this example, linked transaction objects are loaded to get the query results. This is done for simplicity, but you can also include the information you want to return within the query definition itself and skip loading the linked objects.
There are some requirements for the parameters:
orderByDirection
must be provided as “ASC” or “DESC”.orderByParam
must be provided as a string surrounded by backticks (`) to reference the column to sort by. You can choose which column to sort by. To sort by multiple different columns, you need to provide multiple order-by parameters.
For safety reasons, you can’t dynamically define larger chunks of SQL via query parameters. For example, you can’t define `pf_id` DESC, `ext_id` DESC
as a single parameter. Instead, you need to change the query to something like the following:
SELECT id AS transactionDbId FROM pm2_transaction WHERE pf_id IN $P(portfolioId) ORDER BY $P(orderByParam1) $P(orderByDirection1), $P(orderByParam2) $P(orderByDirection2), $P(orderByParam3) $P(orderByDirection3) LIMIT $P(limitParam) OFFSET $P(offsetParam);
If the user doesn’t indicate three columns to sort by, you can provide a default sorting column (you can specify the same column multiple times) for the undefined parameters.
Note that to make the paginated search available to users with limited access rights, you need middleware. Users with limited visibility or extended limited visibility can’t access saved query endpoints in FA.
Troubleshooting
I’m getting the following error message: "Exception while fetching data (/savedQuery/queryResult) : Error while processing custom query parameter myParameter, not proceeding with query."
Most likely, your parameter "myParameter" (replace with the name of the custom parameter you used) contains something that isn't allowed. For example, many special characters are blocked in text parameters.
I’m getting the following error message: "Exception while fetching data (/savedQuery/queryResult) : Error while running the given saved query. This may be e.g. due to a mistake in your query or unsuitable custom query parameters. Please refer to the system logs for additional information."
Your query failed. This can be because you didn’t provide values for all parameters expected by the query, you provided unsuitable values (for example, a list of values when the query expects a single value, or vice versa), or the query itself has a syntax error. The FA Back system logs might provide additional feedback about what exactly went wrong (see Application logs view in FA Monitoring app).
My custom query parameters work in the Queries view in FA Back but not via API call, or vice versa. How can I make my custom query parameters work in both places?
The Queries view and our GraphQL query endpoint work slightly differently as far as custom parameters are concerned. The reason is that users of our GraphQL query endpoints are intentionally more restricted in what data they can access: query endpoint users can execute saved queries, whereas users of the Queries view can write entirely new queries from scratch. Queries view users can query any data in our SQL database while query endpoint users can't.
This means that in the Queries view, custom parameters are inserted directly into the query as-is, without sanitization. This is not always the case when using our query endpoint. Every query that you can run via our GraphQL endpoint you can also run in the Queries view, but not necessarily vice-versa. A particular pitfall you should avoid is with custom string parameters. Consider the following query which fetches IDs for transactions of a specific type for the current date:
SELECT id FROM pm2_transaction tr WHERE tr.type = $P(trTypeCode) AND tr.status != "ORDER" AND tr.transaction_date = CURDATE()
You can call the query like this:
query{ savedQuery(queryId:12345){ queryResult(queryParameters:{ customParameters:{ trTypeCode:"B" } }) { queryResultTransactions } } }
Running the query in FA Back also requires you to input the trTypeCode
parameter surrounded by quotation marks. Entering B into the input field without quotation marks in FA Back does not work because the value is inserted into the query as-is. You could rewrite the query like this:
WHERE tr.type = "$P(trTypeCode)"
This allows you to enter B into the input field without quotation marks, but you would not be able to call the query via the query endpoint.