-
Notifications
You must be signed in to change notification settings - Fork 22
docs(query-builder): add Query Builder Model topic #6072
Changes from 10 commits
2a447c3
5dbfa74
1219969
fb17cdf
251ed94
1f20c4c
3184c06
2629652
b1fe366
ec6281c
26c30a6
a4dc866
3dca0b7
56742c0
feee57b
627afcc
69c28c7
e8b4d3b
904820f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,243 @@ | ||||||
| --- | ||||||
| title: Using Query Builder Model | ||||||
| _description: Angular Query Builder provides easily serializable/deserializable JSON format model, making it easily to build SQL queries. Try it Now. | ||||||
|
teodosiah marked this conversation as resolved.
Outdated
|
||||||
| _keywords: Angular Query Builder component, Angular Query Builder control, Ignite UI for Angular, UI controls, Angular widgets, web widgets, UI widgets, Angular, Native Angular Components Suite, Angular UI Components, Native Angular Components Library | ||||||
| --- | ||||||
|
|
||||||
| ## Query Builder Model | ||||||
| In order to set an expression tree to the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) you need to define [`FilteringExpressionsTree`]({environment:angularApiUrl}/classes/filteringexpressionstree.html). Each [`FilteringExpressionsTree`]({environment:angularApiUrl}/classes/filteringexpressionstree.html) should have filtering logic that represents how a data record should resolve against the tree and depending on the use case, you could pass field name, entity name and an array of return fields. If all fields in certain entity should be returned, the `returnFields` property could be set to ['*']: | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ```ts | ||||||
| const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Entity A', ['*']); | ||||||
| ``` | ||||||
| Once the root [`FilteringExpressionsTree`]({environment:angularApiUrl}/classes/filteringexpressionstree.html) is created, adding conditions, groups or subqueries, could be done by setting its `filteringOperands` property to an array of [`IFilteringExpression`]({environment:angularApiUrl}/interfaces/ifilteringexpression.html) (single expression or a group) and [`IFilteringExpressionsTree`]({environment:angularApiUrl}/interfaces/ifilteringexpressionstree.html) (subquery). | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Each [`IFilteringExpression`]({environment:angularApiUrl}/interfaces/ifilteringexpression.html) and [`IFilteringExpressionsTree`]({environment:angularApiUrl}/interfaces/ifilteringexpressionstree.html) should have `fieldName` that is the name of the column where the filtering expression is placed. If required, you could also set a `condition` of type [`IFilteringOperation`]({environment:angularApiUrl}/interfaces/ifilteringoperation.html), `conditionName`, `searchVal`, `searchTree` of type [`IExpressionTree`]({environment:angularApiUrl}/interfaces/iexpressiontree.html) and `ignoreCase` properties. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The way this is written implies that |
||||||
|
|
||||||
| - Defining a simple **expression**: | ||||||
| ```ts | ||||||
| tree.filteringOperands.push({ | ||||||
| fieldName: 'Name', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('endsWith').name, | ||||||
| searchVal: 'a' | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| - Defining a **group** of expressions: | ||||||
| ```ts | ||||||
| const group = new FilteringExpressionsTree(FilteringLogic.Or, undefined, 'Entity A', ['*']); | ||||||
| group.filteringOperands.push({ | ||||||
| fieldName: 'Name', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('endsWith').name, | ||||||
| searchVal: 'a' | ||||||
| }); | ||||||
| group.filteringOperands.push({ | ||||||
| fieldName: 'DateTime created', | ||||||
| conditionName: IgxDateFilteringOperand.instance().condition('today').name | ||||||
|
Otixa marked this conversation as resolved.
|
||||||
| }); | ||||||
| tree.filteringOperands.push(group); | ||||||
| ``` | ||||||
|
|
||||||
| - Defining a **subquery**: | ||||||
| ```ts | ||||||
| const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'Entity B', ['Number']); | ||||||
| innerTree.filteringOperands.push({ | ||||||
| fieldName: 'Number', | ||||||
| conditionName: IgxNumberFilteringOperand.instance().condition('equals').name, | ||||||
| searchVal: 123 | ||||||
| }); | ||||||
| tree.filteringOperands.push({ | ||||||
| fieldName: 'Id', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, | ||||||
| searchTree: innerTree | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| The model can be serialized/deserialized in JSON format, making it easily transferable between client and server: | ||||||
| ```ts | ||||||
| JSON.stringify(tree, null, 2); | ||||||
| ``` | ||||||
|
|
||||||
| ## Using Sub-Queries | ||||||
| In the context of the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) the *IN / NOT-IN* operators are used with the newly exposed subquery functionality in the *WHERE* clause. | ||||||
|
|
||||||
| > [!Note] | ||||||
| > A subquery is a query nested inside another query used to retrieve data that will be used as a condition for the outer query. | ||||||
|
|
||||||
| Selecting the *IN / NOT-IN* operator in a `FilteringExpression` would create a subquery. After choosing entity and a column to return, it checks if the value in the specified column in the outer query matches or not any of the values returned by the subquery. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| The following expression tree: | ||||||
| ```ts | ||||||
| const innerTree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'products', ['supplier_id']); | ||||||
| innerTree.filteringOperands.push({ | ||||||
| fieldName: 'supplier_id', | ||||||
| conditionName: IgxNumberFilteringOperand.instance().condition('greaterThan').name, | ||||||
| searchVal: 10 | ||||||
| }); | ||||||
|
|
||||||
| const tree = new FilteringExpressionsTree(FilteringLogic.And, undefined, 'suppliers', ['supplier_id']); | ||||||
| tree.filteringOperands.push({ | ||||||
| fieldName: 'supplier_id', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, | ||||||
| searchTree: innerTree | ||||||
| }); | ||||||
| ``` | ||||||
| Could be serialized by calling: | ||||||
| ```ts | ||||||
| JSON.stringify(tree, null, 2); | ||||||
| ``` | ||||||
|
|
||||||
| This would be transferred as: | ||||||
| ``` | ||||||
| { | ||||||
| "filteringOperands": [ | ||||||
| { | ||||||
| "fieldName": "supplier_id", | ||||||
| "condition": { | ||||||
| "name": "inQuery", | ||||||
| "isUnary": false, | ||||||
| "isNestedQuery": true, | ||||||
| "iconName": "in" | ||||||
| }, | ||||||
| "conditionName": "inQuery", | ||||||
| "searchVal": null, | ||||||
| "searchTree": { | ||||||
| "filteringOperands": [ | ||||||
| { | ||||||
| "fieldName": "supplier_id", | ||||||
| "condition": { | ||||||
| "name": "greaterThan", | ||||||
| "isUnary": false, | ||||||
| "iconName": "filter_greater_than" | ||||||
| }, | ||||||
| "conditionName": "greaterThan", | ||||||
| "searchVal": 10, | ||||||
| "searchTree": null | ||||||
| } | ||||||
| ], | ||||||
| "operator": 0, | ||||||
| "entity": "suppliers", | ||||||
| "returnFields": [ | ||||||
| "supplier_id" | ||||||
| ] | ||||||
| } | ||||||
| } | ||||||
| ], | ||||||
| "operator": 0, | ||||||
| "entity": "products", | ||||||
| "returnFields": [ | ||||||
| "supplier_id" | ||||||
| ] | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## SQL Example | ||||||
|
|
||||||
| Let's take a look at a practical example how the Ignite UI for Angular Query Builder Component can be used to build SQL queries. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| In the sample below we have a SQL database with 3 tables - 'suppliers', 'categories' and 'products'. Once we fetch information about them we set our `entities` property of the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) and we can build queries. | ||||||
|
|
||||||
| Let's say we want to find all suppliers who supply products that belong to the 'Beverages' category. Since the data is distributed across the 3 tables we can take advantage of the *IN* operator and accomplish the task by creating subqueries. Each subquery is represented by a `FilteringExpressionsTree` and can be converted to a SQL query through the `transformExpressionTreeToSqlQuery(tree: IExpressionTree)` method. | ||||||
|
|
||||||
| First, we create а `categoriesTree` which will return the `category_id` for the record where `category_name` is `Beverages`. This is the innermost subquery: | ||||||
|
|
||||||
| ```ts | ||||||
| const categoriesTree = new FilteringExpressionsTree(0, undefined, 'categories', ['category_id']); | ||||||
| categoriesTree.filteringOperands.push({ | ||||||
| fieldName: 'category_name', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('equals').name, | ||||||
| searchVal: 'Beverages' | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| The corresponding SQL query for this `FilteringExpressionsTree` will look like this: | ||||||
|
|
||||||
| ``` | ||||||
| SELECT category_id FROM categories WHERE category_name = 'Beverages' | ||||||
| ``` | ||||||
|
|
||||||
| Then we create а `productsTree` that will return the `supplier_id` field from the `categoriesTree` for the records where the `category_id` matches the `category_id` returned by the innermost subquery. We do this by setting the `inQuery` condition and the relevant `searchTree`. This is the middle subquery: | ||||||
|
|
||||||
| ```ts | ||||||
| const productsTree = new FilteringExpressionsTree(0, undefined, 'products', ['supplier_id']); | ||||||
| productsTree.filteringOperands.push({ | ||||||
| fieldName: 'category_id', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, | ||||||
| searchTree: categoriesTree | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| This is the updated state of the SQL query: | ||||||
|
|
||||||
| ``` | ||||||
| SELECT supplier_id FROM products WHERE category_id IN ( | ||||||
| SELECT category_id FROM categories WHERE category_name = 'Beverages' | ||||||
| ) | ||||||
| ``` | ||||||
|
|
||||||
| Finally, we create а `suppliersTree` that will return all fields from `suppliers` entity where the `supplier_id` matches any of the `supplier_id`s returned by the middle subquery. This is the outermost query: | ||||||
|
|
||||||
| ```ts | ||||||
| const suppliersTree = new FilteringExpressionsTree(0, undefined, 'suppliers', ['*']); | ||||||
| suppliersTree.filteringOperands.push({ | ||||||
| fieldName: 'supplier_id', | ||||||
| conditionName: IgxStringFilteringOperand.instance().condition('inQuery').name, | ||||||
| searchTree: productsTree | ||||||
| }); | ||||||
| ``` | ||||||
|
|
||||||
| Our SQL query is now complete: | ||||||
|
|
||||||
| ``` | ||||||
| SELECT * FROM suppliers WHERE supplier_id IN ( | ||||||
| SELECT supplier_id FROM products WHERE category_id IN ( | ||||||
| SELECT category_id FROM categories WHERE category_name = 'Beverages' | ||||||
| ) | ||||||
| ) | ||||||
| ``` | ||||||
|
|
||||||
| Now we can set the `expressionsTree` property of the `IgxQueryBuilderComponent` to `suppliersTree`. Furthermore, every change to the query triggers SQL query execution and refreshing the results data shown in the grid. | ||||||
|
|
||||||
| <code-view style="height:700px" | ||||||
| no-theming | ||||||
| data-demos-base-url="{environment:demosBaseUrl}" | ||||||
| iframe-src="{environment:demosBaseUrl}/interactions/query-builder-sql-sample" > | ||||||
| </code-view> | ||||||
|
|
||||||
| ## Request data from an Endpoint | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this section before the SQL Example section |
||||||
|
|
||||||
| The demo below demonstrates how the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) expression tree could be used to requets data from an endpoint [Northwind WebAPI](https://data-northwind.indigo.design/swagger/index.html) and set it as [`IgxGridComponent`]({environment:angularApiUrl}/classes/igxgridcomponent.html) data source. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| In order to update the data source when there is a change in the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html), the `expressionTreeChange` event should be handled. | ||||||
|
|
||||||
| In the event handler, could be called an `HttpClient` POST request that accepts an endpoint url and body - the [`IgxQueryBuilderComponent`]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) `expressionTree`. | ||||||
|
|
||||||
| After subscribing to the request, the received data should be parsed to proper data type and set it as data source of the [`IgxGridComponent`]({environment:angularApiUrl}/classes/igxgridcomponent.html). | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ```ts | ||||||
| public onChange() { | ||||||
| this.http.post(`${API_ENDPOINT}/QueryBuilder/ExecuteQuery`, this.expressionTree).subscribe(data =>{ | ||||||
| this.data = Object.values(data)[0]; | ||||||
| }); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| <code-view style="height:700px" | ||||||
| no-theming | ||||||
| data-demos-base-url="{environment:demosBaseUrl}" | ||||||
| iframe-src="{environment:demosBaseUrl}/interactions/query-builder-request-sample" > | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this sample can remain here to explain how the customer uses the api to make these requests, the "request sample" should be the main sample in the main topic, instead of the one simply listing the query as text.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Basically as a showcase for the QB. The main topic doesn't have to provide any additional explanation to what the sample is about. |
||||||
| </code-view> | ||||||
|
|
||||||
| ## API References | ||||||
|
|
||||||
| <div class="divider--half"></div> | ||||||
|
|
||||||
| * [IgxQueryBuilderComponent API]({environment:angularApiUrl}/classes/igxquerybuildercomponent.html) | ||||||
| * [IgxQueryBuilderComponent Styles]({environment:sassApiUrl}/index.html#function-query-builder-theme) | ||||||
|
|
||||||
| ## Additional Resources | ||||||
|
|
||||||
| <div class="divider--half"></div> | ||||||
| Our community is active and always welcoming to new ideas. | ||||||
|
|
||||||
| * [Ignite UI for Angular **Forums**](https://www.infragistics.com/community/forums/f/ignite-ui-for-angular) | ||||||
| * [Ignite UI for Angular **GitHub**](https://github.com/IgniteUI/igniteui-angular) | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.