Discover how Snowflake User-Defined Table Functions can bring data extraction into your dbt data pipelines. The article shows how to pull Salesforce data directly into Snowflake and materialize it in dbt models without a separate ingestion tool.
Adding the E to dbt: Extracting Source Systems With dbt Core and Snowflake
Mathias Heinze
Mathias Heinze
Principal Consultant
Mathias is a consultant with many years of experience in Data Vault modeling, DWH architecture and DWH automation. He advises customers from various industries, such as insurance companies, on these topics. His project experience covers the complete value chain from analysis, planning, design and implementation with focus on Snowflake and Oracle technologies. He also has profound knowledge in Domain-Specific-Languages (DSL) for code generation, Big Data and Cloud technologies as well as agile project approaches.
Discover how Snowflake User-Defined Table Functions can bring data extraction into your dbt data pipelines. The article shows how to pull Salesforce data directly into Snowflake and materialize it in dbt models without a separate ingestion tool.
Table of Contents
dbt Excels at Transformation, but Extraction Remains Separate
dbt (data build tool) has become a widely used standard for transformations in modern data engineering. It enables engineers to build modular, version-controlled, and tested SQL pipelines, applying established software engineering practices to data work.
However, dbt is designed for the T in traditional ETL or ELT pipelines. It does not inherently cover extraction or loading.
In a conventional ELT setup, dedicated tools such as Fivetran, Airbyte, or custom ingestion scripts retrieve data from databases, SaaS applications, and APIs. While effective, this often adds operational overhead: more tools to operate, more infrastructure to manage, more context switching, and potentially higher costs.
But what if extraction could happen directly in dbt? Rather than running a separate orchestration layer, maintaining a VM, or licensing an additional connector, teams could build SQL-native pipelines that run from the source system through to the data mart.
With Snowflake, this approach is possible through a concise integration pattern.
Snowflake User-Defined Table Functions for Data Extraction
The central capability is that Snowflake supports Python-based User-Defined Table Functions (UDTFs) through Snowpark. These functions can run Python code within Snowflake and return the result as a standard SQL table. Because dbt Core is available natively in Snowflake, there is no requirement to host or deploy dbt separately.
This makes it possible to:
Write a Python function that connects to an external source system
Register the function as a Snowflake table function
Invoke the function from a Snowflake-hosted dbt model using standard SQL
For dbt, this looks like an ordinary table query. In the background, Snowflake connects to the source system, retrieves the requested data, and returns it as rows and columns within one SQL statement.
This creates an end-to-end ELT data pipeline in dbt without introducing a separate technology layer.
Sample Implementation
The following scripts provide an example implementation for Salesforce.
Create a Snowpark Python Table Function
USE DATABASE DB_MAHT_DBT;
USE SCHEMA UTIL;
-- create secrets
CREATE OR REPLACE SECRET DB_MAHT_DBT.UTIL.SALESFORCE_CREDS
TYPE = GENERIC_STRING
SECRET_STRING = '{"username": "<username>",
"password": "<password>",
"security_token": "<security-token>",
"instance_url": "<instance-url>"}';
-- create network rule
CREATE OR REPLACE NETWORK RULE DB_MAHT_DBT.UTIL.SALESFORCE_NETWORK_RULE
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = (
'login.salesforce.com',
'<instance-url>' );
-- create external access integration
CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION SALESFORCE_ACCESS_INTEGRATION
ALLOWED_NETWORK_RULES = (DB_MAHT_DBT.UTIL.SALESFORCE_NETWORK_RULE)
ALLOWED_AUTHENTICATION_SECRETS = (DB_MAHT_DBT.UTIL.SALESFORCE_CREDS)
ENABLED = TRUE;
-- create UDTF
CREATE OR REPLACE FUNCTION DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE(TARGET_TABLE STRING, FIELD_LIST STRING DEFAULT '', WHERE_CLAUSE STRING DEFAULT '', SOQL_QUERY STRING DEFAULT '')
RETURNS TABLE ("DATA" OBJECT)
LANGUAGE PYTHON
RUNTIME_VERSION = '3.11'ARTIFACT_REPOSITORY = snowflake.snowpark.pypi_shared_repository
PACKAGES = ('simple-salesforce', 'snowflake-snowpark-python')
EXTERNAL_ACCESS_INTEGRATIONS = (SALESFORCE_ACCESS_INTEGRATION)
SECRETS = ('sf_creds' = DB_MAHT_DBT.UTIL.SALESFORCE_CREDS)
HANDLER = 'pythonDataReader'AS
$$
import _snowflake
import json
from simple_salesforce import Salesforce
classpythonDataReader:defprocess(self, target_table, field_list, where_clause, soql_query): creds = json.loads(_snowflake.get_generic_secret_string('sf_creds'))
sf = Salesforce(
username=creds['username'],
password=creds['password'],
security_token=creds['security_token'],
instance_url=creds['instance_url']
)
if soql_query and soql_query.strip():
query = soql_query.strip()
result = sf.query_all(query)
records = result['records']
if records:
fields = [k for k in records[0].keys() if k != 'attributes']
else:
fields = []
else:
sf_object = getattr(sf, target_table)
if field_list and field_list.strip():
fields = [f.strip() for f in field_list.split(',')]
else:
desc = sf_object.describe()
fields = [f['name'] for f in desc['fields']]
query = "SELECT " + ", ".join(fields) + " FROM " + target_table
if where_clause and where_clause.strip():
query += " WHERE " + where_clause
result = sf.query_all(query)
records = result['records']
for r in records:
row = {f: str(r.get(f)) if r.get(f) isnotNoneelseNonefor f in fields}
yield (row,)
$$;
Example UDTF Calls
-- select all columns:
SELECT DATA FROM TABLE(DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE('Account'));
-- select specific columns:
SELECT DATA FROM TABLE(DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE('Account', 'Id, Name, Industry, BillingCity'));
-- using a filter:
SELECT DATA FROM TABLE(DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE('Account', 'Id, Name, Industry', 'Industry = \'Electronics\''));
-- using custom SOQL query:
SELECT DATA FROM TABLE(DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE('a', 'b', 'c', 'SELECT Id, Name FROM Account WHERE Id = \'001g500000KiFO3AAN\''));
In this example, the UDTF returns a VARIANT column, Snowflake’s flexible semi-structured data type for JSON-like objects. Subsequent models can extract individual values with Snowflake’s colon notation, for example data:Id::STRING.
Beispiel-Ausgabe der UDTF
Using the Snowpark UDTF in a dbt Model
This is where the components connect. A dbt model can call the UDTF in the same way it would call any other SQL table function, while dbt manages the model execution. The extracted records are materialized as a Snowflake table and can feed directly into the existing transformation pipeline.
{{ config(
materialized='table') }}
SELECT
data::variant AS data,
'{{ run_started_at }}'::timestamp(0) AS prj$load_dts
FROM TABLE(DB_MAHT_DBT.UTIL.FCT_LOAD_SALESFORCE_TABLE('Account'))
Conclusion: A Complete ELT Pipeline Within dbt
dbt was created for data transformation, and it provides a strong foundation for that task. Combined with Snowflake Snowpark User-Defined Table Functions, it can also serve as the entry point for extraction.
This pattern allows teams to build an ELT pipeline from the source system to a consumption-ready data mart within a dbt project and Snowflake environment. It avoids the need for an additional extraction tool or separate infrastructure.
Python’s broad ecosystem of libraries can make the approach applicable and scalable to many types of source systems, including:
Relational databases such as PostgreSQL, Oracle, and SQL Server
SaaS platforms such as Salesforce, HubSpot, and Jira
File and collaboration systems such as SharePoint
REST APIs
For teams that already use dbt and Snowflake, this pattern is worth evaluating before adding another extraction tool to the technology stack.
If you’re also facing data integration challenges and want to create real added value for your company, we’re happy to support you. Contact us for an initial, non-binding consultation to discuss your specific use case.
Want To Learn More? Contact Us!
Your contact person
Helene Fuchs
Domain Lead Data Platform & Data Management
Your contact person
Pia Ehrnlechner
Domain Lead Data Platform & Data Management
Who is b.telligent?
b.telligent – that’s Data Analytics, AI, Customer Engagement, and Data Visualization. It’s Germany, Austria, Switzerland, and Romania. But most importantly, it’s our team: people with a true passion for data, working together to create innovative solutions that drive sustainable progress for businesses.
We'll show you how to get to actionable insights and decisions faster with Snowflake Intelligence. And: How to realize use cases and increase the ROI of your data platform: with little effort and without technical know-how.
Snowflake’s Openflow makes data integration faster, simpler, and more efficient. In this article, we’ll show how these benefits play out in practice—using a real-world example to highlight strategies for handling large volumes of small incoming files with ease and performance.
With Openflow, Snowflake fundamentally simplifies data integration: extraction and loading happen directly as part of the Snowflake platform — no external ETL tools required. This significantly reduces integration effort and streamlines the entire pipeline management process.