> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.kodelabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Data sources

> Add, configure, optimize, and preview data sources that power Building BI dashboards and calculations.

Data sources supply the underlying data for Building BI dashboards. Each data source defines the columns, metrics, and calculations available to your charts and widgets.

<Note>
  For data model context, see [Physical vs virtual data sources](/products/building-bi/concepts/physical-vs-virtual-data-sources), [Point time series hierarchy](/products/building-bi/data-sources#polling-and-data-structure), [Data source types](/products/building-bi/reference/data-source-types), [calculated fields](/products/building-bi/reference/calculated-fields), and [table structures](/products/building-bi/reference/table-structures).
</Note>

## In this guide

<CardGroup cols={2}>
  <Card title="Choose a source" icon="database" href="#data-source-types">
    Compare physical, virtual, and key time-series sources.
  </Card>

  <Card title="Inspect schema" icon="table" href="#data-source-schema">
    Preview columns, samples, and toolbar actions.
  </Card>

  <Card title="Add calculations" icon="function-square" href="#calculated-columns-metrics-and-urls">
    Create columns, metrics, and URL fields.
  </Card>

  <Card title="Optimize and validate" icon="gauge" href="#optimize-virtual-data-sources">
    Optimize sources, check usage, and apply point bounds.
  </Card>
</CardGroup>

<Info>
  Navigate to `Building BI` and select `Data Sources` in the left navigation panel.
</Info>

<Frame caption="Data Sources page showing all available data sources with their type, database type, and description">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-list.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=801948d90bae5f886cde8e17d0161ba5" alt="Data Sources page showing a table of data sources with columns for Name, Type (Physical or Virtual), Database Type (CLICKHOUSE, POSTGRES, or BIG_QUERY), Description, and Date Added, with a Search bar and Add Data Sources button" width="1024" height="746" data-path="images/kode-os/building-bi/bbi-datasources-list.png" />
</Frame>

<Warning>
  Changes to data sources, calculated columns, and metrics are portfolio-wide. Any modification affects all dashboards across the entire portfolio, not just a single building.
</Warning>

## Data source types

Building BI supports two data source types:

* **Physical** -- Maps directly to a database table managed by the backend. Physical data sources reflect the raw data stored in your connected database.
* **Virtual** -- Built from SQL queries (views) on top of physical tables. Virtual data sources give you flexibility to create custom datasets without modifying the underlying database. You can edit a virtual table's SQL by clicking the three-dot menu on its row.

Both types expose columns and metrics that you can use when building dashboard charts. When you select a chart in edit mode, the `Chart Configuration` panel lets you pick a data source and drag its metrics into the X-Axis, Y-Axis, and Legend fields.

<Frame caption="Chart Configuration panel showing a selected data source and its available metrics">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-chart-config.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=58d36dfb7dd5bbe1bf0b93ef5e7d4676" alt="Dashboard in edit mode showing the Chart Configuration panel with Setup and Style tabs, a Charts section with chart type icons and search, a Data Source dropdown set to Timeseries Last Va, a Metrics list including Total Count, One Metric, Cooling Request Pe, Heating Request P, Percent Running St, Device ID, Zone Temp Inside, and more with fx buttons, X-Axis set to Area Name, and Legend and Y-Axis drop zones" width="991" height="845" data-path="images/kode-os/building-bi/bbi-datasources-chart-config.png" />
</Frame>

The Data Sources page provides two tabs: `All Datasources` lists every data source in your portfolio, and `Favorites` shows only data sources you have starred. Select the star icon on any row to add it to your favorites.

<Frame caption="Favorites tab showing starred data sources for quick access">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-favorites.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=317b6ba053d2e65956a4a4d314a9a87b" alt="Data Sources page showing the Favorites tab with a single starred data source RTU Details of type Virtual and CLICKHOUSE database type added 3 minutes ago" width="1024" height="284" data-path="images/kode-os/building-bi/bbi-datasources-favorites.png" />
</Frame>

Select the three-dot menu on any row in the data sources list to access `Edit Details` or `Delete`.

<Frame caption="Row-level three-dot menu with Edit Details and Delete options">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-row-menu.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=e83d7d3f22d2e99fc3830d4f217fc7f9" alt="Data Sources list showing the All Datasources tab with a three-dot menu open on a row displaying Edit Details and Delete options, with RTU Details starred as a favorite" width="1024" height="288" data-path="images/kode-os/building-bi/bbi-datasources-row-menu.png" />
</Frame>

<Warning>
  Never delete a data source unless you are certain no dashboards rely on it. Deleting a data source instantly breaks every dashboard and chart that references it across the entire portfolio.
</Warning>

### Virtual table considerations

Virtual tables recompute on every query. Use them for light, real-time reshapes. Prefer physical tables when historical trending or heavy aggregation must stay fast.

* You can create virtual tables on ClickHouse and PostgreSQL data sources.
* You cannot create virtual tables on BigQuery.

## Database types

Building BI connects to three database engines. Each stores a specific category of data, and each requires its own SQL dialect for calculated columns and metrics.

| Database       | Purpose                                            | Virtual table support |
| -------------- | -------------------------------------------------- | --------------------- |
| **ClickHouse** | Real-time sensor data (Point Time Series tables)   | Yes                   |
| **PostgreSQL** | Non-sensor multidimensional data with many columns | Yes                   |
| **BigQuery**   | Module data (FTT, FDD, OSS)                        | No                    |

<Warning>
  Tables do not join across database types. You cannot combine a ClickHouse table with a BigQuery table in a single query.
</Warning>

When writing calculated columns or metrics, use the SQL syntax that matches the database type of the data source. ClickHouse, PostgreSQL, and BigQuery each follow different SQL dialects.

## Key data sources

While many tables exist, two primary sources cover the majority of sensor-related use cases:

| Choose Real-Time when…                 | Choose Last Value when…                       |
| -------------------------------------- | --------------------------------------------- |
| You plot trends or history             | You show current status or "now" tiles        |
| Time alignment across readings matters | You compare sensors with different poll rates |
| You need every reading in a range      | You only need the latest value per point      |

### Point Time Series Real-Time

The core sensor table where every reading creates a new row. This is the source of truth for building sensor data. Slight variants exist that poll less frequently — use those only when loading time is a concern.

Point Time Series Real-Time runs on ClickHouse, so all calculated columns and metrics on this table must use ClickHouse SQL syntax.

### Point Time Series Last Value

Returns only the most recent recorded value per sensor. Search `Last Value` in the data sources list to find this table.

<Info>
  Last Value rows are not a synchronized snapshot. Each row is the most recent reading for that sensor, regardless of timestamp. Do not use them for time-sensitive math that assumes every sensor sampled at once.
</Info>

### Aggregated tables

Some data sources aggregate readings at fixed intervals (15-minute, hourly, daily, or monthly). Aggregated tables return summary values (average, sum, maximum, minimum, count, or delta) for each point during the polling period.

Aggregation is valuable for some sensor types. Hourly average air temperature shows how a space generally felt, while an hourly average of elevator floor level is less meaningful.

### Polling and data structure

Each point time series table contains one row per data point at a given time period, along with context columns identifying the device, area, and building. You may see multiple rows from the same device at the same timestamp if they come from different points.

Prefer ID columns over name columns for filters and calculations. Names are editable and not guaranteed unique.

Each row carries nested layers of context around the data point, from the building level down to the individual sensor value:

```mermaid actions={false} theme={"theme":{"light":"github-dark","dark":"github-dark"}}
---
config:
  theme: neutral
  layout: dagre
---
flowchart TB
  subgraph building["Building Level"]
    B1["building_id / building_name"]
    B2["building_region · building_type\nbuilding_sqft · building_timezone"]
  end
  subgraph area["Area (Floor) Level"]
    A1["area_id / area_name"]
  end
  subgraph device["Device (Equipment) Level"]
    D1["device_id / device_name"]
    D2["device_mechanical_type\ndevice_class · device_zone"]
  end
  subgraph point["Point (Sensor) Level"]
    P1["point_id / point_name"]
    P2["point_kind · point_unit · point_enum"]
  end
  subgraph data["Data"]
    V1["date_time · date_time_local"]
    V2["number_value · string_value\nboolean_value · enum_mapping"]
    V3["cur_status · cur_state · point_writable"]
  end

  B1 --> A1 --> D1 --> P1 --> V1

  classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C
  classDef Peach stroke-width:1px, stroke-dasharray:none, stroke:#FBB35A, fill:#FFEFDB, color:#8F632D
  classDef Rose stroke-width:1px, stroke-dasharray:none, stroke:#FF5978, fill:#FFDFE5, color:#8E2236
  classDef Green fill:#B0E4CC, color:#285A48, stroke:#285A48

  class B1,B2 Sky
  class A1 Sky
  class D1,D2 Peach
  class P1,P2 Rose
  class V1,V2,V3 Green

  linkStyle 0,1,2,3 stroke:#00C853,fill:none
```

Two common polling patterns exist:

* **Change of value (COV)** -- A new row is created only when the value changes beyond the COV threshold, with a maximum interval of one hour between rows.
* **Structured polling** -- A new row is created at every polling interval (every 1, 2, 5, 10, or 15 minutes) regardless of whether the value changed.

<Tip>
  Check the `Point` page and `Device` page in the left navigation bar to understand which points and devices exist in the building before building queries against point time series tables.
</Tip>

## Data source schema

Selecting any data source name opens its schema view. The schema reveals the building blocks available for your dashboards across five tabs: `Columns`, `Calculated`, `Metrics`, `URLs`, and `Actions`.

<Frame caption="Data source schema view showing columns with their display names, data types, and visibility settings">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-schema.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=9a0dd8aeb716681835daf1051ec2bb89" alt="Edit RTU Details schema view showing Virtual and Not Optimized badges, Schema and Samples tabs, a warning about changes affecting all charts, and columns tab with a table of columns including building_id, building_name, building_timezone, building_region, building_type, building_sqft, building_brand, building_phone_number, area_id, area_name, datasource_id, and datasource_name with their display names, descriptions, data types, hidden status, and sensitive value flags" width="1024" height="718" data-path="images/kode-os/building-bi/bbi-datasources-schema.png" />
</Frame>

| Tab          | Description                                                           |
| ------------ | --------------------------------------------------------------------- |
| `Columns`    | Native database columns that exist in the underlying table            |
| `Calculated` | Custom single-row logic columns you create with SQL expressions       |
| `Metrics`    | Aggregate-level calculations that recalculate based on chart grouping |
| `URLs`       | Internal or external links generated from row data                    |
| `Actions`    | Actionable operations available on the data source                    |

### Schema toolbar actions

From the top of the schema view, you can:

* **`Sync Datasource`** -- Pull in the latest columns if the underlying KODE table was updated.
* **`+ Add Column`** -- Create a new calculated column, metric, or URL.
* **Three-dot menu** -- Access `Check Column Usage` to see which dashboards reference specific columns, `Check Optimization` to review virtual table performance, or `Edit Details` to change basic parameters.

<Frame caption="Three-dot menu showing Check Column Usage, Check Optimization, and Edit Details options">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-menu.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=b1fc4d2726ef58dbddc7bc38bb54d83b" alt="Data source schema view three-dot menu showing Check Column Usage, Check Optimization, and Edit Details options" width="1024" height="294" data-path="images/kode-os/building-bi/bbi-datasources-menu.png" />
</Frame>

### Samples tab

The `Samples` tab previews a subset of records from the data source before you build charts. Use it to validate fields, confirm virtual table SQL, and inspect example values without creating a report first.

<Steps>
  <Step title="Open a data source">
    Navigate to `Building BI` > `Data Sources` and select a data source name.
  </Step>

  <Step title="Open Samples">
    Next to the `Schema` tab, select `Samples`.
  </Step>

  <Step title="Review the preview">
    The table shows available columns and example values. The preview is read-only and limited to approximately 20 rows for performance. Changes you make to the data source are reflected automatically when you return to **Samples**.

    Native columns and calculated columns appear together in the preview. Calculated columns show an `i` icon in the column header. Select the icon to open the column's query. Use that when you are writing other calculated columns or metrics from the same source.
  </Step>
</Steps>

<Frame caption="Samples tab showing a preview of 20 rows from a physical data source">
  <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-samples.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=292eb4780eb0b35b522bc27b46a2dd93" alt="Edit Point Timeseries Samples tab showing a Physical data source with an info banner stating this is a preview with a maximum of 20 rows visible, and a table with columns for Connector ID, Building ID, Building TZ, Device ID, Field, Kind, Measurement, and Unit" width="1024" height="713" data-path="images/kode-os/building-bi/bbi-datasources-samples.png" />
</Frame>

Both **physical** and **virtual** data sources support sample preview across ClickHouse, PostgreSQL, and BigQuery dialects.

<Tip>
  Calculated columns appear at the end of the sample table. Look for the `i` icon in the header to confirm a column is calculated, then select it to inspect the expression.
</Tip>

| Scenario                     | What you see                                                               |
| ---------------------------- | -------------------------------------------------------------------------- |
| **Structure with no rows**   | Column headers display with a **No results** message                       |
| **Calculated column errors** | Invalid calculated columns are omitted from the preview to avoid confusion |
| **After schema changes**     | Reopen **Samples** or sync the data source to refresh the preview          |

<Note>
  Sample preview is for validation, not full data exploration. Use dashboards and charts for analysis workflows.
</Note>

## Calculated columns, metrics, and URLs

Calculated columns, calculated metrics, and URLs extend your data sources beyond raw columns. Use this section for SQL patterns and examples. For create steps, field options, data-type limits, and columns-versus-metrics graphics, see [Calculated fields](/products/building-bi/reference/calculated-fields).

| Feature      | Calculated columns                               | Calculated metrics                               |
| ------------ | ------------------------------------------------ | ------------------------------------------------ |
| **Scope**    | Single row                                       | Multiple rows                                    |
| **Function** | Applies logic to create a new virtual data field | Aggregates data (for example, SUM, AVG, COUNT)   |
| **Behavior** | Independent; does not aggregate                  | Dynamically recalculates based on chart grouping |

<Warning>
  Editing a calculated column, metric, or URL affects all dashboards portfolio-wide. Always validate your logic before saving. Changing a calculation can break multiple live dashboards.
</Warning>

### Calculated columns

Calculated columns apply row-level logic to produce a new value for each row independently, similar to a spreadsheet formula. Each calculated column contains a single SQL expression.

Use calculated columns to:

* Extract time components for filtering (for example, `toHour(date_time_local)` returns the hour as an integer).
* Isolate a specific sensor's values using conditional logic (for example, return `number_value` only when `ontology_point_field` matches a target, otherwise return null).
* Plot multiple sensor readings side-by-side in a single chart.

The examples below use the Point Time Series Real-Time table, which runs on ClickHouse. All calculated columns and metrics on this table must follow ClickHouse SQL syntax. Match the dialect of your source: [PostgreSQL](https://www.postgresql.org/docs/current/), [ClickHouse](https://clickhouse.com/docs/sql-reference), or [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax).

#### Column rules

* Each calculated column must have a unique name within the data source.
* Column names must use the exact database field names (for example, `building_name`, not "Building Name").
* You cannot edit a calculated column's SQL expression from the chart `fx` editor. Edit it in `Data Sources` directly.

#### Metadata syntax on Real-Time tables

When referencing metadata fields (such as `building_name`, `device_name`, or `ontology_point_field`) on the Point Time Series Real-Time table, you must wrap them in the `dictGetString` pattern:

```sql Real-Time metadata example theme={"theme":{"light":"github-dark","dark":"github-dark"}}
if (
  dictGetString(
    'point_time_series.metadata_dictionary',
    'ontology_point_field',
    tuple(point_id)
  ) = 'discharge_air_pressure_setpoint',
  number_value,
  null
)
```

Replace `discharge_air_pressure_setpoint` with any `ontology_point_field` value. Use the appropriate payload column (`number_value`, `string_value`, or `boolean_value`) depending on the point's data type.

#### Simplified syntax on Last Value tables

The Last Value table is pre-processed, so you can reference metadata columns directly without `dictGetString`:

```sql Last Value metadata example theme={"theme":{"light":"github-dark","dark":"github-dark"}}
if (
  ontology_point_field = 'discharge_air_pressure_setpoint',
  number_value,
  null
)
```

### Calculated metrics

Calculated metrics perform aggregate calculations across multiple rows. Unlike columns, metrics dynamically recalculate based on your chart's grouping. If a chart is grouped by hour, the metric produces one value per hour. If grouped by device, it produces one value per device.

Use aggregate functions like `sumIf`, `countIf`, or `avgIf` to filter rows before aggregating. You can combine multiple functions to build complex logic.

**Example: net occupancy metric**

This metric calculates occupancy by subtracting people leaving from people entering:

```sql Net occupancy (Real-Time table) theme={"theme":{"light":"github-dark","dark":"github-dark"}}
sumIf(
  number_value,
  dictGetString(
    'point_time_series.metadata_dictionary',
    'ontology_point_field',
    tuple(point_id)
  ) = 'people_entering_counter'
) - sumIf(
  number_value,
  dictGetString(
    'point_time_series.metadata_dictionary',
    'ontology_point_field',
    tuple(point_id)
  ) = 'people_leaving_counter'
)
```

<Info>
  Columns operate on a single row. Metrics aggregate across multiple rows. Use a column when you need a per-row value (for dimensions or filters). Use a metric when you need an aggregated result (for chart values).
</Info>

### URLs

URL columns turn data into navigation actions in your dashboards.

#### Internal URLs

Internal URLs follow the pattern `/buildings/{building_id}` and navigate within Cloud BMS on both desktop and mobile. Building BI validates the IDs automatically.

<Tip>
  Add a `Device URL` to table widgets for a quality-of-life improvement. In the widget style settings, set the URL option on the device name column to `Device URL`. Each device name cell becomes a hyperlink to that device's page, letting users jump directly from a dashboard finding to the relevant device.
</Tip>

#### External URLs

External URLs open in a new browser tab, allowing users to navigate to resources outside KODE OS such as a CMMS system or a specific work order.

For URL behavior on chart widgets, data-type limits on metrics, and `Hidden` or `Sensitive Values` options, see [Calculated fields](/products/building-bi/reference/calculated-fields).

## Add a data source

<Steps>
  <Step title="Open the data sources page">
    Navigate to `Building BI` > `Data Sources`.
  </Step>

  <Step title="Start a new data source">
    Select `+ Add Data Sources` and choose `Physical` or `Virtual`.
  </Step>

  <Step title="Configure the data source">
    On the `Details` tab, complete the required fields:

    | Field             | Description                                                                                                 |
    | ----------------- | ----------------------------------------------------------------------------------------------------------- |
    | **Database Type** | The database engine for the source connection (ClickHouse, PostgreSQL, or BigQuery)                         |
    | **Database**      | The target database name (consult support, a data analyst, or copy from an existing client)                 |
    | **Schema**        | The database schema for the source table (consult support, a data analyst, or copy from an existing client) |
    | **Name**          | The internal identifier for the data source                                                                 |
    | **Display Name**  | A human-readable name shown in dashboard configuration panels                                               |
    | **Description**   | Optional summary of the data source's purpose                                                               |

    <Frame caption="Add Data Source dialog showing the Details tab for a virtual data source">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-add-details.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=444effa6c9dd9d45f052d3bdff194201" alt="Add Data Source dialog with Virtual selected, Details tab active, showing Database Type set to CLICKHOUSE, Database set to point_time_series, Name set to rtu_details, Display Name set to RTU Details, a Description field, and Cancel and Save buttons" width="606" height="603" data-path="images/kode-os/building-bi/bbi-datasources-add-details.png" />
    </Frame>

    For virtual tables, switch to the `SQL Expression` tab to define the query. The `Source Tables` field lists the tables referenced in your SQL. Write the SQL expression using the syntax that matches the selected database type (PostgreSQL or ClickHouse).

    <Tip>
      Review existing virtual tables for SQL syntax examples. You can also consult the database engine documentation or use an LLM for query construction. LLMs handle standard ClickHouse and PostgreSQL syntax well, though they may not know KODE OS-specific table structures.
    </Tip>

    <Frame caption="Add Data Source dialog showing the SQL Expression tab with source tables and query editor">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-add-sql.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=d6abbcaad3735183b486ab2cbf6a96eb" alt="Add Data Source dialog with Virtual selected, SQL Expression tab active, showing Source Tables listing connector_time_series, device_time_series, and point_time_series aggregated_last_values, and a SQL Expression editor with a query using dictGetString on point_time_series metadata_dictionary" width="613" height="612" data-path="images/kode-os/building-bi/bbi-datasources-add-sql.png" />
    </Frame>
  </Step>

  <Step title="Save the data source">
    Select `Save` to add the data source to your list. A confirmation toast appears.

    <Frame caption="Confirmation toast after creating a data source">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-created-toast.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=8fdb62a7590002fc7eeb99041e53ad02" alt="Datasource created successfully confirmation toast" width="333" height="90" data-path="images/kode-os/building-bi/bbi-datasources-created-toast.png" />
    </Frame>
  </Step>
</Steps>

<Info>
  Creating new data sources is uncommon. Try to solve problems with existing data sources first. If a table exists at another client but is missing from yours, add it using the same connection details.
</Info>

## Optimize virtual data sources

Unoptimized virtual data sources load slower and can produce errors with large datasets. A warning banner reading "Not Optimized" appears at the top of any unoptimized data source. See [dashboard performance](/products/building-bi/concepts/dashboard-performance) for broader optimization guidance.

### Optimize from the data source page

<Steps>
  <Step title="Open the data source">
    Navigate to `Data Sources` and select the virtual data source you want to optimize.
  </Step>

  <Step title="Edit the SQL expression">
    Select the three-dot menu and choose `Edit Details`. Switch to the `SQL Expression` tab to review and modify the query. An `Optimize your Virtual Datasource` section appears at the bottom with suggestions.

    <Frame caption="Edit Details dialog showing the SQL Expression tab with optimization suggestions">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-edit-sql.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=f5a9e433284bbda9373cd7f21f98cd1b" alt="Edit RTU Details dialog with Virtual selected, SQL Expression tab showing Source Tables and SQL code editor with ClickHouse query, and an Optimize your Virtual Datasource expandable section at the bottom" width="597" height="627" data-path="images/kode-os/building-bi/bbi-datasources-edit-sql.png" />
    </Frame>
  </Step>

  <Step title="Apply optimization suggestions">
    Expand the `Optimize your Virtual Datasource` section. The system generates AI-powered filter suggestions to limit the data retrieved from the source. Review the suggestion and apply it to improve query performance.

    <Frame caption="Optimization suggestions with AI-generated filters for source tables">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-optimize.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=2154609381db4a7d3cbd2e1ac108cf4a" alt="Optimize your Virtual Datasource section showing an AI-generated note about applying filters, a suggested filter template with column_name_filter placeholder, and expandable sections for point_time_series.aggregated_last_values and point_time_series.metadata source tables" width="583" height="625" data-path="images/kode-os/building-bi/bbi-datasources-optimize.png" />
    </Frame>
  </Step>

  <Step title="Save your changes">
    Select `Save` to apply the optimization.
  </Step>
</Steps>

### Identify unoptimized sources from a dashboard

You can also check optimization status while editing a dashboard. Click `Edit Dashboard`, select a chart, and open the `Setup` panel. The `Data Source` dropdown flags any unoptimized sources so you can address them directly.

## Check column usage

Unused columns add overhead to your data sources. Use the column usage check to identify and remove them.

<Steps>
  <Step title="Open column usage">
    From the data source schema view, select the three-dot menu and choose `Check Column Usage`.
  </Step>

  <Step title="Select what to check">
    Choose which categories to scan: `Check Columns`, `Check Calculated`, and `Check Metrics`. Each category shows the total number of items. Select `Check` to run the scan.

    <Frame caption="Check Usage dialog showing column, calculated, and metrics categories to scan">
      <img src="https://mintcdn.com/kodelabs/B4MTivRlYKOrn2ok/images/kode-os/building-bi/bbi-datasources-check-usage.png?fit=max&auto=format&n=B4MTivRlYKOrn2ok&q=85&s=cbff4b9ef9b343ed03fbece7a095e33d" alt="Check Usage dialog stating You are about to check usage of columns in this Data Source, with checkboxes for Check Columns with 51 Total Columns, Check Calculated with 0 Total Columns, and Check Metrics with 1 Total Columns, and Cancel and Check buttons" width="593" height="291" data-path="images/kode-os/building-bi/bbi-datasources-check-usage.png" />
    </Frame>
  </Step>

  <Step title="Remove unused columns">
    Review the results. Columns marked "Unused" can be safely deleted to reduce source overhead.
  </Step>
</Steps>

## Point bounds and unit conversion

KODE OS stores **point high/low bounds** and a **unit conversion foundation** in the platform data model. These capabilities support analytics and diagnostics workflows in Building BI. They do not change displayed point values or trend charts by default.

<Info>
  Bounds and unit conversion are foundation capabilities. End-user value comes from dashboards, metrics, and filters your team builds on top of the platform data.
</Info>

### Point high/low bounds

The platform applies default minimum and maximum bounds per **ontology field** based on sensor or measurement type. Admins can review and adjust bounds per point in [Point modeling](/products/kode-os/devices/device-templates#point-modeling).

When an incoming value exceeds configured bounds, the platform flags the reading with an **Out of Range** state. Use this in Building BI to:

* Filter point time series data to exclude bad readings from KPIs and rollups
* Build sensor health dashboards that list points currently out of range
* Group counts by device, system, floor, or area for calibration prioritization
* Replace dashboards that rely on static ontology bounds with platform bounds logic

After bounds are enabled for your portfolio, run **Sync Datasource** on affected tables to pull the latest columns into Building BI.

### Unit conversion foundation

The unit conversion layer normalizes compatible units within the same measurement type in the background. Use it in calculated columns and metrics when you aggregate data across buildings where the same point field may report in different units (for example, `°C` vs `°F`).

| Applies to                                  | Does not apply yet                      |
| ------------------------------------------- | --------------------------------------- |
| Bounds evaluation across mixed units        | Raw stored values in the database       |
| Metric calculations and normalization in BI | End-user displayed values in KODE OS UI |
| Portfolio comparisons in dashboards         | Trend chart display units               |

<Note>
  [Dynamic units](/products/building-bi/widget-customization#style-tab-reference) in widget **Style** settings control chart label formatting. They are separate from the platform unit conversion foundation used in data and metrics logic.
</Note>

### Build cleaner metrics

Use bounds and unit conversion together for more reliable KPIs:

1. **Normalize units** in calculated metrics when rolling up the same ontology field across sites with different `point_unit` values.
2. **Exclude out-of-range readings** so a small set of failing sensors does not skew portfolio averages or space-level rollups.
3. **Filter dashboards** on **Out of Range** in `cur_state` or related status fields to surface sensors that need calibration or replacement.

<Tip>
  Check the **Data Quality** global template in [Templates](/products/building-bi/templates#global-templates) as a starting point for sensor health dashboards, then extend it with bounds-aware filters and metrics.
</Tip>

## Limitations

Calculated columns and metrics do not support window functions or aggregations of aggregations (nesting aggregate functions). This is a calculated field limit, not a ClickHouse limit. Two workarounds exist:

* **Virtual tables** -- Perform a window function or one level of aggregation in the virtual table SQL, then use the result as a regular column in your charts.

<Info>
  SQL dialect differs per database type. A ClickHouse expression does not work on a PostgreSQL or BigQuery data source without modification. Always verify the database type before writing or copying SQL expressions.
</Info>

<Tip>
  Thousands of calculations have been written across KODE deployments. If you are stuck, review existing calculated columns and metrics in other data sources for working examples, or ask your team for help.
</Tip>

## Column reference

The Point Time Series tables (Real-Time and Last Value) share a common column structure. Use this reference when building calculated columns, metrics, or filters.

<AccordionGroup>
  <Accordion title="Timestamp columns">
    | Column            | Display name    | Type          | Description                                                                                          |
    | ----------------- | --------------- | ------------- | ---------------------------------------------------------------------------------------------------- |
    | `date_time_local` | Date Time Local | Date and Time | Localized timestamp adjusted to the building's timezone. Use for dashboards and user-facing visuals. |
    | `date_time`       | Date Time       | Date and Time | UTC timestamp from the backend. This is the source-of-truth timestamp.                               |
  </Accordion>

  <Accordion title="Entity identifier columns">
    | Column            | Display name    | Type   | Description                                                                               |
    | ----------------- | --------------- | ------ | ----------------------------------------------------------------------------------------- |
    | `building_id`     | Building Id     | String | Unique building identifier. Prefer this over `building_name` to avoid duplication issues. |
    | `building_name`   | Building Name   | String | Building name. Can be duplicated across clients.                                          |
    | `datasource_id`   | Datasource Id   | String | Unique identifier for the data source providing this data.                                |
    | `datasource_name` | Datasource Name | String | Name of the data source connection (for example, BMS or API integration).                 |
    | `device_id`       | Device Id       | String | Unique identifier for the device (equipment) associated with the point.                   |
    | `device_name`     | Device Name     | String | Device name (for example, AHU-1 or Pump-3). Editable and may not be unique.               |
    | `area_id`         | Area Id         | String | Unique identifier for the area (typically a floor level).                                 |
    | `area_name`       | Area Name       | String | Area name (usually a floor name). Aligns with floor plans if configured.                  |
    | `point_id`        | Point Id        | String | Unique sensor or point identifier. Always prefer this over `point_name` for accuracy.     |
    | `point_name`      | Point Name      | String | Sensor or reading name. Can vary by naming convention.                                    |
  </Accordion>

  <Accordion title="Point classification columns">
    | Column           | Display name   | Type    | Description                                                                               |
    | ---------------- | -------------- | ------- | ----------------------------------------------------------------------------------------- |
    | `point_kind`     | Point Kind     | String  | Data type of the point: Number, Boolean, or String. Determines which value column to use. |
    | `point_unit`     | Point Unit     | String  | Unit of measurement (for example, degrees F, %, or kW).                                   |
    | `point_enum`     | Point Enum     | String  | Mapping for categorical values (for example, 1 = ON, 0 = OFF).                            |
    | `point_writable` | Point Writable | Boolean | Indicates if the point can be overridden (true = writable from BI).                       |
  </Accordion>

  <Accordion title="Value columns">
    | Column          | Display name  | Type    | Description                                                                        |
    | --------------- | ------------- | ------- | ---------------------------------------------------------------------------------- |
    | `number_value`  | Number Value  | Double  | Numeric sensor reading. The most commonly used value field.                        |
    | `string_value`  | String Value  | String  | String-based value for points with multiple states (for example, modes or alarms). |
    | `boolean_value` | Boolean Value | Boolean | Boolean value (true/false or 1/0). Often paired with enum mapping.                 |
    | `enum_mapping`  | Enum Mapping  | String  | Human-readable mapping of boolean or coded values. Prefer this over raw boolean.   |
  </Accordion>

  <Accordion title="Ontology columns">
    | Column                         | Display name                 | Type   | Description                                                                         |
    | ------------------------------ | ---------------------------- | ------ | ----------------------------------------------------------------------------------- |
    | `ontology_canonical_type`      | Ontology Canonical Type      | String | Standardized device type across all buildings (for example, AHU or Pump).           |
    | `ontology_canonical_type_name` | Ontology Canonical Type Name | String | Human-readable version of the canonical device type.                                |
    | `device_mechanical_type`       | Device Mechanical Type       | String | Raw mechanical type assigned to the device (can vary by building or client).        |
    | `device_mechanical_type_name`  | Device Mechanical Type Name  | String | Display-friendly version of the mechanical type.                                    |
    | `ontology_entities`            | Ontology Entities            | String | Grouping field for organizing related components (less commonly used in BI).        |
    | `ontology_point_field`         | Ontology Point Field         | String | Standardized field name for the point. Normalizes inconsistent naming across sites. |
    | `ontology_point_field_name`    | Ontology Point Field Name    | String | Human-readable version of the ontology point field.                                 |
    | `ontology_prefix`              | Ontology Prefix              | String | Prefix component of the ontology naming convention.                                 |
    | `ontology_suffix`              | Ontology Suffix              | String | Suffix component of the ontology naming convention.                                 |
    | `ontology_measurement`         | Ontology Measurement         | String | Measurement type (for example, Temperature or Pressure).                            |
    | `ontology_aggregation`         | Ontology Aggregation         | String | Suggested aggregation type for the point (for example, avg or sum).                 |
    | `ontology_descriptor`          | Ontology Descriptor          | String | Additional descriptive tag for the point within ontology.                           |
  </Accordion>

  <Accordion title="Building metadata columns">
    | Column                | Display name         | Type   | Description                                                                |
    | --------------------- | -------------------- | ------ | -------------------------------------------------------------------------- |
    | `building_region`     | Building Region      | String | Region metadata assigned at the building level (configured in Launchpad).  |
    | `building_type`       | Building Type        | String | Type of building (for example, Office or Retail). Configured in Launchpad. |
    | `building_sqft`       | Building Square Feet | Double | Total square footage of the building (Launchpad metadata).                 |
    | `building_timezone`   | Building Timezone    | String | Timezone of the building. Used for localization of timestamps.             |
    | `building_brand`      | Building Brand       | String | Brand or ownership grouping of the building (Launchpad metadata).          |
    | `building_identifier` | Building Identifier  | String | Additional identifier for grouping or referencing buildings.               |
    | `building_district`   | Building District    | String | District grouping for the building (Launchpad metadata).                   |
    | `building_zone`       | Building Zone        | String | Zone grouping at the building level (Launchpad metadata).                  |
  </Accordion>

  <Accordion title="Device metadata columns">
    | Column              | Display name      | Type    | Description                                                       |
    | ------------------- | ----------------- | ------- | ----------------------------------------------------------------- |
    | `device_tenant`     | Device Tenant     | String  | Tenant grouping of the device (client-specific usage).            |
    | `device_class`      | Device Class      | String  | Device classification (custom metadata set in the device module). |
    | `device_zone`       | Device Zone       | String  | Zone grouping of the device within the building.                  |
    | `device_site_level` | Device Site Level | Boolean | Indicates if the device is site-level equipment.                  |
    | `device_location`   | Device Location   | String  | Physical or logical location of the device within the building.   |
  </Accordion>

  <Accordion title="Connectivity and status columns">
    | Column            | Display name     | Type   | Description                                                                                                                                                                               |
    | ----------------- | ---------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `cur_status`      | Cur Status       | String | Connectivity status of the point (OK, Fault, Disabled, or No Value).                                                                                                                      |
    | `cur_state`       | Cur State        | String | Array of active states such as **Override**, **FDD**, **Stale**, and **Out of Range**. Requires parsing in BI. Use **Out of Range** to identify readings outside configured point bounds. |
    | `datasource_type` | Datasource Type  | String | Type of data source (API, BMS, AI).                                                                                                                                                       |
    | `api_axslot_path` | Api Ax Slot Path | String | Backend reference path for API/BMS integration (primarily for engineering use).                                                                                                           |
    | `api_point_name`  | Api Point Name   | String | Original point name from the source system before normalization.                                                                                                                          |
  </Accordion>
</AccordionGroup>

## Related reading

<CardGroup cols={2}>
  <Card title="Physical vs virtual data sources" icon="database" href="/products/building-bi/concepts/physical-vs-virtual-data-sources" arrow={true}>
    Compare direct tables with derived virtual sources.
  </Card>

  <Card title="Key data sources" icon="activity" href="/products/building-bi/data-sources#key-data-sources" arrow={true}>
    Choose Real-Time or Last Value Point Time Series sources.
  </Card>

  <Card title="Database types" icon="server" href="/products/building-bi/data-sources#database-types" arrow={true}>
    Understand ClickHouse, PostgreSQL, and BigQuery trade-offs.
  </Card>

  <Card title="Column reference" icon="table" href="/products/building-bi/data-sources#column-reference" arrow={true}>
    Look up column names for calculations and filters.
  </Card>
</CardGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Dashboards" icon="layout-dashboard" href="/products/building-bi/dashboards" arrow={true}>
    Create and configure interactive dashboards with charts, tables, and widgets.
  </Card>

  <Card title="Widget customization" icon="palette" href="/products/building-bi/widget-customization" arrow={true}>
    Customize chart styles, colors, and layouts for your dashboard widgets.
  </Card>
</CardGroup>
