Рассмотрим следующий пример, где у нас есть три связанных таблицы. Заказы, пользователи и детали заказа. OrderDetails связан с внешними ключами для таблицы Orders и таблицы Users. По сути, это очень типичная установка для реляционных баз данных; возможно вся цель реляционной СУБД.
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);
Здесь мы запрашиваем таблицу OrderDetails, где 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
Вывод запроса выглядит следующим образом:
╔════════════════╦═════════╦════════╗
║ OrderDetailsID ║ OrderID ║ UserID ║
╠════════════════╬═════════╬════════╣
║ 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 ║
41 4441015 ║ 11 ║ 15 ║
║ 2171315 ║ 14 ║ 15 ║
31 3431415 ║ 15 ║ 15 ║
7 4571415 ║ 15 ║ 15 ║
2 6421515 ║ 16 ║ 15 ║
║ 2271715 ║ 18 ║ 15 ║
01 2601715 ║ 18 ║ 15 ║
21 3521715 ║ 18 ║ 15 ║
18 221815 ║ 19 ║ 15 ║
║ 3381915 ║ 20 ║ 15 ║
7 4471915 ║ 20 ║ 15 ║
╚════════════════╩═════════╩════════╝
Как видите, порядок вывода строк не соответствует порядку строк в таблице OrderDetails.
Добавление явного ORDER BY
гарантирует, что строки будут возвращены клиенту в желаемом порядке:
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 ║
7 1017115 ║ 72 ║ 15 ║
13 1113815 ║ 39 ║ 15 ║
╚════════════════╩═════════╩════════╝
Если порядок строк является обязательным, и ваши инженеры знают, что порядок является обязательным, им следует только захотеть использовать ORDER BY
оператор, так как это может стоить им назначения, если произошла ошибка, связанная с неправильным порядком.
Во втором, возможно, более поучительном примере, используя OrderDetails
приведенную выше таблицу, где мы не объединяем никакие другие таблицы, но имеем простое требование найти строки, соответствующие как OrderID, так и UserID, мы видим проблему.
Мы создадим индекс для поддержки запроса, как вы, вероятно, сделали бы в реальной жизни, если производительность имеет какое-либо значение (когда это не так?).
CREATE INDEX OrderDetailsOrderIDUserID ON dbo.OrderDetails(OrderID, UserID);
Вот запрос:
SELECT od.OrderDetailsID
FROM dbo.OrderDetails od
WHERE od.OrderID = 15
AND (od.UserID = 21 OR od.UserID = 22)
И результаты:
╔════════════════╗
║ OrderDetailsID ║
╠════════════════╣
║ 21421 ║
6 5061421 ║
║ 7091421 ║
14 691422 ║
7 3471422 ║
24 7241422 ║
╚════════════════╝
Добавление ORDER BY
предложения определенно гарантирует, что и здесь мы получим правильную сортировку.
Эти макеты являются просто простыми примерами, где строки не могут быть «в порядке» без явного ORDER BY
утверждения. Подобных примеров гораздо больше, и поскольку код механизма СУБД меняется довольно часто, конкретное поведение может со временем меняться.