I Export PostgreSQL Queries to Google Sheets For Free. Here’s How
Tags: showdev, productivity, devops, postgres
Published: 2022-07-05 23:58:55

A couple of years ago, I wouldn’t have known that connecting a PostgreSQL database to Google Sheets could be this expensive.
Despite being a trivial problem, existing market solutions such as Zapier, KPIBees, etc. require us to pay a premium for it.

_TL;DR In this article, I am writing about how I was able to export PostgreSQL queries to Google Sheets via GitHub (and a little bit of Bash scripting)._

## Why Did I Need It

Here’s a little bit of context.

I maintain a tiny side project named [Fareview](https://github.com/ngshiheng/fareview) — a commercial beer price monitoring tool that scrapes commercial beer data from Singapore e-commerce sites and stores it in a PostgreSQL database.
The summary of the data gathered is then synced daily to [Google Sheets for users to view](https://docs.google.com/spreadsheets/d/1ImvPhsWp3mRF5lz7C55Ub2Z5okzitIvU6WG77YWL5PU/).

Instead of paying for a monthly premium, I’ve decided to use GitHub Action to help me with such a task for free.

## Here’s How It Works

This method should also work with any other SQL databases (e.g. MySQL) with a CLI like `psql`.

![A simple illustration of the approach drawn by me](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k9m0dv5zq7zso948bgcn.png)
 

1. Create a simple Bash script that uses Postgres client CLI (`psql`) to run the SQL queries and output them in CSV file format from our PostgreSQL database server
2. Set up a GitHub Actions workflow that runs the Bash script in Step 1 and commits the generated file into our repository on a Cron schedule
3. On Google Sheets, use the `=IMPORTDATA("<url-of-csv-file>")` function to import our CSV data from our repository to our Google Sheets

It is important to note that the `IMPORTDATA` function [updates data automatically at up to 1-hour intervals](https://support.google.com/area120-tables/answer/9904107?hl=en). In case you need a shorter interval use case, you may need to [work around it](https://gist.github.com/aGHz/6868a1ea1defbd6b9ed5#sheet-scripts).

## Here Are the Steps To Do It

### Bash Script

Depending on your use case, you may not even need a Bash script. For instance, you could just run the `psql` command as one of the steps within your GitHub Actions workflow.
Using a Bash script here provides more flexibility as you could also run this manually outside of GitHub Actions in case you need it.

This is the Bash script (`generate_csv.sh`) that I’m running:

```sh
#!/bin/bash
BRANDS=("carlsberg" "tiger" "heineken" "guinness" "asahi")

PGDATABASE="${PGDATABASE-fareview}"
PGHOST="${PGHOST-localhost}"
PGPASSWORD="${PGPASSWORD-}"
PGPORT="${PGPORT-5432}"
PGUSER="${PGUSER-postgres}"

mkdir -p data/
for brand in "${BRANDS[@]}"; do
    PGPASSWORD=$PGPASSWORD psql -v brand="'${brand}'" -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -F ',' -A --pset footer -f alembic/examples/get_all_by_brand.sql >"data/${brand}.csv"
done

```

-   A simple script uses the `psql` command to run SQL query from a `.sql` file with CSV table output mode (`-A` flag)
-   The output of this command is saved in a CSV file within the `data` directory of the Git repository
-   The script gets all the necessary database settings from the environment variables
-   In our GitHub Actions, we’re going to set these environment variables from our repository secrets (note: we’ll have to add these environment variables into our repository ourselves)

Here’s the [permalink](https://github.com/ngshiheng/fareview/blob/0bce19b570408f5f8af2a26ed940fe09b0c0bf6c/scripts/generate_csv.sh) to the Bash script.

### GitHub Actions Workflow

Why GitHub Actions?

GitHub Actions workflow supports running on a [Cron schedule](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule). Essentially, what this means is that we can schedule our job (i.e. our script in this case) to run as short as 5 minutes intervals.

In our use case, we can use this to export our PostgreSQL query to our Google Sheets daily.

Let’s start by creating a `generate_csv.yml` workflow file inside the `.github/workflows` folder of our project directory:

```yml
name: Generate CSV
on:
    workflow_dispatch:
    schedule:
        - cron: "30 23 * * *" # At 23:30 UTC daily
```

-   [`workflow_dispatch`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) is added so that we can manually trigger our workflow from GitHub API, CLI, or browser UI
-   Check out [Crontab Guru](https://crontab.guru/) for Cron schedule syntax

Next, to connect to any database, we’ll need to pass the connection settings:

```yml
name: Generate CSV
on:
    workflow_dispatch:
    schedule:
        - cron: "30 23 * * *" # <https://crontab.guru/#30_23_*_*_*>
env:
    PGDATABASE: ${{ secrets.PGDATABASE }}
    PGHOST: ${{ secrets.PGHOST }}
    PGPASSWORD: ${{ secrets.PGPASSWORD }}
    PGPORT: ${{ secrets.PGPORT }}
    PGUSER: ${{ secrets.PGUSER }}
```

Under the same GitHub project repository Secrets setting, enter the respective environment variables under Actions:

![At https://github.com/<username>/<project>/settings/secrets/actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c0gjp8nv2xy9or217zgw.png)
 

Finally, let's create the job for us to export our PostgreSQL queries and commit them to our repository

```yml
name: Generate CSV
on:
    workflow_dispatch:
    schedule:
        - cron: "30 23 * * *" # <https://crontab.guru/#30_23_*_*_*>
env:
    PGDATABASE: ${{ secrets.PGDATABASE }}
    PGHOST: ${{ secrets.PGHOST }}
    PGPASSWORD: ${{ secrets.PGPASSWORD }}
    PGPORT: ${{ secrets.PGPORT }}
    PGUSER: ${{ secrets.PGUSER }}
jobs:
    generate:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - name: Install PostgreSQL # Step 1
              run: |
                  sudo apt-get update
                  sudo apt-get install --yes postgresql
            - name: Generate CSV # Step 2
              run: scripts/generate_csv.sh
              shell: bash
            - name: Get current date # Step 3
              id: date
              run: echo "::set-output name=date::$(TZ=Asia/Singapore date +'%d %b %Y')"
            - name: Commit changes # Step 4
              if: ${{ success() }}
              uses: EndBug/add-and-commit@v9
              with:
                  pull: "--rebase --autostash ."
                  message: "chore(data): update generated csv automatically for ${{ steps.date.outputs.date }} data"
                  default_author: github_actions
```

A job in a GitHub Actions workflow can contain many steps. Different steps in GitHub Actions run in different containers as well.

1. The first step is to set up and install PostgreSQL (with `psql`) onto the step container
2. Next, we’ll add a step to run our [Bash script](https://github.com/ngshiheng/fareview/blob/0bce19b570408f5f8af2a26ed940fe09b0c0bf6c/scripts/generate_csv.sh) that runs an [SQL query from a file](https://github.com/ngshiheng/fareview/blob/0bce19b570408f5f8af2a26ed940fe09b0c0bf6c/alembic/examples/get_all_by_brand.sql)
3. Optional: Get the local date-time so that we can use it as part of our commit message in Step 4
4. Commit the generated CSV file from Step 2 into our repository. Here, I am using the [Add & Commit GitHub Actions](https://github.com/marketplace/actions/add-commit) to commit my CSV file changes.

Why use the `--autostash` flag with the `git pull`? This allows us to automatically stash and pop the pending CSV file changes before committing and pushing them to the repository. This helps us to work around Git commit issues whereby other developers could be pushing new code changes while this job runs.

That's it! we now have a Cron job that runs every day to update our CSV file for us so that [our Google Sheets](https://docs.google.com/spreadsheets/d/1ImvPhsWp3mRF5lz7C55Ub2Z5okzitIvU6WG77YWL5PU/) can import them.

![Git commit history of this job](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ilw7phpfmnv79bw3rc2.png)

## Closing Throughs

Having GitHub — a highly available service to host our CSV file for us feels great. What’s more, having GitHub hosting this for free almost feels like some sort of a cheat.

I have also used this similar approach to run a [scraper job to fetch Michelin Guide restaurants](https://github.com/ngshiheng/michelin-my-maps/blob/95d8a05e7c4393af6ed2f8e380c44ac7cc2e92ba/.github/workflows/scrape.yml) from the Michelin Guide website.

Alternatively, I have also considered using Google Sheets API to sync my data directly to Google Sheets. Given the integration efforts required, I’m glad that I stick to this very simple method.

Thanks for reading, and here's the final [link to the example project](https://github.com/ngshiheng/fareview).

---

_This article was originally published on [jerrynsh.com](https://jerrynsh.com/i-export-postgresql-queries-to-gsheets-for-free-heres-how/)_