> ## Documentation Index
> Fetch the complete documentation index at: https://fruitstand.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Compare ETF and Mutual Fund Performance in Snowflake

> Rank ETFs and mutual funds side by side by trailing returns in Snowflake SQL, on a common as-of date, with results for 18 S&P 500, total-market and bond funds.

*By [Fruit Stand Team](https://fruitstand.dev/about.md) · November 10, 2026 · Guides*

To compare ETF and mutual fund performance in Snowflake, join a list of
tickers to their trailing returns on a date every fund has, then rank them
with `rank() over (partition by ...)`. Across seven S\&P 500 index funds, the
ETFs and mutual funds finished within 0.14 points a year of each other over
10 years, in roughly expense-ratio order.

Below is the query, results for 18 funds in three peer groups, and the two
details that make a side-by-side comparison misleading if you miss them.

> [!NOTE]
>
> Returns come from Fruit Stand's [Fund Returns listing on the Snowflake
> Marketplace](https://app.snowflake.com/marketplace/listing/GZTYZTG3LLO9), as of November 6, 2026, the latest date with a row for every
> fund below. Queries were run on November 10, 2026. Expense ratios are the
> published figures, checked across several fund comparison sites the same
> week. This isn't investment advice.

## The query

Don't have fund returns in Snowflake yet? [Fund and ETF Returns Data on
Snowflake, Compared](https://fruitstand.dev/blog/fund-etf-returns-comparison.md) covers where to get
them.

The Fund Returns listing has one row per fund per as-of date in
`ext_fund_trailing_returns_vw`, and the fund's name and type in
`ext_fund_vw`. List the funds you want with a peer group for each, then rank
within the group:

```sql theme={null}
with picks (peer_group, code) as (
    select * from values
        ('S&P 500', 'VOO'), ('S&P 500', 'IVV'), ('S&P 500', 'SPY'),
        ('S&P 500', 'VFIAX'), ('S&P 500', 'FXAIX'), ('S&P 500', 'SWPPX'), ('S&P 500', 'VFINX'),
        ('Total market', 'VTI'), ('Total market', 'ITOT'), ('Total market', 'SCHB'),
        ('Total market', 'VTSAX'), ('Total market', 'FSKAX'), ('Total market', 'SWTSX'), ('Total market', 'VTSMX'),
        ('US bonds', 'BND'), ('US bonds', 'AGG'), ('US bonds', 'VBTLX'), ('US bonds', 'FXNAX')
),
common_date as (
    -- latest date with a row for every pick, since mutual fund NAVs can land a day after ETF prices
    select max(as_of_date) as as_of_date
    from (
        select as_of_date
        from ext_fund_trailing_returns_vw
        where code in (select code from picks)
        group by as_of_date
        having count(distinct code) = (select count(*) from picks)
    )
)
select p.peer_group,
       r.code,
       f.name,
       iff(f.type = 'ETF', 'ETF', 'Mutual fund') as wrapper,
       r.as_of_date,
       round(r.return_1y * 100, 2) as return_1y_pct,
       round(r.return_5y * 100, 2) as return_5y_pct,
       round(r.return_10y * 100, 2) as return_10y_pct,
       rank() over (partition by p.peer_group order by r.return_10y desc) as rank_10y
from picks p
join ext_fund_vw f on f.code = p.code
join ext_fund_trailing_returns_vw r on r.code = p.code
join common_date d on r.as_of_date = d.as_of_date
order by p.peer_group, rank_10y;
```

Three parts do the work:

* **`picks`** keeps the comparison to funds that hold the same thing. Ranking
  an S\&P 500 fund against a bond fund tells you about the market, not the
  fund.
* **`common_date`** finds the latest as-of date that has a row for every
  fund. More on why below.
* **`rank() over (partition by p.peer_group ...)`** restarts the ranking in
  each group. Swap `return_10y` for `return_1y` or `return_5y` to rank on a
  different period.

`type` is `'ETF'` for ETFs; mutual funds appear as `'FUND'` or
`'Mutual Fund'`, so the query labels everything that isn't an ETF as a
mutual fund. Returns of 2 years and longer are annualized, and shorter ones
are cumulative.

## The results

### S\&P 500

| Rank | Fund | Wrapper | Expense ratio | 1 year | 5 years | 10 years |
| -: | - | - | -: | -: | -: | -: |
| 1 | Fidelity 500 Index (FXAIX) | Mutual fund | 0.015% | 11.04% | 11.06% | 15.37% |
| 2 | Vanguard S\&P 500 ETF (VOO) | ETF | 0.03% | 10.96% | 10.98% | 15.31% |
| 3 | Schwab S\&P 500 Index (SWPPX) | Mutual fund | 0.02% | 11.00% | 10.97% | 15.30% |
| 3 | iShares Core S\&P 500 ETF (IVV) | ETF | 0.03% | 10.97% | 10.97% | 15.30% |
| 5 | Vanguard 500 Index Admiral (VFIAX) | Mutual fund | 0.04% | 11.00% | 10.96% | 15.29% |
| 6 | Vanguard 500 Index Investor (VFINX) | Mutual fund | 0.14% | 10.89% | 10.85% | 15.23% |
| 6 | SPDR S\&P 500 ETF Trust (SPY) | ETF | 0.0945% | 10.88% | 10.90% | 15.23% |

The cheapest fund, a mutual fund, ranks first, and the two most expensive,
one ETF and one mutual fund, tie for last. VOO and VFIAX are two share
classes of the same Vanguard portfolio, so their 0.02-point gap over 10 years
is close to VFIAX's 0.01-point-higher fee.

Averaged by wrapper, the three ETFs returned 15.28% a year over 10 years and
the four mutual funds 15.30%. The wrapper made no measurable difference.

### Total US stock market

| Rank | Fund | Wrapper | 1 year | 5 years | 10 years |
| -: | - | - | -: | -: | -: |
| 1 | Vanguard Total Stock Market ETF (VTI) | ETF | 11.14% | 9.86% | 14.79% |
| 2 | Vanguard Total Stock Market Admiral (VTSAX) | Mutual fund | 11.16% | 9.85% | 14.77% |
| 2 | Schwab U.S. Broad Market ETF (SCHB) | ETF | 11.01% | 9.89% | 14.77% |
| 4 | Fidelity Total Market Index (FSKAX) | Mutual fund | 11.01% | 9.87% | 14.76% |
| 5 | iShares Core S\&P Total U.S. Stock Market ETF (ITOT) | ETF | 11.03% | 9.86% | 14.75% |
| 6 | Schwab Total Stock Market Index (SWTSX) | Mutual fund | 10.99% | 9.83% | 14.72% |
| 7 | Vanguard Total Stock Market Investor (VTSMX) | Mutual fund | 11.11% | 9.76% | 14.67% |

These funds track three families of total-market index (CRSP, S\&P and Dow
Jones), so some of the spread is the index rather than the fund. Even so, the top six sit within 0.07 points a year of each other.

### US investment-grade bonds

| Rank | Fund | Wrapper | 1 year | 5 years | 10 years |
| -: | - | - | -: | -: | -: |
| 1 | Vanguard Total Bond Market Admiral (VBTLX) | Mutual fund | 0.21% | −0.23% | 1.47% |
| 2 | iShares Core U.S. Aggregate Bond ETF (AGG) | ETF | 0.29% | −0.19% | 1.45% |
| 2 | Fidelity U.S. Bond Index (FXNAX) | Mutual fund | 0.17% | −0.23% | 1.45% |
| 4 | Vanguard Total Bond Market ETF (BND) | ETF | 0.23% | −0.26% | 1.44% |

Four funds, 0.03 points apart over 10 years. Bond index funds are even
closer than stock funds, because their fees are similar and small.

## Compare on a date every fund has

ETF prices are final at the market close. Mutual fund NAVs are calculated
after the close and often reach data providers the next morning, so the
latest as-of date can be missing some funds. On November 10, 2026, the
latest date in the listing, November 9, had no row yet for 352 active
mutual funds and 24 ETFs that had one for November 6, VBTLX among them.

Filtering on `max(as_of_date)` alone would drop VBTLX from the bond
comparison without an error. Comparing each fund's own latest row is worse:
November 9 was a 2.3% up day for the S\&P 500, so a fund still on November 6
would look about 2 points behind on its 1-week and 1-month returns. The `common_date` step
avoids both by using the latest date that has every fund.

## Single years can differ more than 10 years

Calendar-year returns are in `ext_fund_calendar_year_returns_vw`, one row
per fund per year. Pivot two funds side by side to see the gap each year:

```sql theme={null}
select calendar_year,
       round(max(iff(code = 'VOO', calendar_year_return, null)) * 100, 2) as voo_pct,
       round(max(iff(code = 'VFIAX', calendar_year_return, null)) * 100, 2) as vfiax_pct,
       round((max(iff(code = 'VOO', calendar_year_return, null))
            - max(iff(code = 'VFIAX', calendar_year_return, null))) * 100, 2) as gap_pct_points
from ext_fund_calendar_year_returns_vw
where code in ('VOO', 'VFIAX')
  and calendar_year between 2016 and 2025
group by calendar_year
order by calendar_year;
```

| Year | VOO (ETF) | VFIAX (mutual fund) | Gap |
| - | -: | -: | -: |
| 2016 | 12.17% | 11.94% | +0.23 |
| 2017 | 21.77% | 21.78% | −0.01 |
| 2018 | −4.50% | −4.45% | −0.05 |
| 2019 | 31.37% | 31.46% | −0.10 |
| 2020 | 18.32% | 18.32% | +0.01 |
| 2021 | 28.79% | 28.65% | +0.14 |
| 2022 | −18.17% | −18.16% | −0.01 |
| 2023 | 26.32% | 26.24% | +0.08 |
| 2024 | 24.98% | 24.97% | +0.01 |
| 2025 | 17.82% | 17.83% | −0.01 |

The same portfolio differed by as much as 0.23 points in a single year, but
the gaps run in both directions and cancel out: 0.02 points a year over 10
years. An ETF's return is measured on its market price, which can sit a
little above or below the value of its holdings at year-end, while a mutual
fund's is measured on its NAV. That's why a one-year comparison of an ETF
and a mutual fund on the same index can look like a real difference when it
isn't.

## What decides the ranking

* **Fees.** Within a peer group, the expense ratio is the most consistent
  driver. The 0.125-point fee gap between FXAIX and VFINX shows up as 0.14
  points a year over 10 years.
* **The index.** Total-market funds tracking different indexes can drift
  apart by a few hundredths of a point a year even at the same fee.
* **Fund structure, a little.** SPY is a unit investment trust that holds
  dividends in cash until it pays them out instead of reinvesting them,
  which costs it slightly more than its fee alone.
* **Not the wrapper.** In all three groups, ETFs and mutual funds are mixed
  together in the ranking.

Where ETFs do tend to win is outside returns: they have no minimum beyond one
share, trade during the day, and usually distribute fewer
capital gains in a taxable account. Trailing returns don't capture those
differences.

## Ranking against a benchmark

The query above ranks funds against each other. To measure each fund
against an index instead, see [How to Compare a Fund's Returns With Its
Benchmark in SQL](https://fruitstand.dev/blog/compare-fund-returns-benchmark-sql.md). For the
daily NAVs behind these returns, see [Where to Get Daily NAV History for US
Mutual Funds in SQL](https://fruitstand.dev/blog/mutual-fund-nav-history-sql.md).

## F.A.Q.

> [accordion] Do ETFs outperform mutual funds?
>
> Not because they're ETFs. For index funds tracking the same index, the ranking follows fees: in our S\&P 500 comparison the cheapest mutual fund ranked first and an ETF tied for last. Comparisons across all ETFs and all mutual funds mostly measure the mix of active and index funds in each group.

> [accordion] Is VOO or VFIAX better?
>
> Over 10 years to November 6, 2026, VOO returned 15.31% a year and VFIAX 15.29%. They're share classes of the same Vanguard fund, so the difference is VOO's 0.03% expense ratio against VFIAX's 0.04%. The choice is usually about how you buy: VFIAX has a \$3,000 minimum at Vanguard, VOO trades like a stock.

> [accordion] Why don't ETF and mutual fund returns match on the same day?
>
> Usually because they're on different dates: mutual fund NAVs often arrive a day after ETF prices. Compare on the latest date both have. Over a single year, ETF market prices can also differ slightly from the NAV of their holdings.

> [accordion] Can I rank every fund in a category?
>
> Yes, if you have a list of the funds in it. Replace the `picks` values with your list, or select codes from `ext_fund_vw` by name, and keep peer groups narrow so the ranking compares like with like.

> [accordion] Are the returns total returns?
>
> Yes. Fund Returns calculates every return from adjusted closing prices, which include distributions and splits.

To rank any of about 32,000 US mutual funds and ETFs side by side, start a
free 30-day trial of the [Fund Returns listing on the Snowflake
Marketplace](https://app.snowflake.com/marketplace/listing/GZTYZTG3LLO9).
The schema is in the [dataset docs](https://fruitstand.dev/datasets/fund-returns.md). Questions?
Reach out at [contact@fruitstand.dev](mailto:contact@fruitstand.dev).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.