【问题标题】:Is this the correct way to use UNION ALL in a stored procedure?这是在存储过程中使用 UNION ALL 的正确方法吗?
【发布时间】:2012-12-30 20:20:08
【问题描述】:

这是在存储过程中UNION ALL 的正确方法吗?

ALTER PROCEDURE [GetHomePageObjectPageWise]
      @PageIndex INT = 1
      ,@PageSize INT = 10
      ,@PageCount INT OUTPUT
      ,@whereStoryID varchar(2000)
      ,@whereAlbumID varchar(2000)
      ,@wherePictureID varchar(2000)
AS
BEGIN
      SET NOCOUNT ON;

      SELECT StoryID
      , AlbumID
      , StoryTitle
      , NULL AS AlbumName
      , (SELECT URL FROM AlbumPictures WHERE (AlbumID = dbo.Stories.AlbumID) AND (AlbumCover = 'True')) AS AlbumCover
      , Votes
      , NULL AS PictureId
      , 'stories' AS tableName
      , NEWID() AS Sort 

INTO #Results1
FROM Stories WHERE StoryID IN (SELECT StringVal FROM funcListToTableInt(@whereStoryID))

      SELECT    NULL AS StoryID
      , AlbumID
      , NULL AS StoryTitle
      , AlbumName
      , (SELECT URL FROM AlbumPictures AS AlbumPictures_3 WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')) AS AlbumCover
      , Votes
      , NULL AS PictureId
      , 'albums' AS tableName
      , NEWID() AS Sort
INTO #Results2
FROM Albums WHERE AlbumID IN (SELECT StringVal FROM funcListToTableInt(@whereAlbumID))

        SELECT NULL AS StoryID
        , NULL AS AlbumID
        , NULL AS StoryTitle
        , NULL AS AlbumName
        , URL
        , Votes
        , PictureID
        , 'pictures' AS tableName
        , NEWID() AS Sort
        INTO #Results3
FROM AlbumPictures AS AlbumPictures_1
WHERE PictureID IN (SELECT StringVal FROM funcListToTableInt(@wherePictureID))

SELECT * INTO #Results4 FROM #Results1
UNION ALL
SELECT * FROM #Results2
UNION ALL
SELECT * FROM #Results3

SELECT ROW_NUMBER() OVER
            (
                  ORDER BY [Sort] DESC
            )AS RowNumber
            , * INTO #Results
            FROM #Results4


      DECLARE @RecordCount INT
      SELECT @RecordCount = COUNT(*) FROM #Results

      SET @PageCount = CEILING(CAST(@RecordCount AS DECIMAL(10, 2)) / CAST(@PageSize AS DECIMAL(10, 2)))

      SELECT * FROM #Results
      WHERE RowNumber BETWEEN(@PageIndex -1) * @PageSize + 1 AND(((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1

      DROP TABLE #Results
      DROP TABLE #Results1
      DROP TABLE #Results2
      DROP TABLE #Results3
      DROP TABLE #Results4
END

【问题讨论】:

  • 你能详细说明你的问题是什么吗?
  • @GordonLinoff 如给定示例所示,第一次我正在做 3 selects 并将结果放入 3 views 然后我在 3 views 上做 union all。所以我想知道,这是正确的方法还是我可以以更好的方式对其进行优化?
  • 您不是将结果放在三个视图中,而是将它们放在三个临时表中。然后,您将其复制到第四个临时表,并将 that 复制到第五个临时表。这似乎必须有一个更简单的方法。而且您每次都在复制所有记录,即使您只需要结果集中的 10 行(可调整)。但是,实际上很难看到您到底在寻找什么(NEWID() 用于列 Sort?),以便能够改进您的程序。你能展示你的表格、样本数据、样本输入参数和所需的输出吗?
  • @hvd 请检查我的其他问题以获取表结构、示例输入和输出。http://stackoverflow.com/questions/14071312/combining-rows-from-multiple-tables-with-different-number-of-columns/
  • @user1593175 啊,所以你想从三个表中的任何一个中随机抽取 10 行?你在用 PageIndex 做什么?当您请求第 1 页,然后再次请求第 1 页时,您将不会得到相同的结果,因为您将它们重新洗牌。这似乎是你想要的,但在那里有 PageIndex 似乎完全没有必要。我也怀疑您是如何选择随机行的,this question 对此部分有更多详细信息和替代方案。

标签: sql sql-server stored-procedures sql-server-2008-r2 union


【解决方案1】:

这些天我喜欢使用非物化CTEs 而不是临时表——尽管在某些情况下(比如数据需要索引)我会使用临时表。

主要是我会改变很多装饰性的东西,希望将来让它更具可读性(这未经测试,因为我没有你的数据副本)

ALTER PROCEDURE [GetHomePageObjectPageWise]
      @PageIndex        INT = 1
      ,@PageSize        INT = 10
      ,@PageCount       INT OUTPUT
      ,@whereStoryID    VARCHAR(2000)
      ,@whereAlbumID    VARCHAR(2000)
      ,@wherePictureID VARCHAR(2000)
AS
BEGIN
     SET NOCOUNT ON;

    WITH Results1 AS
        (
        SELECT 
            StoryID,
            AlbumID,
            StoryTitle,
            [AlbumName] = NULL,
            [AlbumCover] = 
                (
                SELECT URL 
                FROM AlbumPictures 
                WHERE (AlbumID = dbo.Stories.AlbumID) AND (AlbumCover = 'True')
                ),
            Votes,
            [PictureId] = NULL,
            [tableName] = 'stories',
            [Sort] = NEWID()
        FROM Stories 
        WHERE 
                StoryID IN 
                (
                SELECT StringVal 
                FROM funcListToTableInt(@whereStoryID)
                )
        )
    , Results2 AS
        (
        SELECT    
            [StoryID] = NULL ,
            AlbumID,
            [StoryTitle] NULL,
            AlbumName,
            [AlbumCover] = 
                (
                SELECT URL 
                FROM AlbumPictures AS AlbumPictures_3 --<<<DO YOU NEED THIS ALIAS?
                WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')
                ),
            Votes,
            [PictureId] = NULL,
            [tableName] = 'albums',
            [Sort] = NEWID()
        FROM Albums 
        WHERE 
            AlbumID IN 
                (
                SELECT StringVal 
                FROM funcListToTableInt(@whereAlbumID)
                )
        )       
    , Result3 AS  
        (
        SELECT 
            [StoryID] = NULL, 
            [AlbumID] = NULL,
            [StoryTitle] = NULL,
            [AlbumName] = NULL,
            URL,
            Votes,
            PictureID,
            [tableName] = 'pictures',
            [Sort] = NEWID()
        FROM AlbumPictures --AS AlbumPictures_1 <<<DO YOU NEED THIS ALIAS?
        WHERE 
            PictureID IN 
                (
                SELECT StringVal 
                FROM funcListToTableInt(@wherePictureID)
                )
        )
    , Result4 AS  
        (
        SELECT * FROM Results1 UNION ALL
        SELECT * FROM Results2 UNION ALL
        SELECT * FROM Results3
        )
    , Results AS
        (
        SELECT 
                [RowNumber] = ROW_NUMBER() OVER (ORDER BY [Sort] DESC),
            x.* 
        FROM Results4   x
        )
 SELECT * 
 FROM Results
 WHERE RowNumber BETWEEN(@PageIndex -1) * @PageSize + 1 AND(((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1;
 DECLARE @RecordCount INT = @@RowCount; 

 SET @PageCount = CEILING(CAST(@RecordCount AS DECIMAL(10, 2)) / CAST(@PageSize AS DECIMAL(10, 2)));

END

我通常使用 Aaron Bertrand 的建议来编写存储过程 这篇博文是我的清单,也是我用来尝试将我使用的所有 Sproc 的风格统一起来的模板:

https://sqlblog.org/2008/10/30/my-stored-procedure-best-practices-checklist


我认为正如 Gordon 建议的那样,您可以将大量逻辑移出存储过程并创建一个 VIEW,如下所示:

CREATE VIEW [console].[vw_mySimpleView]
AS

BEGIN
    SET NOCOUNT ON;

    WITH Results1 AS
        (
        SELECT 
            StoryID,
            AlbumID,
            StoryTitle,
            [AlbumName] = NULL,
            [AlbumCover] = 
                (
                SELECT URL 
                FROM AlbumPictures 
                WHERE (AlbumID = dbo.Stories.AlbumID) AND (AlbumCover = 'True')
                ),
            Votes,
            [PictureId] = NULL,
            [tableName] = 'stories',
            [Sort] = NEWID()
        FROM Stories 
        )
    , Results2 AS
        (
        SELECT    
            [StoryID] = NULL ,
            AlbumID,
            [StoryTitle] NULL,
            AlbumName,
            [AlbumCover] = 
                (
                SELECT URL 
                FROM AlbumPictures 
                WHERE (AlbumID = Albums.AlbumID) AND (AlbumCover = 'True')
                ),
            Votes,
            [PictureId] = NULL,
            [tableName] = 'albums',
            [Sort] = NEWID()
        FROM Albums 
        )       
    , Result3 AS  
        (
        SELECT 
            [StoryID] = NULL, 
            [AlbumID] = NULL,
            [StoryTitle] = NULL,
            [AlbumName] = NULL,
            URL,
            Votes,
            PictureID,
            [tableName] = 'pictures',
            [Sort] = NEWID()
        FROM AlbumPictures
        )
    , Result4 AS  
        (
        SELECT * FROM Results1 UNION ALL
        SELECT * FROM Results2 UNION ALL
        SELECT * FROM Results3
        )
SELECT *
FROM Results4;

GO 

那么 Sproc 会短很多:

ALTER PROCEDURE [GetHomePageObjectPageWise]
      @PageIndex        INT = 1
      ,@PageSize        INT = 10
      ,@PageCount       INT OUTPUT
      ,@whereStoryID    VARCHAR(2000)
      ,@whereAlbumID    VARCHAR(2000)
      ,@wherePictureID VARCHAR(2000)
AS
BEGIN
     SET NOCOUNT ON;

    SELECT * 
    FROM 
        (
        SELECT 
            [RowNumber] = ROW_NUMBER() OVER (ORDER BY [Sort] DESC),
            x.* 
        FROM 
            (
            SELECT *
            FROM [dbo].[vw_mySimpleView] 
            WHERE 
                StoryID IN 
                    (
                    SELECT StringVal 
                    FROM funcListToTableInt(@whereStoryID)
                    )
                            OR  
                            AlbumID IN 
                                   (
                                   SELECT StringVal 
                                   FROM funcListToTableInt(@whereAlbumID)
                                   )
            )   x
        )
    WHERE RowNumber BETWEEN(@PageIndex -1) * @PageSize + 1 AND(((@PageIndex -1) * @PageSize + 1) + @PageSize) - 1;
    DECLARE @RecordCount INT = @@RowCount; 

    SET @PageCount = CEILING(CAST(@RecordCount AS DECIMAL(10, 2)) / CAST(@PageSize AS DECIMAL(10, 2)));


END

【讨论】:

  • 您使用了 Result3 两次,也使用了 Result4..但我猜您没有在任何地方声明 Result4..对吗?
  • 错字 - 希望现在更好
  • 感谢您的时间和精力。除了可读性之外,我和你的解决方案基本上没有区别?
  • 通过CTEs 和布局的可读性。我想我在一个DECLARE @RecordCount INT = (SELECT COUNT(*) cnt FROM Results); 中做了这个声明,我去掉了一些表别名,因为它们似乎没有必要。我添加了分号,因为它们将来会用到。
  • 太好了。谢谢。你是个好人。 +1 并接受您的回答。有什么办法可以跳过这个过程并在动态 sql 中做同样的事情吗?
【解决方案2】:

这里有一些cmets:

(1) 我更喜欢表值变量 (declare @Results as table . . .) 而不是临时表。

(2) 通常,编写单个查询可能比单独的查询更好。因此,无论如何,您都可以消除中间结果表。 SQL 引擎旨在优化执行路径。给他们一个工作的机会。也就是说,有时他们会出错,而中间表是可取的/必要的。

(3) 你的排序没问题,但你需要小心。如果 Sort 有重复的值,则可能会出现重复值的风险,并且不同的迭代会导致问题。

(4) 既然您实际上只是从查询返回结果,为什么不直接定义查询(也许作为视图)并完全消除存储过程?存储过程使得 SQL Server 不太可能出于分页目的缓存结果。

(5) 我也想知道您是否可以删除 from 子句中的函数调用,因为它们也会对性能产生负面影响。

【讨论】:

  • @MartinSmith。 . .像往常一样,你是对的,我学到了一些新东西。谢谢。
  • 感谢您的出色回答。您能否详细说明第 2 点和第 4 点?你介意给我第 4 点的代码 sn-p 吗?
  • 还在等你的回复
  • @user1593175 SO 是一个了不起的资源 - 有时你从这里得到正确的想法,但你需要与他们一起玩,然后可能会回来提出更多问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-07
  • 2012-05-11
  • 2018-12-31
  • 2021-04-29
  • 1970-01-01
相关资源
最近更新 更多