Azure SQL Database and Azure SQL Managed Instance run the same engine, so SPSync writes the same tables to either. What differs is the network path, and that is where most of the setup time goes. This walkthrough replicates Meridian Insurance Group’s Policies, Claims Register and Underwriting Referrals lists into Azure SQL, covers the firewall and VNet steps for each target, and ends with a claims leakage report in Power BI.
The underwriting team built its first report straight on the lists, and it struggled. Refresh took minutes, each policy came back as an id, and once a list passed 5,000 items a query that filters or sorts on a non-indexed column could fail. A Power Automate flow broke whenever the Policy lookup or the multi-value Affected Coverages column changed, and a Data Factory pipeline needed its mapping updated by hand. Each month the numbers ended up in a spreadsheet.
One engine, two ways in
SPSync reads the lists in full once, then follows them with delta queries and writes the changes to Azure SQL on a schedule. Power BI, Excel and any report writer read the database and never touch SharePoint. The schema is built from the lists: lookups and people become key columns and multi-value columns become child tables. Because Database and Managed Instance share the SQL Server engine over the wire, everything in this post applies to both, apart from how you provision and reach the server.
Signing in and opening the network path
Install the desktop app, or SPSync in your Azure for a tenant-wide setup, and sign in with
the account that already opens the three lists in SharePoint Online. The sign-in is
delegated, so SPSync sees what that account sees, and the permissions wizard sets out what
Sites.Selected grants before an admin approves it. SPSync never writes to SharePoint.
On the database side, SPSync uses SQL authentication: a username and password on an encrypted connection string. Microsoft Entra authentication is not supported, so create a SQL login on whichever target you use.
Azure SQL Database is a logical server with a public endpoint on port 1433, guarded by server-level firewall rules. Add the SPSync host to the IP allow-list, or use a private endpoint if the server is locked to a VNet. Managed Instance lives in a subnet of your own VNet and has no public endpoint unless you turn one on. SPSync needs a route into that VNet through peering, ExpressRoute or a VPN gateway, or you can enable the instance’s public endpoint, which listens on port 3342. Past that point the connection string, schema and sync behave identically. On the command line, sign-in is a device code and the token is kept in the OS keychain.
Policies, claims and coverages as tables
Every table is keyed on the SharePoint item id. The Policy lookup on Claims Register becomes
policy_id, a key column pointing at sp_policies. Affected Coverages becomes its own child
table, one row per coverage per claim, so a claim with three coverages is three rows and
can be counted. Claims handlers and underwriters resolve to one shared users table, which
makes “assigned to” and “handler” join the same way.
If a SharePoint column changes type, say a number becoming text, SPSync adds a column for the new type and leaves the old values alone. A column removed in SharePoint stays in the table, and a report built on it keeps working. SPSync does not add foreign key constraints, so load order cannot fail a sync. Add them yourself once you trust the schema.
Running the first sync
The first run is a full read; after that, each run reads the delta. When Graph asks clients to slow down, SPSync backs off, and the tenant’s other calls are unaffected. The events screen splits the totals by list, so an operator can see what ran without opening a log file.
Power BI with DirectQuery
Power BI connects to the replica with the SQL Server connector, using import for a scheduled model or DirectQuery for something close to live. DirectQuery pushes filtered SQL to Azure SQL, which the SharePoint List connector cannot do at any useful scale. The policy lookup is a column, so you define the relationship once in the model and the report joins claims to policies like any two tables. The leakage question, which line of business has the widest gap between reserve and payments, becomes a single grouped query.
Keeping the claims tables current
On a five-minute interval each run reads only the changes, so a report on the replica is a
few minutes behind SharePoint at most. A full reconcile, every seven days by default,
re-checks every item to confirm the two sides match; run one on demand as a QA check or to
catch up after your own server or Microsoft 365 has had an outage. The desktop app and
SPSync in your Azure have a schedule picker. From the command line, spsync run does one
run, and cron, a scheduled task or a CI pipeline sets the interval.
Claims that are edited or withdrawn
The next delta run picks up an edit. A claim deleted in SharePoint is soft-deleted: it stays in the table with a flag, so a report can include or exclude it, and the reporting view SPSync can generate for each table leaves it out.
The tabs on each step above show the desktop app, SPSync in your Azure and the command line doing the same work. To replicate these lists anywhere else as well, add a replication per destination; the destinations page has the full list, and if the one you need is missing, tell us.
Connect to SharePoint and sign in
What you see: A delegated sign-in to Meridian Insurance Group's site, followed by the one-page pack from the permissions wizard.
Why it matters: Policy and claims data moves under permissions the underwriting team already holds, which keeps the compliance review short.
What it replaces: A registered Azure app with broad Sites.Read.All permissions that an admin has to reason about tenant-wide.
Pick the lists and the Azure SQL destination
What you see: Policies, Claims Register and Underwriting Referrals ticked together, mapped onto a new Azure SQL schema with a preview of every column and type.
Why it matters: Test connection proves the firewall rule or VNet route works before the first run, which is where Azure SQL setups usually stall.
What it replaces: A hand-written Data Factory pipeline or a Power Automate flow per list, each one breaking separately when a column changes.
Tick Policies, Claims Register and Underwriting Referrals, then choose Azure SQL as the destination.
Name the server, the database and SQL authentication on an encrypted connection, then run Test connection to confirm it is reachable.
Check the schema preview: every column, its type, and which columns are lookups before anything runs.
Write the replication config for all three lists against Azure SQL in one file.
spsync export-config --output underwriting.yaml --pipeline-name underwriting --prefix sp --destination-kind mssql --dataset-name dbo --client-id $SPSYNC_CLIENT_ID --connection-string-env UNDERWRITING_DB_URL --site-url https://contoso.sharepoint.com/sites/meridian-insurance --lists-by-name "Policies,Claims Register,Underwriting Referrals"Check the Graph permissions and the Azure SQL connection before the first run.
spsync doctor underwriting.yaml
Run the first sync
What you see: 900 policies, 1,400 claims and 140 underwriting referrals written, broken out by list on the run's own events screen.
Why it matters: With the full read done, a new claim logged in SharePoint is in Azure SQL within one five-minute cycle.
What it replaces: A refresh that fails or times out against the SharePoint List connector directly.
Click Run now, open the run's own events and watch the per-list row counts arrive: 900 policies, 1,400 claims, 140 underwriting referrals.
Turn reporting views on from the run's own screen, then set the schedule to five minutes so the three-list replication checks for changes twelve times an hour.
Run the replication; the terminal shows the full load first, then delta counts on later runs.
spsync run underwriting.yaml
See the result: lookups, child tables and joins
What you see: sp_claims_register joined to sp_policies in a SQL query, and a Subrogation Status column added to sp_claims_register automatically after a change in SharePoint.
Why it matters: Claims leakage is a sum across claims grouped by policy, which Azure SQL computes in one statement.
What it replaces: An export to a spreadsheet every time claims leakage needs a join SharePoint cannot answer on its own.
Open the destination database and browse sp_policies, sp_claims_register and sp_underwriting_referrals; each keeps the SharePoint item id as its key.
Run a join between sp_claims_register and sp_policies on policy_id, something no SharePoint list view answers directly.
Query the Azure SQL database directly to confirm the join and the row counts.
sqlcmd -S your-server.database.windows.net -d underwriting -Q "select p.line_of_business, sum(c.estimated_reserve - c.paid_to_date) from dbo.sp_claims_register c join dbo.sp_policies p on c.policy_id = p.sp_item_id group by p.line_of_business"
Connect Power BI with DirectQuery
What you see: Power BI reading sp_claims_register straight from Azure SQL with DirectQuery, the policy lookup available as a column to join on.
Why it matters: DirectQuery sends filtered SQL to Azure SQL, so the report avoids per-item API calls and the 5,000-item view threshold.
What it replaces: A leakage report refreshed through the SharePoint List connector, which slows to a crawl as the claims list grows.
In Power BI Desktop, connect to the Azure SQL database and choose DirectQuery for sp_claims_register.
Build the claims leakage report against the policy lookup the same way you would against any other database table, and define the relationship once in the model.
Keep the replication current from a script or CI job, independent of when the report is opened.
spsync run underwriting.yaml