If I was requested to write a query, that required 14 tables, in the query I would break the query into probably 3 ctes. I learned from a Microsoft sql book ( There used to be a Wikipedia page that described this as well and even had a table) that ... the number of possible query execution plans exponentially increases with the number of tables in the join (other other things being equal. indexes etc). And if there are too many possible execution plans, the query engine will stop looking at possible execution plans, take the best and just execute. So if you have too many tables you may not get the best plan.
It's correct that if you have many tables in a query, there are chances that the optimizer will not get it 100% perfect, because the estimation errors can multiply rapidly. That is, it is not only a matter of trying different order, it also a matter of getting the estimates correct.
However, the CTEs you are planning will not have any impact of this. A CTE is just a local macro, and the algebrizer will expand the CTE into the query before the optimizer sees it. And the CTE is a logical construct. The optimizer may recast the computation order, and never compute the CTE as such.
What sometimes is a good idea is to materialise intermediate results in a temp table. Since a temp table has statistics, this can help the optimizer to estimate the next step more accurately. But obviously, materialising the data into a temp table will also add overhead. And if you make an incorrect choice, you can degrade performance quite a bit.
This is a demo query from one of my presentations:
WITH OrderAmounts AS (
SELECT OD.OrderID, SUM(OD.Quantity * OD.UnitPrice) AS Amount
FROM dbo.[Order Details] OD
GROUP BY OD.OrderID
)
SELECT O.OrderID, C.CustomerID, C.CustomerName, C.City, OA.Amount
FROM dbo.Orders O
JOIN dbo.Customers C ON O.CustomerID = C.CustomerID
JOIN OrderAmounts OA ON O.OrderID = OA.OrderID
WHERE O.OrderDate = '19970421'
ORDER BY O.OrderID;
The database has one million orders and the CTE says "Compute the sum of all orders". Yet this query returns instantly, because the optimizer realises that it only has to compute the sum for the orders on the given date.
Next part of my demo goes like this:
DROP TABLE IF EXISTS #OrderAmounts;
CREATE TABLE #OrderAmounts (OrderID int NOT NULL PRIMARY KEY,
Amount decimal(20,2) NOT NULL);
INSERT #OrderAmounts (OrderID, Amount)
SELECT OD.OrderID, SUM(OD.Quantity * OD.UnitPrice) AS Amount
FROM dbo.[Order Details] OD
GROUP BY OD.OrderID;
SELECT O.OrderID, C.CustomerID, C.CustomerName, C.City, OA.Amount
FROM dbo.Orders O
JOIN dbo.Customers C ON O.CustomerID = C.CustomerID
JOIN #OrderAmounts OA ON O.OrderID = OA.OrderID
WHERE O.OrderDate = '19970421'
ORDER BY O.OrderID;
This runs in five seconds on my demo machine, because this time SQL Server are very clearly instructed to compute the sum of all orders.
There is a second aspect when you have a complex query: it may also help you to get the query right by saving data into intermediate tables that permits you inspect the data to verify that your logic is correct.