Considere o exemplo a seguir, onde temos três tabelas relacionadas. Pedidos, Usuários e Detalhes do Pedido. OrderDetails está vinculado a chaves estrangeiras na tabela Orders e na tabela Users. Essa é essencialmente uma configuração muito típica para bancos de dados relacionais; indiscutivelmente todo o propósito de um DBMS relacional .
USE tempdb;
IF OBJECT_ID(N'dbo.OrderDetails', N'U') IS NOT NULL
DROP TABLE dbo.OrderDetails;
IF OBJECT_ID(N'dbo.Orders', N'U') IS NOT NULL
DROP TABLE dbo.Orders;
IF OBJECT_ID(N'dbo.Users', N'U') IS NOT NULL
DROP TABLE dbo.Users;
CREATE TABLE dbo.Orders
(
OrderID int NOT NULL
CONSTRAINT OrderTestPK
PRIMARY KEY
CLUSTERED
, SomeOrderData varchar(1000)
CONSTRAINT Orders_somedata_df
DEFAULT (CRYPT_GEN_RANDOM(1000))
);
CREATE TABLE dbo.Users
(
UserID int NOT NULL
CONSTRAINT UsersPK
PRIMARY KEY
CLUSTERED
, SomeUserData varchar(1000)
CONSTRAINT Users_somedata_df
DEFAULT (CRYPT_GEN_RANDOM(1000))
);
CREATE TABLE dbo.OrderDetails
(
OrderDetailsID int NOT NULL
CONSTRAINT OrderDetailsTestPK
PRIMARY KEY
CLUSTERED
, OrderID int NOT NULL
CONSTRAINT OrderDetailsOrderID
FOREIGN KEY
REFERENCES dbo.Orders(OrderID)
, UserID int NOT NULL
CONSTRAINT OrderDetailsUserID
FOREIGN KEY
REFERENCES dbo.Users(UserID)
, SomeOrderDetailsData varchar(1000)
CONSTRAINT OrderDetails_somedata_df
DEFAULT (CRYPT_GEN_RANDOM(1000))
);
INSERT INTO dbo.Orders (OrderID)
SELECT TOP(100) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.syscolumns sc;
INSERT INTO dbo.Users (UserID)
SELECT TOP(100) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.syscolumns sc;
INSERT INTO dbo.OrderDetails (OrderDetailsID, OrderID, UserID)
SELECT TOP(10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, o.OrderID
, u.UserID
FROM sys.syscolumns sc
CROSS JOIN dbo.Orders o
CROSS JOIN dbo.Users u
ORDER BY NEWID();
CREATE INDEX OrderDetailsOrderID ON dbo.OrderDetails(OrderID);
CREATE INDEX OrderDetailsUserID ON dbo.OrderDetails(UserID);
Aqui, estamos consultando a tabela OrderDetails em que o UserID é 15:
SELECT od.OrderDetailsID
, o.OrderID
, u.UserID
FROM dbo.OrderDetails od
INNER JOIN dbo.Users u ON u.UserID = od.UserID
INNER JOIN dbo.Orders o ON od.OrderID = o.OrderID
WHERE u.UserID = 15
A saída da consulta é semelhante a:
╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
00 2200115 ║ 2 ║ 15 ║
30 630215 ║ 3 ║ 15 ║
║ 1990215 ║ 3 ║ 15 ║
60 4960215 ║ 3 ║ 15 ║
7 100715 ║ 8 ║ 15 ║
30 3930815 ║ 9 ║ 15 ║
║ 6310815 ║ 9 ║ 15 ║
║ 4441015 ║ 11 ║ 15 ║
7 2171315 ║ 14 ║ 15 ║
3 3431415 ║ 15 ║ 15 ║
║ 4571415 ║ 15 ║ 15 ║
2 6421515 ║ 16 ║ 15 ║
7 2271715 ║ 18 ║ 15 ║
║ 2601715 ║ 18 ║ 15 ║
║ 3521715 ║ 18 ║ 15 ║
18 221815 ║ 19 ║ 15 ║
║ 3381915 ║ 20 ║ 15 ║
║ 4471915 ║ 20 ║ 15 ║
╚════════════════╩═════════╩════════╝
Como você pode ver, a ordem das linhas de saída não corresponde à ordem das linhas na tabela OrderDetails.
Adicionar um explícito ORDER BY
garante que as linhas sejam retornadas ao cliente na ordem desejada:
SELECT od.OrderDetailsID
, o.OrderID
, u.UserID
FROM dbo.OrderDetails od
INNER JOIN dbo.Users u ON u.UserID = od.UserID
INNER JOIN dbo.Orders o ON od.OrderID = o.OrderID
WHERE u.UserID = 15
ORDER BY od.OrderDetailsID;
╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
15 3915 ║ 40 ║ 15 ║
7 100715 ║ 8 ║ 15 ║
18 221815 ║ 19 ║ 15 ║
99 299915 ║ 100 ║ 15 ║
82 368215 ║ 83 ║ 15 ║
38 603815 ║ 39 ║ 15 ║
30 630215 ║ 3 ║ 15 ║
28 728515 ║ 86 ║ 15 ║
22 972215 ║ 23 ║ 15 ║
201 992015 ║ 21 ║ 15 ║
17 1017115 ║ 72 ║ 15 ║
13 1113815 ║ 39 ║ 15 ║
╚════════════════╩═════════╩════════╝
Se a ordem de linhas é imperativo, e seus engenheiros sabem que a ordem é imperativo, eles devem sempre apenas quer usar um ORDER BY
comunicado, uma vez que pode custar-lhes a sua designação, se houve uma falha relacionada à ordem incorreta.
Um segundo exemplo, talvez mais instrutivo, usando a OrderDetails
tabela acima, em que não estamos juntando nenhuma outra tabela, mas com um requisito simples de encontrar linhas que correspondam ao Código do Pedido e ao Código do Usuário, vemos o problema.
Criaremos um índice para dar suporte à consulta, como você provavelmente faria na vida real se o desempenho for de alguma forma importante (quando não é?).
CREATE INDEX OrderDetailsOrderIDUserID ON dbo.OrderDetails(OrderID, UserID);
Aqui está a consulta:
SELECT od.OrderDetailsID
FROM dbo.OrderDetails od
WHERE od.OrderID = 15
AND (od.UserID = 21 OR od.UserID = 22)
E os resultados:
╔════════════════╗
║ OrderDetailsID ║
╠════════════════╣
4 21421 ║
║ 5061421 ║
║ 7091421 ║
14 691422 ║
14 3471422 ║
║ 7241422 ║
╚════════════════╝
A adição de uma ORDER BY
cláusula garantirá definitivamente que também obtemos a classificação correta aqui.
Esses modelos são apenas exemplos simples, onde não é garantido que as linhas estejam "em ordem" sem uma ORDER BY
declaração explícita . Existem muitos outros exemplos como esse e, como o código do mecanismo DBMS muda com bastante frequência, o comportamento específico pode mudar com o tempo.