# SQL Server
> Use the SQL Server replication guide when you need to read tables or queries from SQL Server and write the results to MotherDuck. The guide covers Python, pyodbc, SQL Server authentication, and loading dataframe results into MotherDuck.
## How it works with MotherDuck

1. Connect to SQL Server with the Microsoft ODBC driver and `pyodbc`.
2. Read a SQL Server table or query result into a dataframe.
3. Connect to MotherDuck from Python and persist the dataframe as a MotherDuck table.

## Load on a schedule with a Flight

A [Flight](/concepts/flights) is Python that MotherDuck schedules and runs next to your data. Use one instead of running the copy by hand when the load should repeat on a cron, retry on a transient failure, and keep a run history.

There is no DuckDB `sqlserver` extension, so a Flight runs the same `pyodbc` read as the replication guide and registers the result on an `md:` connection:

```python
import os

import duckdb
import pandas as pd
import pyodbc

def main():
    connection_string = (
        "DRIVER={ODBC Driver 17 for SQL Server};"
        f"SERVER={os.environ['SQLSERVER_HOST']},1433;"
        f"DATABASE={os.environ['SQLSERVER_DATABASE']};"
        f"UID={os.environ['SQLSERVER_USER']};"
        f"PWD={os.environ['SQLSERVER_PASSWORD']};"
    )

    connection = pyodbc.connect(connection_string)
    try:
        cursor = connection.cursor()
        cursor.execute("SELECT * FROM Production.BillOfMaterials")
        columns = [column[0] for column in cursor.description]
        bom = pd.DataFrame.from_records(cursor.fetchall(), columns=columns)
    finally:
        connection.close()

    md = duckdb.connect("md:")
    md.register("bom", bom)
    md.execute("CREATE OR REPLACE TABLE my_db.main.bill_of_materials AS SELECT * FROM bom")

if __name__ == "__main__":
    main()
```

The SQL Server password is a credential, so keep it in a [Flight secret](/sql-reference/motherduck-sql-reference/create-secret#flight-secrets) rather than in the Flight's source or config. Each `PARAMS` key is injected into the run as an environment variable, which is what the code above reads:

```sql
CREATE SECRET sqlserver_creds IN MOTHERDUCK (
    TYPE FLIGHTS,
    PARAMS MAP {
        'SQLSERVER_HOST': '<host>',
        'SQLSERVER_DATABASE': '<database>',
        'SQLSERVER_USER': '<user>',
        'SQLSERVER_PASSWORD': '<password>'
    }
);
```

Create the Flight with [`MD_CREATE_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-create-flight), passing the Python above as `source_code`, `pyodbc` and `pandas` in `requirements_txt`, `flight_secret_names := ['sqlserver_creds']`, and a `schedule_cron` for the cadence you want.

:::note
`pyodbc` needs the Microsoft ODBC driver, which is not preinstalled in the Flight runtime. Install it at the start of the run with `subprocess` and `apt-get`, as described in [Beyond Python](/concepts/flights#beyond-python).
:::

## Related content

- [Replicating SQL Server tables to MotherDuck](/key-tasks/data-warehousing/replication/sql-server)
- [Loading data into MotherDuck](/key-tasks/loading-data-into-motherduck/)
- [MotherDuck authentication](/key-tasks/authenticating-and-connecting-to-motherduck/authenticating-to-motherduck)


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

URL-encode query parameter values and send a GET request:

```text
GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fintegrations%2Fdatabases%2Fsql-server%2F&page_title=SQL%20Server&text=<url-encoded user feedback, max 2000 characters>
```

Optionally append `&source=<url-encoded interface identifier>` such as `claude.ai` or `chatgpt`.

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed query parameters, and `429` when rate-limited.
