Cross join yields execution plan that reads one of the tables multiple times
I have a query where I need to cross join a small table with 5 rows to a table with roughly 31k rows. The execution plan SQL Server came up with does index scans in both tables and joins them with Nested Loops (INNER JOIN). However, when checking the execution plan, I noticed that the scan on the smaller table produced 5 x 31k = 155k rows for the following step. Does that mean SQL Server is scanning the index of the smaller table 31k times?
Here is a small reproduction of the problem I'm having:
CREATE TABLE A (id INT PRIMARY KEY);
CREATE TABLE B (id INT PRIMARY KEY);
INSERT INTO A(id)
VALUES (1), (2), (3), (4), (5);
INSERT INTO B(id)
VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)
SELECT * FROM A CROSS JOIN B
Here is the query live statistics of the SELECT command

My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
sql-server
add a comment |
I have a query where I need to cross join a small table with 5 rows to a table with roughly 31k rows. The execution plan SQL Server came up with does index scans in both tables and joins them with Nested Loops (INNER JOIN). However, when checking the execution plan, I noticed that the scan on the smaller table produced 5 x 31k = 155k rows for the following step. Does that mean SQL Server is scanning the index of the smaller table 31k times?
Here is a small reproduction of the problem I'm having:
CREATE TABLE A (id INT PRIMARY KEY);
CREATE TABLE B (id INT PRIMARY KEY);
INSERT INTO A(id)
VALUES (1), (2), (3), (4), (5);
INSERT INTO B(id)
VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)
SELECT * FROM A CROSS JOIN B
Here is the query live statistics of the SELECT command

My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
sql-server
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago
add a comment |
I have a query where I need to cross join a small table with 5 rows to a table with roughly 31k rows. The execution plan SQL Server came up with does index scans in both tables and joins them with Nested Loops (INNER JOIN). However, when checking the execution plan, I noticed that the scan on the smaller table produced 5 x 31k = 155k rows for the following step. Does that mean SQL Server is scanning the index of the smaller table 31k times?
Here is a small reproduction of the problem I'm having:
CREATE TABLE A (id INT PRIMARY KEY);
CREATE TABLE B (id INT PRIMARY KEY);
INSERT INTO A(id)
VALUES (1), (2), (3), (4), (5);
INSERT INTO B(id)
VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)
SELECT * FROM A CROSS JOIN B
Here is the query live statistics of the SELECT command

My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
sql-server
I have a query where I need to cross join a small table with 5 rows to a table with roughly 31k rows. The execution plan SQL Server came up with does index scans in both tables and joins them with Nested Loops (INNER JOIN). However, when checking the execution plan, I noticed that the scan on the smaller table produced 5 x 31k = 155k rows for the following step. Does that mean SQL Server is scanning the index of the smaller table 31k times?
Here is a small reproduction of the problem I'm having:
CREATE TABLE A (id INT PRIMARY KEY);
CREATE TABLE B (id INT PRIMARY KEY);
INSERT INTO A(id)
VALUES (1), (2), (3), (4), (5);
INSERT INTO B(id)
VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)
SELECT * FROM A CROSS JOIN B
Here is the query live statistics of the SELECT command

My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
sql-server
sql-server
edited 6 hours ago
Erik Darling
21.5k1264104
21.5k1264104
asked 7 hours ago
Luís Gabriel de AndradeLuís Gabriel de Andrade
1026
1026
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago
add a comment |
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago
add a comment |
2 Answers
2
active
oldest
votes
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
Yes, for every row that comes out of A, you need to produce all of the rows for B. Since there are five rows in A, you make five trips to B.
Cross Joins can only be implemented with Nested Loops, and that's normal behavior for it.
add a comment |
Yes, a nested loop join will run the bottom (inner) input for each row in the outer input.

Using your same examples data you can change the query to be a full outer join without a join condition and (on my machine) the result is a plan with a merge join.
SELECT * FROM A full outer JOIN B
on 1=1

1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "182"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e) {
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom)) {
StackExchange.using('gps', function() { StackExchange.gps.track('embedded_signup_form.view', { location: 'question_page' }); });
$window.unbind('scroll', onScroll);
}
};
$window.on('scroll', onScroll);
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f229070%2fcross-join-yields-execution-plan-that-reads-one-of-the-tables-multiple-times%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
Yes, for every row that comes out of A, you need to produce all of the rows for B. Since there are five rows in A, you make five trips to B.
Cross Joins can only be implemented with Nested Loops, and that's normal behavior for it.
add a comment |
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
Yes, for every row that comes out of A, you need to produce all of the rows for B. Since there are five rows in A, you make five trips to B.
Cross Joins can only be implemented with Nested Loops, and that's normal behavior for it.
add a comment |
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
Yes, for every row that comes out of A, you need to produce all of the rows for B. Since there are five rows in A, you make five trips to B.
Cross Joins can only be implemented with Nested Loops, and that's normal behavior for it.
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
Yes, for every row that comes out of A, you need to produce all of the rows for B. Since there are five rows in A, you make five trips to B.
Cross Joins can only be implemented with Nested Loops, and that's normal behavior for it.
edited 6 hours ago
jadarnel27
4,96411736
4,96411736
answered 7 hours ago
Erik DarlingErik Darling
21.5k1264104
21.5k1264104
add a comment |
add a comment |
Yes, a nested loop join will run the bottom (inner) input for each row in the outer input.

Using your same examples data you can change the query to be a full outer join without a join condition and (on my machine) the result is a plan with a merge join.
SELECT * FROM A full outer JOIN B
on 1=1

1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
add a comment |
Yes, a nested loop join will run the bottom (inner) input for each row in the outer input.

Using your same examples data you can change the query to be a full outer join without a join condition and (on my machine) the result is a plan with a merge join.
SELECT * FROM A full outer JOIN B
on 1=1

1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
add a comment |
Yes, a nested loop join will run the bottom (inner) input for each row in the outer input.

Using your same examples data you can change the query to be a full outer join without a join condition and (on my machine) the result is a plan with a merge join.
SELECT * FROM A full outer JOIN B
on 1=1

Yes, a nested loop join will run the bottom (inner) input for each row in the outer input.

Using your same examples data you can change the query to be a full outer join without a join condition and (on my machine) the result is a plan with a merge join.
SELECT * FROM A full outer JOIN B
on 1=1

answered 7 hours ago
Bob KlimesBob Klimes
2,1401022
2,1401022
1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
add a comment |
1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
1
1
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
It is dubious that the merge join is an improvement though. It will be a many to many merge that continually replays the rows cached in a work table.
– Martin Smith
7 hours ago
add a comment |
Thanks for contributing an answer to Database Administrators Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e) {
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom)) {
StackExchange.using('gps', function() { StackExchange.gps.track('embedded_signup_form.view', { location: 'question_page' }); });
$window.unbind('scroll', onScroll);
}
};
$window.on('scroll', onScroll);
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fdba.stackexchange.com%2fquestions%2f229070%2fcross-join-yields-execution-plan-that-reads-one-of-the-tables-multiple-times%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e) {
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom)) {
StackExchange.using('gps', function() { StackExchange.gps.track('embedded_signup_form.view', { location: 'question_page' }); });
$window.unbind('scroll', onScroll);
}
};
$window.on('scroll', onScroll);
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e) {
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom)) {
StackExchange.using('gps', function() { StackExchange.gps.track('embedded_signup_form.view', { location: 'question_page' }); });
$window.unbind('scroll', onScroll);
}
};
$window.on('scroll', onScroll);
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
var $window = $(window),
onScroll = function(e) {
var $elem = $('.new-login-left'),
docViewTop = $window.scrollTop(),
docViewBottom = docViewTop + $window.height(),
elemTop = $elem.offset().top,
elemBottom = elemTop + $elem.height();
if ((docViewTop elemBottom)) {
StackExchange.using('gps', function() { StackExchange.gps.track('embedded_signup_form.view', { location: 'question_page' }); });
$window.unbind('scroll', onScroll);
}
};
$window.on('scroll', onScroll);
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
My question is whether the index scan on the smaller table is performing multiple I/O operations, one for each row on the bigger table, as opposed to doing it only once and having its result being fed to the following step only once.
– Luís Gabriel de Andrade
7 hours ago