Use the `SELECT` clause, along with `FROM`, `WHERE`, `GROUP BY`, `HAVING`, `ORDER BY`, and `LIMIT` to search and aggregate data.
Among these clauses, `SELECT` and `FROM` are required, as they specify which fields to retrieve and which indices to retrieve them from. All other clauses are optional. Use them according to your needs.
### Syntax
The complete syntax for searching and aggregating data is as follows:
```sql
SELECT[DISTINCT](*|expression)[[AS]alias][,...]
FROMindex_name
[WHEREpredicates]
[GROUPBYexpression[,...]
[HAVINGpredicates]]
[ORDERBYexpression[IS[NOT]NULL][ASC|DESC][,...]]
[LIMIT[offset,]size]
```
### Fundamentals
Apart from the predefined keywords of SQL, the most basic elements are literal and identifiers.
A literal is a numeric, string, date or boolean constant. An identifier is an OpenSearch index or field name.
With arithmetic operators and SQL functions, use literals and identifiers to build complex expressions.
The expression in turn can be combined into a predicate with logical operator. Use a predicate in the `WHERE` and `HAVING` clause to filter out data by specific conditions.
Rule `expression`:

Rule `predicate`:

### Execution Order
These SQL clauses execute in an order different from how they appear:
*Example 2*: Use field name(s) to retrieve only specific fields:
```sql
SELECTfirstname,lastname
FROMaccounts
```
| id | firstname | lastname
:--- | :--- | :---
0 | Amber | Duke
1 | Hattie | Bond
2 | Nanette | Bates
3 | Dale | Adams
*Example 3*: Use field aliases instead of field names. Field aliases are used to make field names more readable:
```sql
SELECTaccount_numberASnum
FROMaccounts
```
| id | num
:--- | :---
0 | 1
1 | 6
2 | 13
3 | 18
*Example 4*: Use the `DISTINCT` clause to get back only unique field values. You can specify one or more field names:
```sql
SELECTDISTINCTage
FROMaccounts
```
| id | age
:--- | :---
0 | 28
1 | 32
2 | 33
3 | 36
## From
Specify the index that you want search.
You can specify subqueries within the `FROM` clause.
### Syntax
Rule `tableName`:

*Example 1*: Use index aliases to query across indexes. To learn about index aliases, see [Index Alias](../opensearch/index-alias/).
In this sample query, `acc` is an alias for the `accounts` index:
```sql
SELECTaccount_number,accounts.age
FROMaccounts
```
or
```sql
SELECTaccount_number,acc.age
FROMaccountsacc
```
| id | account_number | age
:--- | :--- | :---
0 | 1 | 32
1 | 6 | 36
2 | 13 | 28
3 | 18 | 33
*Example 2*: Use index patterns to query indices that match a specific pattern:
```sql
SELECTaccount_number
FROMaccount*
```
| id | account_number
:--- | :---
0 | 1
1 | 6
2 | 13
3 | 18
## Where
Specify a condition to filter the results.
| Operators | Behavior
:--- | :---
`=` | Equal to.
`<>` | Not equal to.
`>` | Greater than.
`<` | Less than.
`>=` | Greater than or equal to.
`<=` | Less than or equal to.
`IN` | Specify multiple `OR` operators.
`BETWEEN` | Similar to a range query. For more information about range queries, see [Range query](../opensearch/term/#range).
`LIKE` | Use for full text search. For more information about full-text queries, see [Full-text queries](../opensearch/full-text/).
`IS NULL` | Check if the field value is `NULL`.
`IS NOT NULL` | Check if the field value is `NOT NULL`.
Combine comparison operators (`=`, `<>`, `>`, `>=`, `<`, `<=`) with boolean operators `NOT`, `AND`, or `OR` to build more complex expressions.
*Example 1*: Use comparison operators for numbers, strings, or dates:
```sql
SELECTaccount_number
FROMaccounts
WHEREaccount_number=1
```
| id | account_number
:--- | :---
0 | 1
*Example 2*: OpenSearch allows for flexible schema so documents in an index may have different fields. Use `IS NULL` or `IS NOT NULL` to retrieve only missing fields or existing fields. We do not differentiate between missing fields and fields explicitly set to `NULL`:
```sql
SELECTaccount_number,employer
FROMaccounts
WHEREemployerISNULL
```
| id | account_number | employer
:--- | :--- | :---
0 | 18 |
*Example 3*: Deletes a document that satisfies the predicates in the `WHERE` clause:
```sql
DELETEFROMaccounts
WHEREage>30
```
## Group By
Group documents with the same field value into buckets.
*Example 1*: Group by fields:
```sql
SELECTage
FROMaccounts
GROUPBYage
```
| id | age
:--- | :---
0 | 28
1 | 32
2 | 33
3 | 36
*Example 2*: Group by field alias:
```sql
SELECTaccount_numberASnum
FROMaccounts
GROUPBYnum
```
| id | num
:--- | :---
0 | 1
1 | 6
2 | 13
3 | 18
*Example 4*: Use scalar functions in the `GROUP BY` clause:
```sql
SELECTABS(age)ASa
FROMaccounts
GROUPBYABS(age)
```
| id | a
:--- | :---
0 | 28.0
1 | 32.0
2 | 33.0
3 | 36.0
## Having
Use the `HAVING` clause to aggregate inside each bucket based on aggregation functions (`COUNT`, `AVG`, `SUM`, `MIN`, and `MAX`).
The `HAVING` clause filters results from the `GROUP BY` clause:
*Example 1*:
```sql
SELECTage,MAX(balance)
FROMaccounts
GROUPBYageHAVINGMIN(balance)>10000
```
| id | age | MAX (balance)
:--- | :---
0 | 28 | 32838
1 | 32 | 39225
## Order By
Use the `ORDER BY` clause to sort results into your desired order.
*Example 1*: Use `ORDER BY` to sort by ascending or descending order. Besides regular field names, using `ordinal`, `alias`, or `scalar` functions are supported:
```sql
SELECTaccount_number
FROMaccounts
ORDERBYaccount_numberDESC
```
| id | account_number
:--- | :---
0 | 18
1 | 13
2 | 6
3 | 1
*Example 2*: Specify if documents with missing fields are to be put at the beginning or at the end of the results. The default behavior of OpenSearch is to return nulls or missing fields at the end. To push them before non-nulls, use the `IS NOT NULL` operator:
```sql
SELECTemployer
FROMaccounts
ORDERBYemployerISNOTNULL
```
| id | employer
:--- | :---
0 |
1 | Netagy
2 | Pyrami
3 | Quility
## Limit
Specify the maximum number of documents that you want to retrieve. Used to prevent fetching large amounts of data into memory.
*Example 1*: If you pass in a single argument, it's mapped to the `size` parameter in OpenSearch and the `from` parameter is set to 0.
```sql
SELECTaccount_number
FROMaccounts
ORDERBYaccount_numberLIMIT1
```
| id | account_number
:--- | :---
0 | 1
*Example 2*: If you pass in two arguments, the first is mapped to the `from` parameter and the second to the `size` parameter in OpenSearch. You can use this for simple pagination for small indices, as it's inefficient for large indices.
Use `ORDER BY` to ensure the same order between pages: