Query SQL Server Data from YugabyteDB Using FDW (POC-Friendly)

Why this tip exists

Sometimes you just want to prove it’s possible:

  • ● “Can YugabyteDB read data from SQL Server?”

  • ● “Can I explore SQL Server tables from YSQL without exporting data?”

  • ● “Can I do a quick POC before committing to CDC / ETL / migration tooling?”

This tip shows a fast, local POC pattern using:

  • ● SQL Server (Docker)

  • ● PostgreSQL + tds_fdw as a lightweight bridge

  • ● YugabyteDB (YSQL) using built-in postgres_fdw

⚠️ Important framing

  • ● This is not a production ingestion pattern

  • ● FDWs are best for POCs, exploration, and validation

  • ● For scale & performance, move to CDC/ETL later

What we’re building (high level)
				
					YugabyteDB (YSQL)
   |
   | postgres_fdw
   v
PostgreSQL (bridge)
   |
   | tds_fdw (TDS protocol)
   v
SQL Server (Docker)
				
			
Prerequisites
  • ● AlmaLinux 9 VM

  • ● Docker installed and running

  • ● YugabyteDB running via yugabyted

Confirm YugabyteDB is up and running:

				
					yugabyted status >/dev/null && echo "✅ YugabyteDB is running"
				
			

Example:

				
					[root@localhost ~]# yugabyted start > start.log

[root@localhost ~]# yugabyted status >/dev/null && echo "✅ YugabyteDB is running"
✅ YugabyteDB is running
				
			
Step 1️⃣: Create a Docker network (quietly)
				
					docker network create fdw-net >/dev/null
echo "✅ Docker network fdw-net created"
				
			
Step 2️⃣: Start SQL Server (minimal output)
				
					docker run -d --name mssql \
  --network fdw-net \
  -e ACCEPT_EULA=Y \
  -e MSSQL_SA_PASSWORD=Str0ngP@ssw0rd! \
  -p 1433:1433 \
  mcr.microsoft.com/mssql/server:2022-latest >/dev/null

echo "✅ SQL Server container started"
				
			

Give it ~30 seconds to initialize.

Step 3️⃣: Create a dog-themed demo table in SQL Server 🐕
3a. Create database
				
					docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'Str0ngP@ssw0rd!' -C -b \
  -Q "IF DB_ID('dog_demo') IS NULL CREATE DATABASE dog_demo;"
echo "✅ SQL Server: database dog_demo created"
				
			
3b. Create table
				
					docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'Str0ngP@ssw0rd!' -C -b -d dog_demo \
  -Q "IF OBJECT_ID('dbo.dog_breeds','U') IS NOT NULL DROP TABLE dbo.dog_breeds;
      CREATE TABLE dbo.dog_breeds (
        breed NVARCHAR(50) NOT NULL,
        top_name NVARCHAR(50) NOT NULL,
        avg_weight_lbs INT NOT NULL
      );"
echo "✅ SQL Server: table dbo.dog_breeds created"
				
			
3c. Insert 5 rows
				
					docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'Str0ngP@ssw0rd!' -C -b -d dog_demo \
  -Q "INSERT INTO dbo.dog_breeds VALUES
      ('Labrador Retriever','Buddy',65),
      ('German Shepherd','Max',75),
      ('Golden Retriever','Charlie',70),
      ('French Bulldog','Luna',28),
      ('Beagle','Cooper',25);"
echo "✅ SQL Server: inserted 5 dog rows"
				
			
3d. Verify:
				
					docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -P 'Str0ngP@ssw0rd!' -C -b -d dog_demo \
  -h -1 -W -Q "SET NOCOUNT ON; SELECT 'rows_loaded=' + CAST(COUNT(*) AS varchar(10)) FROM dbo.dog_breeds;"
				
			

Example:

				
					[root@localhost ~]# docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'Str0ngP@ssw0rd!' -C -b -d dog_demo \
-h -1 -W -Q "SET NOCOUNT ON; SELECT 'rows_loaded=' + CAST(COUNT(*) AS varchar(10)) FROM dbo.dog_breeds;"
rows_loaded=5
				
			
Step 4️⃣: Start the PostgreSQL bridge container
				
					docker run -d --name pgbridge \
  --network fdw-net \
  -e POSTGRES_PASSWORD=postgres \
  -p 55432:5432 \
  postgres:16 >/dev/null

echo "✅ PostgreSQL bridge container started"
				
			
Step 5️⃣: Install tds_fdw inside the bridge (quiet build)
				
					docker exec pgbridge bash -lc "
apt-get update -qq &&
apt-get install -y -qq git build-essential postgresql-server-dev-16 freetds-dev &&
git clone -q https://github.com/tds-fdw/tds_fdw.git /tmp/tds_fdw &&
cd /tmp/tds_fdw &&
make USE_PGXS=1 >/dev/null &&
make USE_PGXS=1 install >/dev/null
"

docker exec pgbridge psql -U postgres -c "CREATE EXTENSION tds_fdw;" >/dev/null
echo "✅ tds_fdw installed in bridge Postgres"
				
			
Step 6️⃣: Expose SQL Server table in Postgres via tds_fdw
				
					docker exec pgbridge psql -U postgres -v ON_ERROR_STOP=1 -c "
CREATE SERVER mssql_srv
  FOREIGN DATA WRAPPER tds_fdw
  OPTIONS (servername 'mssql', port '1433', database 'dog_demo');

CREATE USER MAPPING FOR postgres
  SERVER mssql_srv
  OPTIONS (username 'sa', password 'Str0ngP@ssw0rd!');

CREATE FOREIGN TABLE public.mssql_dog_breeds (
  breed text,
  top_name text,
  avg_weight_lbs int
)
SERVER mssql_srv
OPTIONS (schema_name 'dbo', table_name 'dog_breeds');
" >/dev/null

echo "✅ SQL Server exposed via tds_fdw"
				
			

Quick check (optional):

				
					docker exec pgbridge psql -U postgres -c \
"SELECT * FROM public.mssql_dog_breeds ORDER BY avg_weight_lbs DESC;"
				
			

Example:

				
					[root@localhost ~]# docker exec pgbridge psql -U postgres -c \
"SELECT * FROM public.mssql_dog_breeds ORDER BY avg_weight_lbs DESC;"
NOTICE:  tds_fdw: Query executed correctly
NOTICE:  tds_fdw: Getting results
NOTICE:  tds_fdw: Query executed correctly
NOTICE:  tds_fdw: Getting results
       breed        | top_name | avg_weight_lbs
--------------------+----------+----------------
 German Shepherd    | Max      |             75
 Golden Retriever   | Charlie  |             70
 Labrador Retriever | Buddy    |             65
 French Bulldog     | Luna     |             28
 Beagle             | Cooper   |             25
(5 rows)
				
			
Step 7️⃣: Query SQL Server data from YugabyteDB 🎉

Connect to YSQL:

				
					ysqlsh -h 127.0.0.1 -p 5433 -U yugabyte -d yugabyte
				
			

Create FDW objects:

				
					CREATE EXTENSION IF NOT EXISTS postgres_fdw;

CREATE SERVER pgbridge_srv
  FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (host '127.0.0.1', port '55432', dbname 'postgres');

CREATE USER MAPPING FOR yugabyte
  SERVER pgbridge_srv
  OPTIONS (user 'postgres', password 'postgres');

CREATE FOREIGN TABLE sqlserver_dog_breeds (
  breed text,
  top_name text,
  avg_weight_lbs int
)
SERVER pgbridge_srv
OPTIONS (schema_name 'public', table_name 'mssql_dog_breeds');
				
			

Query SQL Server from YugabyteDB:

				
					SELECT breed, top_name
FROM sqlserver_dog_breeds
WHERE avg_weight_lbs >= 60
ORDER BY avg_weight_lbs DESC;
				
			

Example:

				
					yugabyte=# SELECT breed, top_name
yugabyte-# FROM sqlserver_dog_breeds
yugabyte-# WHERE avg_weight_lbs >= 60
yugabyte-# ORDER BY avg_weight_lbs DESC;
       breed        | top_name
--------------------+----------
 German Shepherd    | Max
 Golden Retriever   | Charlie
 Labrador Retriever | Buddy
(3 rows)
				
			
What this proves
  • ✔ YugabyteDB (YSQL) can query SQL Server data
    ✔ FDW is excellent for POCs and exploration
    ✔ You can validate schema + queries before migration
What this is not
  • ❌ Not high-throughput ingestion
    ❌ Not a replacement for CDC
    ❌ Not intended for latency-sensitive production paths
Cleanup (optional) (Be careful, one command below will Destroy YugabyteDB!)
				
					docker rm -f pgbridge mssql >/dev/null
docker network rm fdw-net >/dev/null
yugabyted destroy >/dev/null
echo "✅ Demo environment cleaned up"
				
			
Final takeaway 🐕

If you can query it, you can reason about it. FDWs give you a low-friction way to explore legacy systems from YugabyteDB. and decide when and how to migrate properly.

Have Fun!

Pretty sure Santa is missing one reindeer… and he’s snacking on our front lawn.