Snowflake

This guide walks you through connecting Snowflake to Solid so you can get insights into your data lineage, usage, and quality.

Overview

Solid requires access to:

  • The schema of your Snowflake datasets
  • Query logs from the Query History table
  • [Optional] Read-only grants for data profiling

Note: You will need ACCOUNTADMIN role to create the Solid role and user.

You have two options for connecting Snowflake to Solid:

OptionBest ForWhat You'll Do
Automatic Pull (Recommended)Ongoing use and continuous monitoringSet up a service user that runs automatically
Manual PullQuick proof of concept or one-time setupExport data manually and upload it

Option 1: Automatic Pull (Recommended)

Best for: Production use with continuous monitoring and updates.

What This Does

This script creates a dedicated service user that Solid uses to automatically pull metadata from your Snowflake account. The script is modular, allowing you to choose whether to include read-only grants based on your use case.

The warehouse is configured to:

  • Use XSMALL size (minimal cost)
  • Auto-suspend after 5 minutes of inactivity
  • Auto-resume when needed

Prerequisites

  • You need ACCOUNTADMIN role to run this script
  • Replace <your_database> with your actual database name

Configuration Variables

The script defines several variables to customize the setup:

VariableDefaultDescription
solid_usernameSOLID_DATAThe name for the new Snowflake user
solid_warehouse_sizeXSMALLThe compute size for the new virtual warehouse
solid_warehouse_nameSOLID_DATA_WHThe name for the new virtual warehouse
solid_role_nameSOLID_DATA_ROLEThe name for the new Snowflake role
database_to_monitor(must be replaced)The database you want Solid to monitor

Setup Script

-- ==========================================
-- 1. CONFIGURATION & VARIABLES
-- ==========================================
set solid_username='SOLID_DATA';
set solid_warehouse_size='XSMALL';
set solid_warehouse_name='SOLID_DATA_WH';
set solid_role_name='SOLID_DATA_ROLE';
set database_to_monitor=''; -- REPLACE THIS WITH YOUR DATABASE NAME

USE ROLE ACCOUNTADMIN;

-- ==========================================
-- 2. CORE SETUP: Role, User, and Warehouse
-- ==========================================
-- Create virtual warehouse (Auto-suspends after 5 min to optimize cost)
CREATE WAREHOUSE IF NOT EXISTS identifier($solid_warehouse_name) 
    WAREHOUSE_SIZE=$solid_warehouse_size 
    INITIALLY_SUSPENDED=TRUE
    AUTO_SUSPEND = 5 
    AUTO_RESUME = TRUE;

-- Create the custom role for Solid
CREATE ROLE IF NOT EXISTS identifier($solid_role_name);

-- Create the Service User and assign the role
CREATE USER IF NOT EXISTS identifier($solid_username) 
    DEFAULT_ROLE=$solid_role_name 
    TYPE=SERVICE;

GRANT ROLE identifier($solid_role_name) TO USER identifier($solid_username);

-- ==========================================
-- 3. GRANT REQUIRED PERMISSIONS
-- ==========================================
-- Warehouse Permissions
-- OPERATE: Allows starting, stopping, and resizing the warehouse
-- USAGE: Allows using the warehouse to execute queries
-- MONITOR: Allows viewing warehouse status, usage statistics, and query history
GRANT OPERATE, USAGE, MONITOR ON WAREHOUSE identifier($solid_warehouse_name) 
TO ROLE identifier($solid_role_name);

-- Allow access to internal query history and metadata
GRANT IMPORTED PRIVILEGES ON DATABASE "SNOWFLAKE" 
TO ROLE identifier($solid_role_name);

-- Grant access to the monitored database
GRANT USAGE, MONITOR ON DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT USAGE, MONITOR ON ALL SCHEMAS IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT USAGE, MONITOR ON FUTURE SCHEMAS IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

-- ==========================================
-- 4. REFERENCE GRANTS PROCEDURE
-- ==========================================
-- Logic to ensure lineage visibility even if schema-level future grants exist
-- REFERENCES privilege is vital for foreign key constraints and metadata lookups
-- but does not allow data access
USE DATABASE identifier($database_to_monitor);

CREATE OR REPLACE PROCEDURE GRANT_REFERENCES_TO_SOLID()
    RETURNS VARCHAR LANGUAGE javascript EXECUTE AS CALLER
AS
$$
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
    var schemas_to_grant = snowflake.createStatement({ sqlText:`select * from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
    var granted_schemas = "";
    while(schemas_to_grant.next()) {
      table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT MONITOR ON ALL DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT MONITOR ON FUTURE DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      granted_schemas += table_schema + "; "
    }
    return `Granted references for schemas ${granted_schemas}`;
}
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT MONITOR ON ALL DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT MONITOR ON FUTURE DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
return `Granted references for database`;
$$;

CALL GRANT_REFERENCES_TO_SOLID();

-- ==========================================
-- 5. SEMANTIC VIEW GRANTS (REQUIRED)
-- ==========================================
-- Allows Solid to create semantic views in the monitored database
-- This grant is required for Solid to function

-- Option A: Grant on all current and future schemas in the database (recommended)
GRANT CREATE SEMANTIC VIEW ON FUTURE SCHEMAS IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

-- Option B: Grant on a specific schema only (if you prefer not to grant on all schemas)
-- Replace  with the specific schema name
-- GRANT CREATE SEMANTIC VIEW ON SCHEMA identifier($database_to_monitor).
-- TO ROLE identifier($solid_role_name);

-- ==========================================
-- 6. [OPTIONAL] SOLID READ ONLY GRANTS
-- ==========================================
-- Only run this section if you want Solid to perform direct data profiling/quality checks
-- This gives SOLID_DATA_ROLE read-only access to the data within the monitored database

GRANT SELECT ON ALL TABLES IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT SELECT ON ALL VIEWS IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT SELECT ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT SELECT ON ALL STREAMS IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

GRANT SELECT ON ALL DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) 
TO ROLE identifier($solid_role_name);

CREATE OR REPLACE PROCEDURE GRANT_SELECT_FUTURES_TO_SOLID()
    RETURNS VARCHAR LANGUAGE javascript EXECUTE AS CALLER
AS
$$
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
    var schemas_to_grant = snowflake.createStatement({ sqlText:`select SCHEMA_NAME from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
    while(schemas_to_grant.next()) {
      table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE DYNAMIC TABLES IN SCHEMA ${table_schema} TO ROLE identifier($solid_role_name)`}).execute();
    }
    return `Granted future select for schemas`;
}
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE DYNAMIC TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($solid_role_name)`}).execute();
return `Granted future select for database`;
$$;

CALL GRANT_SELECT_FUTURES_TO_SOLID();

How to Use the Script

  1. Replace Placeholder — Before running, replace <your_database> with the actual name of the Snowflake database you want Solid to monitor.
  2. Execute in Snowflake — Run the script in your Snowflake worksheet or client.
  3. Conditional Execution:
    • To monitor metadata and schema changes only (lineage, schema drift detection) without reading actual data, run sections 1–5 (stop before Section 6: SOLID READ ONLY GRANTS).
    • To also enable data profiling and quality checks, run the full script including Section 6.

Note on Semantic View Grants (Section 5): This grant is required for Solid to function. Option A (recommended) grants permission across all current and future schemas in the database. Option B allows you to restrict the grant to a specific schema if your security policies require more granular control.


Option 2: Manual Pull (Quick Start)

Best for: Testing Solid or if you can't grant direct database access yet.

Steps

  1. Run the queries below in your Snowflake Query Editor
  2. Export each result as a CSV file
  3. Compress the files (ZIP or GZIP)
  4. Upload to the Solid Azure Storage container

Metadata Collection

Columns

For each database, run the following query. You can choose the relevant schemas or remove the filter in the last line:

SELECT
    *,
    t1.COMMENT AS COLUMN_COMMENT,
    t2.COMMENT AS TABLE_COMMENT
FROM
    {DB}.INFORMATION_SCHEMA.COLUMNS t1
LEFT JOIN {DB}.INFORMATION_SCHEMA.TABLES t2 ON
    t1.TABLE_CATALOG = t2.TABLE_CATALOG
    AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA
    AND t1.TABLE_NAME = t2.TABLE_NAME
WHERE
    t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA'
    AND t1.TABLE_CATALOG ILIKE '{DB}'
    AND t1.TABLE_SCHEMA IN ({relevant schemas});

Example:

SELECT
    *,
    t1.COMMENT AS COLUMN_COMMENT,
    t2.COMMENT AS TABLE_COMMENT
FROM
    ACME_DEMO.INFORMATION_SCHEMA.COLUMNS t1
LEFT JOIN ACME_DEMO.INFORMATION_SCHEMA.TABLES t2 ON
    t1.TABLE_CATALOG = t2.TABLE_CATALOG
    AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA
    AND t1.TABLE_NAME = t2.TABLE_NAME
WHERE
    t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA'
    AND t1.TABLE_CATALOG ILIKE 'ACME_DEMO'
    AND t1.TABLE_SCHEMA IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW');

Primary Keys, Foreign Keys, and Unique Constraints

For each database and schema, run these queries:

SHOW PRIMARY KEYS IN SCHEMA {db}.{schema};
SHOW IMPORTED KEYS IN SCHEMA {db}.{schema};

Example:

SHOW PRIMARY KEYS IN SCHEMA ACME_DEMO.INTERMEDIATE;
SHOW IMPORTED KEYS IN SCHEMA ACME_DEMO.INTERMEDIATE;

Query History Collection

Queries with Database Context

For each database, run:

SELECT
    QUERY_ID,
    QUERY_TEXT,
    DATABASE_NAME,
    SCHEMA_NAME,
    QUERY_TYPE,
    USER_NAME,
    ROLE_NAME,
    WAREHOUSE_NAME,
    EXECUTION_STATUS,
    START_TIME,
    END_TIME,
    TOTAL_ELAPSED_TIME,
    QUERY_HASH,
    QUERY_PARAMETERIZED_HASH,
    USER_TYPE,
    USER_DATABASE_NAME,
    USER_SCHEMA_NAME,
    ROWS_WRITTEN_TO_RESULT
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    QUERY_TYPE = 'SELECT'
    AND QUERY_TAG != 'SolidData'
    AND EXECUTION_STATUS = 'SUCCESS'
    AND END_TIME > '{today - 90 days}'
    AND DATABASE_NAME ILIKE '{DB}'
    AND ROWS_WRITTEN_TO_RESULT > 0
    AND SCHEMA_NAME IN ({relevant schemas})
ORDER BY
    END_TIME DESC
LIMIT 1000000;

Example:

SELECT
    QUERY_ID,
    QUERY_TEXT,
    DATABASE_NAME,
    SCHEMA_NAME,
    QUERY_TYPE,
    USER_NAME,
    ROLE_NAME,
    WAREHOUSE_NAME,
    EXECUTION_STATUS,
    START_TIME,
    END_TIME,
    TOTAL_ELAPSED_TIME,
    QUERY_HASH,
    QUERY_PARAMETERIZED_HASH,
    USER_TYPE,
    USER_DATABASE_NAME,
    USER_SCHEMA_NAME,
    ROWS_WRITTEN_TO_RESULT
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    QUERY_TYPE = 'SELECT'
    AND QUERY_TAG != 'SolidData'
    AND EXECUTION_STATUS = 'SUCCESS'
    AND END_TIME > '2025-09-07 18:49:47 +0000'
    AND DATABASE_NAME ILIKE 'ACME_DEMO'
    AND ROWS_WRITTEN_TO_RESULT > 0
    AND SCHEMA_NAME IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW')
ORDER BY
    END_TIME DESC
LIMIT 1000000;

Queries without Database Context

Run this query to capture queries that don't reference a specific database:

SELECT
    QUERY_ID,
    QUERY_TEXT,
    DATABASE_NAME,
    SCHEMA_NAME,
    QUERY_TYPE,
    USER_NAME,
    ROLE_NAME,
    WAREHOUSE_NAME,
    EXECUTION_STATUS,
    START_TIME,
    END_TIME,
    TOTAL_ELAPSED_TIME,
    QUERY_HASH,
    QUERY_PARAMETERIZED_HASH,
    USER_TYPE,
    USER_DATABASE_NAME,
    USER_SCHEMA_NAME,
    ROWS_WRITTEN_TO_RESULT
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    QUERY_TYPE = 'SELECT'
    AND EXECUTION_STATUS = 'SUCCESS'
    AND END_TIME > '{today - 90 days}'
    AND DATABASE_NAME IS NULL
    AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY
    END_TIME DESC
LIMIT 1000000;

Example:

SELECT
    QUERY_ID,
    QUERY_TEXT,
    DATABASE_NAME,
    SCHEMA_NAME,
    QUERY_TYPE,
    USER_NAME,
    ROLE_NAME,
    WAREHOUSE_NAME,
    EXECUTION_STATUS,
    START_TIME,
    END_TIME,
    TOTAL_ELAPSED_TIME,
    QUERY_HASH,
    QUERY_PARAMETERIZED_HASH,
    USER_TYPE,
    USER_DATABASE_NAME,
    USER_SCHEMA_NAME,
    ROWS_WRITTEN_TO_RESULT
FROM
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE
    QUERY_TYPE = 'SELECT'
    AND EXECUTION_STATUS = 'SUCCESS'
    AND END_TIME > '2025-09-07 18:49:47 +0000'
    AND DATABASE_NAME IS NULL
    AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY
    END_TIME DESC
LIMIT 1000000;

Users Collection

SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.USERS;

Table Usage Collection

For each database, run:

SELECT
    DATABASE_NAME,
    SCHEMA_NAME,
    TABLE_NAME,
    DATE(START_TIME) AS DAY,
    SUM(ROWS_ADDED) AS total_rows_added,
    SUM(ROWS_REMOVED) AS total_rows_removed,
    SUM(ROWS_UPDATED) AS total_rows_updated,
    (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count
FROM
    SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY
WHERE
    START_TIME >= '{today - 90 days}'
    AND END_TIME <= 'today'
    AND DATABASE_NAME = '{db}'
    AND SCHEMA_NAME IN ({relevant schemas})
GROUP BY
    DATABASE_NAME,
    SCHEMA_NAME,
    TABLE_NAME,
    DATE(START_TIME)
HAVING
    (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0
ORDER BY
    DAY ASC;

Example:

SELECT
    DATABASE_NAME,
    SCHEMA_NAME,
    TABLE_NAME,
    DATE(START_TIME) AS DAY,
    SUM(ROWS_ADDED) AS total_rows_added,
    SUM(ROWS_REMOVED) AS total_rows_removed,
    SUM(ROWS_UPDATED) AS total_rows_updated,
    (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count
FROM
    SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY
WHERE
    START_TIME >= '2025-09-07 00:00:00.000'
    AND END_TIME <= '2025-10-12 00:00:00.000'
    AND DATABASE_NAME = 'ACME_DEMO'
    AND SCHEMA_NAME IN ('MARTS', 'INTERMEDIATE', 'STAGING', 'RAW')
GROUP BY
    DATABASE_NAME,
    SCHEMA_NAME,
    TABLE_NAME,
    DATE(START_TIME)
HAVING
    (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0
ORDER BY
    DAY ASC;

What Data Does Solid Collect?

For transparency, here are the specific queries Solid executes when automatically collecting data.

Metadata

SELECT 
    *, 
    t1.COMMENT AS COLUMN_COMMENT, 
    t2.COMMENT AS TABLE_COMMENT 
FROM 
    {database}.INFORMATION_SCHEMA.COLUMNS t1 
LEFT JOIN {database}.INFORMATION_SCHEMA.TABLES t2 
    ON t1.TABLE_CATALOG = t2.TABLE_CATALOG 
    AND t1.TABLE_SCHEMA = t2.TABLE_SCHEMA 
    AND t1.TABLE_NAME = t2.TABLE_NAME 
WHERE 
    t1.TABLE_SCHEMA != 'INFORMATION_SCHEMA' 
    AND t1.TABLE_CATALOG ILIKE '{database}'
    AND t1.TABLE_SCHEMA IN ({schemas});

Query History

Queries with database context:

SELECT 
    QUERY_ID, QUERY_TEXT, DATABASE_NAME, SCHEMA_NAME, QUERY_TYPE, 
    USER_NAME, ROLE_NAME, WAREHOUSE_NAME, EXECUTION_STATUS, 
    START_TIME, END_TIME, TOTAL_ELAPSED_TIME, QUERY_HASH, 
    QUERY_PARAMETERIZED_HASH, USER_TYPE, USER_DATABASE_NAME,
    USER_SCHEMA_NAME, ROWS_WRITTEN_TO_RESULT 
FROM 
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY 
WHERE 
    QUERY_TYPE='SELECT' 
    AND QUERY_TAG!='SolidData' 
    AND EXECUTION_STATUS='SUCCESS' 
    AND END_TIME>'{last_collect_time}' 
    AND DATABASE_NAME ILIKE '{database}' 
    AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY END_TIME DESC 
LIMIT 1000000;

Queries without database context:

SELECT 
    QUERY_ID, QUERY_TEXT, DATABASE_NAME, SCHEMA_NAME, QUERY_TYPE, 
    USER_NAME, ROLE_NAME, WAREHOUSE_NAME, EXECUTION_STATUS, 
    START_TIME, END_TIME, TOTAL_ELAPSED_TIME, QUERY_HASH, 
    QUERY_PARAMETERIZED_HASH, USER_TYPE, USER_DATABASE_NAME,
    USER_SCHEMA_NAME, ROWS_WRITTEN_TO_RESULT 
FROM 
    SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY 
WHERE 
    QUERY_TYPE='SELECT' 
    AND EXECUTION_STATUS='SUCCESS' 
    AND END_TIME>'{last_collect_time}' 
    AND DATABASE_NAME IS NULL 
    AND ROWS_WRITTEN_TO_RESULT > 0
ORDER BY END_TIME DESC 
LIMIT 1000000;

Table Usage

SELECT 
    DATABASE_NAME, SCHEMA_NAME, TABLE_NAME, 
    DATE(START_TIME) AS day, 
    SUM(ROWS_ADDED) AS total_rows_added, 
    SUM(ROWS_REMOVED) AS total_rows_removed, 
    SUM(ROWS_UPDATED) AS total_rows_updated, 
    (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) AS total_count 
FROM 
    SNOWFLAKE.ACCOUNT_USAGE.TABLE_DML_HISTORY 
WHERE 
    START_TIME >= '{start_day}' 
    AND END_TIME <= '{end_day}' 
    AND DATABASE_NAME = '{database}' 
GROUP BY DATABASE_NAME, SCHEMA_NAME, TABLE_NAME, DATE(START_TIME) 
HAVING (SUM(ROWS_ADDED) + SUM(ROWS_REMOVED) + SUM(ROWS_UPDATED)) > 0 
ORDER BY day ASC;

Primary and Foreign Keys

SHOW PRIMARY KEYS IN SCHEMA {database}.{schema};
SHOW IMPORTED KEYS IN SCHEMA {database}.{schema};

Users

SELECT * FROM SNOWFLAKE.ACCOUNT_USAGE.USERS;

Column Data Profiling (Optional — Requires Read Access)

This only applies if you enabled Section 6 (Read-Only Grants) in the setup script.

Solid samples up to 100,000 rows per table to profile your data:

Column TypeMetrics Collected
NumericMIN, MAX, AVG, row count, distinct count, null count
TextTop 100 distinct values (LISTAGG), row count, distinct count, null count
DateMIN and MAX values, row count, distinct count, null count

Connecting in Solid

Step 1: Navigate to Integrations

Go to Settings → Integrations → Snowflake

Step 2: Enter Connection Details

FieldWhat to Enter
UserSOLID_DATA (or your service user name)
Password / Private KeyYour authentication credential
AccountYour Snowflake account identifier
WarehouseSOLID_DATA_WH (or your warehouse name)

Step 3: Test the Connection

Click Test Connection. If it fails:

  • Double-check your credentials
  • Make sure the warehouse is running
  • Confirm the role has USAGE rights on the database

Step 4: Select What to Monitor

Choose which databases and schemas you want Solid to analyze.

Step 5: Save

Click Save and Solid will start analyzing your data.


Troubleshooting

Connection Issues

  1. Verify you replaced <your_database> with your actual database name in the script
  2. Confirm you have ACCOUNTADMIN role when running the setup script
  3. Check the warehouse is running in Snowflake
  4. Verify all grants completed successfully by reviewing the script output

Permission Issues

  1. Check the role permissions by running SHOW GRANTS TO ROLE SOLID_DATA_ROLE;
  2. Verify the database name matches what you specified in the setup
  3. Ensure future grants are in place for new tables and views

Performance Concerns

The Solid warehouse uses XSMALL size and auto-suspends after 5 minutes of inactivity. You can adjust these settings by modifying the warehouse configuration in the setup script.


Security Notes

  • The service user created has read-only access to metadata and query history
  • Optional data profiling requires explicit SELECT grants (Section 6)
  • All credentials should be stored securely
  • The service user does not have permissions to modify data or schema
  • The REFERENCES privilege allows foreign key constraints and metadata lookups but does not allow data access