Tôi đang viết một truy vấn sẽ được sử dụng để kết quả trang cho nguồn cấp dữ liệu xã hội. Khái niệm này là ứng dụng di động sẽ yêu cầu N mục và cung cấp thời gian bắt đầu mà tôi đã gọi @CutoffTime
bên dưới. Mục đích của thời gian cắt là để thiết lập khi cửa sổ phân trang sẽ bắt đầu. Lý do tại sao chúng tôi sử dụng dấu thời gian thay vì bù hàng là dấu thời gian sẽ cho phép chúng tôi trang từ một vị trí nhất quán khi nhận được bài đăng cũ hơn ngay cả khi nội dung xã hội mới hơn được thêm vào.
Vì các mục nguồn cấp dữ liệu xã hội có thể là từ chính bạn hoặc bạn bè của bạn, tôi đang sử dụng UNION
để kết hợp các kết quả từ hai nhóm đó. Ban đầu tôi đã thử TheQuery_CTE
logic mà không có UNION
và nó chậm chó.
Đây là những gì tôi đã làm (bao gồm cả lược đồ bảng thích hợp):
CREATE TABLE [Content].[Photo]
(
[PhotoId] INT NOT NULL PRIMARY KEY IDENTITY (1, 1),
[Key] UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
[FullResolutionUrl] NVARCHAR(255) NOT NULL,
[Description] NVARCHAR(255) NULL,
[Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
);
CREATE TABLE [Content].[UserPhotoAssociation]
(
[PhotoId] INT NOT NULL,
[UserId] INT NOT NULL,
[ShowInSocialFeed] BIT NOT NULL DEFAULT 0,
CONSTRAINT [PK_UserPhotos] PRIMARY KEY ([PhotoId], [UserId]),
CONSTRAINT [FK_UserPhotos_User] FOREIGN KEY ([UserId])
REFERENCES [User].[User]([UserId]),
CONSTRAINT [FK_UserPhotos_Photo] FOREIGN KEY ([PhotoId])
REFERENCES [Content].[Photo]([PhotoId])
);
CREATE TABLE [Content].[FlaggedPhoto]
(
[FlaggedPhotoId] INT NOT NULL PRIMARY KEY IDENTITY(1,1),
[PhotoId] INT NOT NULL,
[FlaggedBy] INT NOT NULL,
[FlaggedOn] DATETIME2(0) NOT NULL DEFAULT SYSDATETIME(),
[FlaggedStatus] INT NOT NULL DEFAULT 1,
[ReviewedBy] INT NULL,
[ReviewedAt] DATETIME2(0) NULL
CONSTRAINT [FK_Photos_PhotoId_to_FlaggedPhotos_PhotoId] FOREIGN KEY ([PhotoId])
REFERENCES [Content].[Photo]([PhotoId]),
CONSTRAINT [FK_FlaggedPhotoStatus_FlaggedPhotoStatusId_to_FlaggedPhotos_FlaggedStatus] FOREIGN KEY ([FlaggedStatus])
REFERENCES [Content].[FlaggedContentStatus]([FlaggedContentStatusId]),
CONSTRAINT [FK_User_UserId_to_FlaggedPhotos_FlaggedBy] FOREIGN KEY ([FlaggedBy])
REFERENCES [User].[User]([UserId]),
CONSTRAINT [FK_User_UserId_to_FlaggedPhotos_ReviewedBy] FOREIGN KEY ([ReviewedBy])
REFERENCES [User].[User]([UserId])
);
CREATE TABLE [User].[CurrentConnections]
(
[MonitoringId] INT NOT NULL PRIMARY KEY IDENTITY,
[Monitor] INT NOT NULL,
[Monitored] INT NOT NULL,
[ShowInSocialFeed] BIT NOT NULL DEFAULT 1,
CONSTRAINT [FK_Monitoring_Monitor_to_User_UserId] FOREIGN KEY ([Monitor])
REFERENCES [dbo].[User]([UserId]),
CONSTRAINT [FK_Monitoring_Monitored_to_User_UserId] FOREIGN KEY ([Monitored])
REFERENCES [dbo].[User]([UserId])
);
CREATE TABLE [Content].[PhotoLike]
(
[PhotoLikeId] INT NOT NULL PRIMARY KEY IDENTITY,
[PhotoId] INT NOT NULL,
[UserId] INT NOT NULL,
[Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
[Archived] DATETIME2(2) NULL,
CONSTRAINT [FK_PhotoLike_PhotoId_to_Photo_PhotoId] FOREIGN KEY ([PhotoId])
REFERENCES [Content].[Photo]([PhotoId]),
CONSTRAINT [FK_PhotoLike_UserId_to_User_UserId] FOREIGN KEY ([UserId])
REFERENCES [User].[User]([UserId])
);
CREATE TABLE [Content].[Comment]
(
[CommentId] INT NOT NULL PRIMARY KEY IDENTITY,
[PhotoId] INT NOT NULL,
[UserId] INT NOT NULL,
[Comment] NVARCHAR(255) NOT NULL,
[Created] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
[CommentOrder] DATETIME2(2) NOT NULL DEFAULT SYSUTCDATETIME(),
[Archived] DATETIME2(2) NULL,
CONSTRAINT [FK_Comment_PhotoId_to_Photo_PhotoId] FOREIGN KEY ([PhotoId])
REFERENCES [Content].[Photo]([PhotoId]),
CONSTRAINT [FK_Comment_UserId_to_User_UserId] FOREIGN KEY ([UserId])
REFERENCES [User].[User]([UserId])
);
/*
End table schema
*/
DECLARE @UserId INT,
@NumberOfItems INT,
@CutoffTime DATETIME2(2) = NULL -- Stored Proc input params
-- Make the joins and grab the social data we need once since they are used in subsequent queries that aren't shown
DECLARE @SocialFeed TABLE ([Key] UNIQUEIDENTIFIER, [PhotoId] INT
, [Description] NVARCHAR(255), [FullResolutionUrl] NVARCHAR(255)
, [Created] DATETIME2(2), [CreatorId] INT, [LikeCount] INT
, [CommentCount] INT, [UserLiked] BIT);
-- Offset might be different for each group
DECLARE @OffsetMine INT = 0, @OffsetTheirs INT = 0;
IF @CutoffTime IS NOT NULL
BEGIN
-- Get the offsets
;WITH [GetCounts_CTE] AS
(
SELECT
[P].[PhotoId] -- INT
, 1 AS [MyPhotos]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
[FP].[FlaggedStatus] = 3 -- Flagged photos that are confirmed apply to everyone
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
AND
[UPA].[UserId] = @UserId -- Show the requesting user
AND
[P].[Created] >= @CutoffTime -- Get the newer items
UNION
SELECT
[P].[PhotoId] -- INT
, 0 AS [MyPhotos]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
INNER JOIN [User].[CurrentConnections] [M] ON
[M].[Monitored] = [UPA].[UserId]
AND
[M].[Monitor] = @UserId AND [M].[ShowInSocialFeed] = 1 -- this join isn't present above
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
(
[FP].[FlaggedStatus] = 3
OR
([FP].[FlaggedBy] = @UserId AND [FP].[FlaggedStatus] = 1)
) -- Flagged photos that are confirmed apply to everyone, pending flags apply to the user
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
AND
[P].[Created] >= @CutoffTime -- Get the newer items
)
SELECT
@OffsetMine = SUM(CASE WHEN [MyPhotos] = 1 THEN 1 ELSE 0 END)
, @OffsetTheirs = SUM(CASE WHEN [MyPhotos] = 0 THEN 1 ELSE 0 END)
FROM [GetCounts_CTE]
END
-- Prevent absence of social data from throwing an error below.
SET @OffsetMine = ISNULL(@OffsetMine, 0);
SET @OffsetTheirs = ISNULL(@OffsetTheirs, 0);
-- Actually select the data I want
;WITH TheQuery_CTE AS
(
SELECT
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
, COUNT(DISTINCT [PL].[PhotoLikeId]) AS [LikeCount] -- Count distinct used due to common join key
, COUNT(DISTINCT [C].[CommentId]) AS [CommentCount]
, CAST(ISNULL(MAX(CASE WHEN [PL].[UserId] = @UserId THEN 1 END), 0) AS BIT) AS [UserLiked]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
LEFT JOIN [Content].[PhotoLike] [PL] ON
[PL].[PhotoId] = [P].[PhotoId]
AND
[PL].[Archived] IS NULL
LEFT JOIN [Content].[Comment] [C] ON
[C].[PhotoId] = [P].[PhotoId]
AND
[C].[Archived] IS NULL
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
[FP].[FlaggedStatus] = 3 -- Flagged photos that are confirmed apply to everyone
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
AND
[UPA].[UserId] = @UserId -- Show the requesting user
GROUP BY
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
ORDER BY
[P].[Created] DESC
, [P].[Key] -- Ensure consistent order in case of duplicate timestamps
OFFSET @OffsetMine ROWS FETCH NEXT @NumberOfItems ROWS ONLY
UNION
SELECT
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
, COUNT(DISTINCT [PL].[PhotoLikeId]) AS [LikeCount]
, COUNT(DISTINCT [C].[CommentId]) AS [CommentCount]
, CAST(ISNULL(MAX(CASE WHEN [PL].[UserId] = @UserId THEN 1 END), 0) AS BIT) AS [UserLiked]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
INNER JOIN [User].[CurrentConnections] [M] ON
[M].[Monitored] = [UPA].[UserId]
AND
[M].[Monitor] = @UserId AND [M].[ShowInSocialFeed] = 1
LEFT JOIN [Content].[PhotoLike] [PL] ON
[PL].[PhotoId] = [P].[PhotoId]
AND
[PL].[Archived] IS NULL
LEFT JOIN [Content].[Comment] [C] ON
[C].[PhotoId] = [P].[PhotoId]
AND
[C].[Archived] IS NULL
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
(
[FP].[FlaggedStatus] = 3
OR
([FP].[FlaggedBy] = @UserId AND [FP].[FlaggedStatus] = 1)
) -- Flagged photos that are confirmed apply to everyone, pending flags apply to the user
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
GROUP BY
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
ORDER BY
[P].[Created] DESC
, [P].[Key] -- Ensure consistant order in case of duplicate timestamps
OFFSET @OffsetTheirs ROWS FETCH NEXT @NumberOfItems ROWS ONLY
)
INSERT INTO @SocialFeed ([Key], [PhotoId], [Description], [FullResolutionUrl]
, [Created], [CreatorId], [LikeCount], [CommentCount], [UserLiked])
SELECT TOP (@NumberOfItems)
[Key]
, [PhotoId]
, [Description]
, [FullResolutionUrl]
, [Created]
, [UserId]
, [LikeCount]
, [CommentCount]
, [UserLiked]
FROM [TheQuery_CTE]
ORDER BY -- Order here so the top works properly
[Created] DESC
, [Key] -- Ensure consistent order in case of duplicate timestamps
-- Output the social feed
SELECT
[P].[Key]
, [P].[PhotoId]
, [P].[Description] AS [PhotoDescription]
, [P].[FullResolutionUrl]
, [P].[Created] AS [Posted]
, [P].[CreatorId]
, [LikeCount]
, [CommentCount]
, [UserLiked]
FROM @Photos [P]
-- Select other data needed to build the object tree in the application layer
Tôi nhận ra rằng tôi có thể thoát khỏi vấn UNION
đề này GetCounts_CTE
nhưng tôi không nghĩ nó sẽ thực sự giải quyết được bất kỳ vấn đề nào tôi thấy dưới đây.
Tôi thấy một vài vấn đề tiềm ẩn:
- Đó là rất nhiều logic trùng lặp, vì vậy tôi có thể làm cho cuộc sống khó khăn hơn cho chính mình.
- Nếu việc chèn xảy ra giữa việc tính số lượng và chọn dữ liệu tôi sẽ tắt. Tôi không nghĩ rằng điều này sẽ xảy ra thường xuyên nhưng nó sẽ dẫn đến các lỗi lạ / khó gỡ lỗi.
- Tất cả các vấn đề thông minh hơn / nhiều kinh nghiệm hơn mọi người sẽ tìm thấy với thiết lập ở trên.
Cách tốt nhất để viết truy vấn này là gì? Điểm thưởng giải pháp làm cho cuộc sống của tôi đơn giản hơn.
Biên tập:
Tôi không muốn chọn tất cả dữ liệu và để khách hàng hiển thị các mục một cách lười biếng, vì tôi không muốn lạm dụng các gói dữ liệu của mọi người bằng cách buộc họ tải xuống các mục mà họ sẽ không bao giờ nhìn thấy. Phải thừa nhận rằng dữ liệu có thể sẽ không lớn trong sơ đồ lớn của mọi thứ, nhưng chồng lên nhau ....
Chỉnh sửa 2:
Tôi hoàn toàn nghi ngờ đây không phải là giải pháp tối ưu, nhưng nó là giải pháp tốt nhất mà tôi nghĩ ra cho đến nay.
Chuyển UNION
truy vấn của tôi sang VIEW
tương tự như Greg đề xuất hoạt động tốt để ẩn logic đó và đưa ra một truy vấn ngắn gọn hơn trong quy trình được lưu trữ của tôi. Khung nhìn cũng trừu tượng hóa sự xấu xí / phức tạp của liên minh, điều này thật tuyệt vì tôi đang sử dụng nó hai lần trong lựa chọn của mình. Đây là mã cho chế độ xem:
CREATE VIEW [Social].[EverFeed]
AS
SELECT
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
, COUNT(DISTINCT [PL].[PhotoLikeId]) AS [LikeCount] -- Distinct due to common join key
, COUNT(DISTINCT [C].[CommentId]) AS [CommentCount]
, CAST(ISNULL(
MAX(CASE WHEN [PL].[UserId] = [UPA].[UserId] THEN 1 END), 0) AS BIT) AS [UserLiked]
, NULL AS [Monitor]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
LEFT JOIN [Content].[PhotoLike] [PL] ON
[PL].[PhotoId] = [P].[PhotoId]
AND
[PL].[Archived] IS NULL
LEFT JOIN [Content].[Comment] [C] ON
[C].[PhotoId] = [P].[PhotoId]
AND
[C].[Archived] IS NULL
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
[FP].[FlaggedStatus] = 3 -- Flagged photos that are confirmed apply to everyone
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
GROUP BY
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
UNION
SELECT
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
, COUNT(DISTINCT [PL].[PhotoLikeId]) AS [LikeCount]
, COUNT(DISTINCT [C].[CommentId]) AS [CommentCount]
, CAST(ISNULL(
MAX(CASE WHEN [PL].[UserId] = [M].[Monitor] THEN 1 END), 0) AS BIT) AS [UserLiked]
, [M].[Monitor]
FROM [Content].[Photo] [P]
INNER JOIN [Content].[UserPhotoAssociation] [UPA] ON
[UPA].[PhotoId] = [P].[PhotoId]
AND
[UPA].[ShowInSocialFeed] = 1
INNER JOIN [User].[CurrentConnections] [M] ON
[M].[Monitored] = [UPA].[UserId]
AND
[M].[ShowInSocialFeed] = 1
LEFT JOIN [Content].[PhotoLike] [PL] ON
[PL].[PhotoId] = [P].[PhotoId]
AND
[PL].[Archived] IS NULL
LEFT JOIN [Content].[Comment] [C] ON
[C].[PhotoId] = [P].[PhotoId]
AND
[C].[Archived] IS NULL
LEFT JOIN [Content].[FlaggedPhoto] [FP] ON
[FP].[PhotoId] = [P].[PhotoId]
AND
(
[FP].[FlaggedStatus] = 3
OR
([FP].[FlaggedBy] = [M].[Monitor] AND [FP].[FlaggedStatus] = 1)
) -- Flagged photos that are confirmed (3) apply to everyone
-- , pending flags (1) apply to the user
WHERE
[FP].[FlaggedPhotoId] IS NULL -- Filter out flagged photos
GROUP BY
[P].[Key]
, [P].[PhotoId]
, [P].[Description]
, [P].[FullResolutionUrl]
, [P].[Created]
, [UPA].[UserId]
, [M].[Monitor]
Sử dụng chế độ xem đó, tôi rút ngắn truy vấn của mình xuống như sau. Lưu ý Tôi đang thiết lập OFFSET
với một truy vấn con.
DECLARE @UserId INT, @NumberOfItems INT, @CutoffTime DATETIME2(2);
SELECT
[Key]
, [PhotoId]
, [Description]
, [FullResolutionUrl]
, [Created]
, [UserId]
, [LikeCount]
, [CommentCount]
, [UserLiked]
FROM [Social].[EverFeed] [EF]
WHERE
(
([EF].[UserId] = @UserId AND [EF].[Monitor] IS NULL)
OR
[EF].[Monitor] = @UserId
)
ORDER BY -- Order here so the top works properly
[Created] DESC
, [Key] -- Ensure consistant order in case of duplicate timestamps
OFFSET CASE WHEN @CutoffTime IS NULL THEN 0 ELSE
(
SELECT
COUNT([PhotoId])
FROM [Social].[EverFeed] [EF]
WHERE
(
([EF].[UserId] = @UserId AND [EF].[Monitor] IS NULL)
OR
[EF].[Monitor] = @UserId
)
AND
[EF].[Created] >= @CutoffTime -- Get the newer items
) END
ROWS FETCH NEXT @NumberOfItems ROWS ONLY
Khung nhìn tách biệt sự phức tạp của UNION
bộ lọc khỏi bộ lọc. Tôi nghĩ rằng truy vấn con trong OFFSET
mệnh đề sẽ ngăn chặn các vấn đề tương tranh mà tôi đã lo lắng bằng cách làm cho toàn bộ truy vấn nguyên tử.
Một vấn đề tôi vừa tìm thấy trong khi gõ này là: trong đoạn mã trên nếu hai ảnh có cùng ngày tạo trên các "trang" khác nhau thì ảnh trên các trang tiếp theo sẽ bị lọc ra. Hãy xem xét các dữ liệu sau:
PhotoId | Created | ...
------------------------
1 | 2015-08-26 01:00.00
2 | 2015-08-26 01:00.00
3 | 2015-08-26 01:00.00
Với kích thước trang là 1 trên trang ban đầu, PhotoId 1
sẽ được trả lại. Với cùng kích thước trang trên trang thứ hai, kết quả sẽ không được trả về. Tôi nghĩ rằng để giải quyết vấn đề này, tôi sẽ phải thêm Hướng dẫn Key
làm tham số ....