> For the complete documentation index, see [llms.txt](https://docs.pinot.apache.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pinot.apache.org/build-with-pinot/querying-and-sql/sql-syntax/lookup-udf-join.md).

# Lookup UDF Join

{% hint style="info" %}
Lookup UDF Join is **only supported with the single-stage query engine (v1)**. Lookup joins can be executed using [query hints](/build-with-pinot/querying-and-sql/multi-stage-query/join-strategies/lookup-join-strategy.md) in the multi-stage query engine. For more information about using JOINs with the multi-stage query engine, see [JOINs](/build-with-pinot/querying-and-sql/sql-syntax/joins.md).
{% endhint %}

Lookup UDF is used to get dimension data via primary key from a dimension table allowing a decoration join functionality. Lookup UDF can only be used with [a dimension table](/build-with-pinot/ingestion/batch-ingestion/dim-table.md) in Pinot.

## Access control

A query that uses `LOOKUP()` must be authorized for both the table in the `FROM` clause and every dimension table named by the function. Pinot returns HTTP `403` when the requester does not have table access to a referenced dimension table.

`LOOKUP()` resolves rows directly by primary key and cannot apply a row-level security (RLS) filter to the dimension table. When broker row- and column-level authorization is enabled with `pinot.broker.enable.row.column.level.auth`, Pinot rejects a lookup query if the requester has an RLS filter configured for its dimension table. Use a regular multi-stage lookup join when the dimension table requires row-level filtering.

Custom broker access-control implementations must implement table authorization for a set of tables through `authorize(RequesterIdentity, Set<String>)`, because a single query can reference the fact table and one or more lookup dimension tables.

## Syntax

The UDF function syntax is listed as below:

```
lookupUDFSpec:
    LOOKUP
    '('
    '''dimTable'''
    '''dimColToLookup'''
    [ '''dimJoinKey''', factJoinKey ]*
    ')'
```

* `dimTable` Name of the dim table to perform the lookup on.
* `dimColToLookUp` The column name of the dim table to be retrieved to decorate our result.
* `dimJoinKey` The column name on which we want to perform the lookup i.e. the join column name for dim table.
* `factJoinKey` The column name on which we want to perform the lookup against e.g. the join column name for fact table

Noted that:

1. all the dim-table-related expressions are expressed as literal strings, this is the LOOKUP UDF syntax limitation: we cannot express column identifier which doesn't exist in the query's main table, which is the `factTable` table.
2. the syntax definition of `[ '''dimJoinKey''', factJoinKey ]*` indicates that if there are multiple dim partition columns, there should be multiple join key pair expressed.

## Examples

Here are some of the examples

### Single-partition-key-column Example

Consider the table `baseballStats`

| Column                | Type   |
| --------------------- | ------ |
| playerID              | STRING |
| yearID                | INT    |
| teamID                | STRING |
| league                | STRING |
| playerName            | STRING |
| playerStint           | INT    |
| numberOfGames         | INT    |
| numberOfGamesAsBatter | INT    |
| AtBatting             | INT    |
| runs                  | INT    |

and dim table `dimBaseballTeams`

| Column      | Type   |
| ----------- | ------ |
| teamID      | STRING |
| teamName    | STRING |
| teamAddress | STRING |

several acceptable queries are:

#### Dim-Fact LOOKUP example

```
SELECT
  playerName,
  teamID,
  LOOKUP('dimBaseballTeams', 'teamName', 'teamID', teamID) AS teamName,
  LOOKUP('dimBaseballTeams', 'teamAddress', 'teamID', teamID) AS teamAddress
FROM baseballStats
```

| playerName  | teamID | teamName                                                                   | teamAddress                          |
| ----------- | ------ | -------------------------------------------------------------------------- | ------------------------------------ |
| David Allan | BOS    | Boston Red Caps/Beaneaters (from 1876–1900) or Boston Red Sox (since 1953) | 4 Jersey Street, Boston, MA          |
| David Allan | CHA    | null                                                                       | null                                 |
| David Allan | SEA    | Seattle Mariners (since 1977) or Seattle Pilots (1969)                     | 1250 First Avenue South, Seattle, WA |
| David Allan | SEA    | Seattle Mariners (since 1977) or Seattle Pilots (1969)                     | 1250 First Avenue South, Seattle, WA |

#### Self LOOKUP example

```
SELECT 
  teamID, 
  teamName AS nameFromLocal,
  LOOKUP('dimBaseballTeams', 'teamName', 'teamID', teamID) AS nameFromLookup
FROM dimBaseballTeams
```

| teamID | nameFromLocal                                               | nameFromLookup                                              |
| ------ | ----------------------------------------------------------- | ----------------------------------------------------------- |
| ANA    | Anaheim Angels                                              | Anaheim Angels                                              |
| ARI    | Arizona Diamondbacks                                        | Arizona Diamondbacks                                        |
| ATL    | Atlanta Braves                                              | Atlanta Braves                                              |
| BAL    | Baltimore Orioles (original- 1901–1902 current- since 1954) | Baltimore Orioles (original- 1901–1902 current- since 1954) |

### Complex-partition-key-columns Example

Consider a single dimension table with schema:

BILLING SCHEMA

| Column        | Type    |
| ------------- | ------- |
| customerId    | INT     |
| creditHistory | STRING  |
| firstName     | STRING  |
| lastName      | STRING  |
| isCarOwner    | BOOLEAN |
| city          | STRING  |
| maritalStatus | STRING  |
| buildingType  | STRING  |
| missedPayment | STRING  |
| billingMonth  | STRING  |

#### Self LOOKUP example

```
select 
  customerId,
  missedPayment, 
  LOOKUP('billing', 'city', 'customerId', customerId, 'creditHistory', creditHistory) AS lookedupCity 
from billing
```

| customerId | missedPayment | lookedupCity  |
| ---------- | ------------- | ------------- |
| 341        | Paid          | Palo Alto     |
| 374        | Paid          | Mountain View |
| 398        | Paid          | Palo Alto     |
| 427        | Paid          | Cupertino     |
| 435        | Paid          | Cupertino     |

## Usage FAQ

* The data return type of the UDF will be that of the `dimColToLookUp` column type.
* when multiple primary key columns are used for the dimension table (e.g. composite primary key), ensure that the order of keys appearing in the lookup() UDF is the same as the order defined in the `primaryKeyColumns` from the dimension table schema.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.pinot.apache.org/build-with-pinot/querying-and-sql/sql-syntax/lookup-udf-join.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
