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

# Updating data

> Real-time and batch updates in ClickHouse.

<div />

Propel's offers both [real-time](#real-time-updates) and [batch](#batch-updates) update capabilities.

## Real-time updates

Real-time updates allow you to update existing rows in a table, which are immediately reflected when querying.

### Use cases

* Update rows in a table and query their most recent state
* Update slowly changing dimensions
* Update entity tables (users, accounts, etc)

### How it works

Real-time updates are supported by ClickHouse's ReplacingMergeTree table engine. This table engine replaces rows that share the same sorting key, keeping only the most recently inserted row (or the row with the largest “ver” column value).

De-duplication happens at query time and asynchronously, through background merges. For those familiar with ClickHouse, Propel automatically includes the "FINAL" modifier on queries to ReplacingMergeTree tables.

However, it's important to note that there are two implications:

1. The row count in ReplacingMergeTree tables may be larger than the logical number of rows. This is because there may be duplicate rows in the table that haven't been merged yet. These rows will be de-duplicated at query time.
2. De-duplicating records at query time can have a performance impact on very large tables. This can be compensated for by using a large Propeller.

### Create a Data Pool to handle real-time updates

In this section, we will guide you through the process of creating a Data Pool that can handle real-time updates. We will provide step-by-step instructions on how to create it via the Console, API, and Terraform.

<Tabs>
  <Tab title="Console">
    To create a Data Pool that can handle real-time updates in Console, first go to the **"Data Pools"** section, then click **"Create Data Pool"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-01.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=cd545c27666d2fbc2746810c94bf8d68" alt="Propel's Data Pools Console page" width="1393" height="634" data-path="images/docs/2024-07-22-updating-data-01.png" />
    </Frame>

    Select the Data Pool type you need to create.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-02.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=80f4070ce08619699dc8dc076d4761a9" alt="Create new Data Pool" width="1396" height="726" data-path="images/docs/2024-07-22-updating-data-02.png" />
    </Frame>

    In the **"Table Settings"** step, select **"Mutable Data"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-03.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=0fa8b83a89fede6711870e2137cd6257" alt="ClickHouse table settings configuration" width="1466" height="373" data-path="images/docs/2024-07-22-updating-data-03.png" />
    </Frame>

    Answer the questions on how to uniquely identify records and your query patterns. For more information, read the "Mutable records" section of the [How to select a table engine and sorting key](https://www.propeldata.com/docs/guides/table-engine-and-sorting-key#mutable-records) guide.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-04.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=ee411e277d3f78522f40a6a41708d21c" alt="ClickHouse table settings configuration questions" width="1466" height="694" data-path="images/docs/2024-07-22-updating-data-04.png" />
    </Frame>

    Next, you will get the suggested table settings. Note that the table engine is ReplacingMergeTree, and the sorting key determines the uniqueness of the row.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-05.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=cb4330a36ab5c5c1dfb76e6ae76cc5a7" alt="Suggested ClickHouse table settings" width="1465" height="744" data-path="images/docs/2024-07-22-updating-data-05.png" />
    </Frame>

    Click **"Next"** to finish setting up the Data Pool. Once it is created, you can go to the **"Details"** tab and verify the settings.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-06.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=1cc44db00e8cb50cf931d2c1ae23b159" alt="Data Pool details" width="1072" height="502" data-path="images/docs/2024-07-22-updating-data-06.png" />
    </Frame>
  </Tab>

  <Tab title="API">
    The API request below creates a Data Pool for mutable records. It uses the ReplacingMergeTree table engine, the `timestamp` and `order_item_id` columns to sort and determine uniqueness.

    ```graphql theme={"system"}
    mutation {
      createDataPoolV2(input: {
        uniqueName: "Updatable TacoSoft Order Items"
        description: "Data Pool for updatable TacoSoft Order Items"
        timestamp: {
          columnName: "timestamp"
        }
        columns: [
          { columnName: "quantity", type: INT32, isNullable: false },
          { columnName: "taco_name", type: STRING, isNullable: false },
          { columnName: "sauce_name", type: STRING, isNullable: false },
          { columnName: "restaurant_id", type: STRING, isNullable: false },
          { columnName: "restaurant_name", type: STRING, isNullable: false },
          { columnName: "taco_total_price", type: FLOAT, isNullable: false },
          { columnName: "order_item_id", type: STRING, isNullable: false },
          { columnName: "tortilla_id", type: STRING, isNullable: false },
          { columnName: "toppings", type: JSON, isNullable: false },
          { columnName: "sauce_id", type: STRING, isNullable: false },
          { columnName: "taco_unit_price", type: FLOAT, isNullable: false },
          { columnName: "order_id", type: STRING, isNullable: false },
          { columnName: "order_item_generated_at", type: TIMESTAMP, isNullable: false },
          { columnName: "taco_id", type: STRING, isNullable: false },
          { columnName: "timestamp", type: TIMESTAMP, isNullable: false },
          { columnName: "tortilla_name", type: STRING, isNullable: false }
        ],
        tableSettings: {
          engine: {
            replacingMergeTree: {
              type: REPLACING_MERGE_TREE
            }
          }
          partitionBy: ["toYYYYMM(timestamp)"]
          orderBy: ["timestamp", "order_item_id"]
        }
      }) {
        dataPool {
          id
          uniqueName
          description
          tableSettings {
            orderBy
            partitionBy
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Terraform">
    The following Terraform creates a Data Pool for mutable records. It uses the ReplacingMergeTree table engine, the `timestamp`, and `order_item_id` columns to sort and determine uniqueness.

    ```hcl theme={"system"}
    resource "propel_data_pool" "updatable_tacosoft_order_items" {
      unique_name = "Updatable TacoSoft Order Items"
      description = "Data Pool for updatable TacoSoft Order Items"
      timestamp   = "timestamp"

      table_settings {
        engine {
          type = "REPLACING_MERGE_TREE"
        }
        partition_by = ["toYYYYMM(timestamp)"]
        order_by     = ["timestamp", "order_item_id"]
      }

      column {
        name     = "quantity"
        type     = "INT32"
        nullable = false
      }

      column {
        name     = "taco_name"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "sauce_name"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "restaurant_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "restaurant_name"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "taco_total_price"
        type     = "FLOAT"
        nullable = false
      }

      column {
        name     = "order_item_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "tortilla_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "toppings"
        type     = "JSON"
        nullable = false
      }

      column {
        name     = "sauce_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "taco_unit_price"
        type     = "FLOAT"
        nullable = false
      }

      column {
        name     = "order_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "order_item_generated_at"
        type     = "TIMESTAMP"
        nullable = false
      }

      column {
        name     = "taco_id"
        type     = "STRING"
        nullable = false
      }

      column {
        name     = "timestamp"
        type     = "TIMESTAMP"
        nullable = false
      }

      column {
        name     = "tortilla_name"
        type     = "STRING"
        nullable = false
      }
    }
    ```
  </Tab>
</Tabs>

***

### Create a Materialized View to handle real-time updates

In this section, we will guide you through the process of creating a Materialized View that can handle real-time updates. We will provide step-by-step instructions on how to create it via the Console, API, and Terraform.

<Tabs>
  <Tab title="Console">
    To create a Materialized View that can handle real-time updates in the Console, first go to the **"Materialized View"** section and click **"Create new Materialized View"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-07.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=00f85d238b983617cfb5ee2c951f53d5" alt="Create new Materialized View" width="1206" height="614" data-path="images/docs/2024-07-22-updating-data-07.png" />
    </Frame>

    Then, enter the query that defines the Materialized View. For this example, we are going to duplicate rows from the "TacoSoft Demo Data" table, so we just need to select all records.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-08.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=53d3424e2bc38aac0ed383136053136a" alt="Materialized View query" width="1119" height="777" data-path="images/docs/2024-07-22-updating-data-08.png" />
    </Frame>

    Select **"New Data Pool"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-09.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=cccc979705a4010ff7c188b0ee6e65ea" alt="Materialized View destination" width="1117" height="299" data-path="images/docs/2024-07-22-updating-data-09.png" />
    </Frame>

    Give your destination Data Pool a name and description.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-10.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=9e898567118ca755282f1c6fb22ba60a" alt="Materialized View destination configuration" width="1115" height="517" data-path="images/docs/2024-07-22-updating-data-10.png" />
    </Frame>

    Select **"Mutable records"**, then click **"Continue"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-11.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=3f652d1e27414a62b233e58d8598c418" alt="Materialized View destination configuration questions" width="1116" height="323" data-path="images/docs/2024-07-22-updating-data-11.png" />
    </Frame>

    Answer the questions on how to uniquely identify records and your query patterns. For more information, read the "Mutable records" section of the [How to select a table engine and sorting key](https://www.propeldata.com/docs/guides/table-engine-and-sorting-key#mutable-records) guide.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-12.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=9e26969395af7c8c9dfe739b0b1bffc4" alt="Suggested ClickHouse table settings" width="1119" height="775" data-path="images/docs/2024-07-22-updating-data-12.png" />
    </Frame>

    Next, you will get the suggested table settings. Note that the table engine is ReplacingMergeTree, and the sorting key determines the uniqueness of the row.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-13.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=738cc8137dfc9505c23fadbf67fdbc03" alt="Materialized View details" width="1115" height="747" data-path="images/docs/2024-07-22-updating-data-13.png" />
    </Frame>

    Click **"Continue"** to finish setting up the Materialized View.

    Once you complete setting up the Materialized View, you will have a Data Pool with the de-duplicated records.
  </Tab>

  <Tab title="API">
    ```graphql theme={"system"}
    mutation {
      createMaterializedView(input: {
        uniqueName: "TacoSoft Deduplication Materialized View",
        description: "Deduplicates TacoSoft records",
        sql: """
          SELECT
            *
          FROM "TacoSoft Demo Data"
        """,
        destination: {
          newDataPool: {
            timestamp: {
              columnName: "timestamp"
            },
            uniqueName: "TacoSoft unique order items",
            accessControlEnabled: true
            tableSettings: {
                    engine: {
                      replacingMergeTree: {
                        type: REPLACING_MERGE_TREE
                      }
                    }
                    orderBy: ["timestamp", "order_item_id"]
                  }
          }
        },
        backfillOptions: {
          backfill: true
        }
      }) {
        materializedView {
            id
          sql
          uniqueName
        }
      }
    }
    ```
  </Tab>

  <Tab title="Terraform">
    ```hcl theme={"system"}
    resource "propel_materialized_view" "tacosoft_deduplication_materialized_view" {
      unique_name = "TacoSoft Deduplication Materialized View"
      description = "Deduplicates TacoSoft records"

      sql = <<-SQL
      SELECT
        *
      FROM "TacoSoft Demo Data"
      SQL

      new_data_pool {
        unique_name            = "TacoSoft unique order items"
        timestamp              = "timestamp"
        access_control_enabled = true

        table_settings {
          engine {
              type = "REPLACING_MERGE_TREE"
          }
          order_by = ["timestamp", "order_item_id"]
        }
      }

      backfill = true
    }
    ```
  </Tab>
</Tabs>

***

## Batch updates

Batch updates are useful for one-off or automated jobs where you need to update many rows in a table. You can perform a batch update using the Console or the [`createUpdateDataPoolRecordsJob`](/docs/management-api/mutations#createupdatedatapoolrecordsjob) API.

### Use cases

* Backfill columns
* Update column(s) for all table rows
* Update column(s) for table rows matching a filter
* Redact columns on specific rows

### How it works

Batch updates in Propel are supported by table mutations in ClickHouse. When you perform a batch update, Propel issues an `ALTER TABLE … UPDATE` statement to ClickHouse and monitors its progress. The mutation proceeds part-by-part, partition-by-partition, updating rows in the table.

### Create a batch update job

There are two parts to the update job.

First, the list of filters that will be used for updating records. Records matching these filters will be updated.

Second, the columns and values to be updated. The value can be:

* **Other column names.** For example, `sauce_name`.
* **A string in single quotes.** For example, `'al pastor'`.
* **A number.** For example: `10`.
* **A JSON property.** For example: `order.id`.
* **An expression.** For example: `total_price * quantity` or `CONCAT(taco_name, '🌮')`.

<Note>
  To update a column to a string value, you must wrap the value in single quotes <code>'al pastor'</code> .
</Note>

<Tabs>
  <Tab title="Console">
    To initiate a batch update job in the Console, navigate to the Data Pool from which you need to update data, click on the **"Operations"** tab, and then click **"Update data"**.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-14.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=6949cddfd181349c29704c455fd93029" alt="Update data Console UI for ClickHouse" width="1262" height="267" data-path="images/docs/2024-07-22-updating-data-14.png" />
    </Frame>

    Here, you can specify the filters of the data to update and set the values to update.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-15.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=0377c7b22308644d20553e577a6b58c4" alt="Update data Console UI for ClickHouse" width="1201" height="910" data-path="images/docs/2024-07-22-updating-data-15.png" />
    </Frame>

    Lastly, you can see the progress of the updates job and when it is completed.

    <Frame>
      <img src="https://mintcdn.com/propeldocs/G8bGSg66eBpnYaHB/images/docs/2024-07-22-updating-data-16.png?fit=max&auto=format&n=G8bGSg66eBpnYaHB&q=85&s=5cd787670d0b8c45e9e8bfcd21004be6" alt="Update data Console UI for ClickHouse" width="1371" height="476" data-path="images/docs/2024-07-22-updating-data-16.png" />
    </Frame>
  </Tab>

  <Tab title="API">
    Here's an example of how to update data using the API ([read the docs for more details](/docs/management-api/mutations#createupdatedatapoolrecordsjob)):

    ```graphql theme={"system"}
    mutation {
      createUpdateDataPoolRecordsJob (
        input: {
          dataPool: "DPO00000000000000000000000000"
          filterSql: "restaurant_name='Farolito' AND taco_name='Veggie'"
          set: [
            { "column": "taco_name", "expression": "'Vegetarian'"}
          ]
        }
      ) {
        id
      }
    }
    ```
  </Tab>
</Tabs>

***

### Notes on updating non-nullable columns:

* Suppose we have a non-nullable column A and a nullable column B. We execute an update setting `A = B + 1`. The operation yields null if the job encounters a record where B is null. This will result in an error when attempting to assign it to A, since A cannot be null. Consequently, the job fails, and the remaining records remain unchanged, while the records processed before encountering the null value are updated.
* If the column being updated has a different data type than that of the update expression, the result will be null. This could cause a similar error as the previous example if the column is non-nullable.
