web-dev-qa-db-fra.com

Créer une vue avec la clause ORDER BY

J'essaie de créer une vue avec une clause ORDER BY. Je l'ai créé avec succès sur SQL Server 2012 SP1, mais lorsque j'essaie de le recréer sur SQL Server 2008 R2, le message d'erreur suivant s'affiche: 

Msg 102, Niveau 15, Etat 1, Procédure TopUsers, Ligne 11
Incorrect syntaxe proche de 'OFFSET'.

Le code pour créer la vue est

CREATE View [dbo].[TopUsersTest] 
as 
select 
u.[DisplayName]  , sum(a.AnswerMark) as Marks
From Users_Questions us inner join [dbo].[Users] u
on u.[UserID] = us.[UserID] 
inner join [dbo].[Answers] a
on a.[AnswerID] = us.[AnswerID]
group by [DisplayName] 
order by Marks desc
OFFSET 0 ROWS

======================

Ceci est une capture d'écran du diagramme

Je souhaite retourner les utilisateurs DisplayName et UserTotalMarks et ordonner ce résultat desc, ainsi l'utilisateur avec le plus grand résultat sera sur le dessus.

35
El Sa7eR

Je ne suis pas sûr de ce que vous pensez que ce ORDER BY accomplit? Même si do place ORDER BY dans la vue de manière légale (par exemple, en ajoutant une clause TOP), si vous ne faites que sélectionner dans la vue, par exemple. SELECT * FROM dbo.TopUsersTest; sans clause ORDER BY, SQL Server est libre de renvoyer les lignes de la manière la plus efficace, ce qui ne correspond pas nécessairement à l'ordre attendu. En effet, ORDER BY est surchargé, dans la mesure où il tente de remplir deux objectifs: trier les résultats et dicter les lignes à inclure dans TOP. Dans ce cas, TOP gagne toujours (bien que, en fonction de l'index choisi pour analyser les données, vous remarquerez peut-être que votre commande fonctionne comme prévu - mais il ne s'agit que d'une coïncidence).

Pour accomplir ce que vous voulez, vous devez ajouter votre clause ORDER BY aux requêtes qui extraient des données de la vue, et non au code de la vue elle-même.

Donc, votre code de vue devrait juste être:

CREATE VIEW [dbo].[TopUsersTest] 
AS 
  SELECT 
    u.[DisplayName], SUM(a.AnswerMark) AS Marks
  FROM
    dbo.Users_Questions AS uq
    INNER JOIN [dbo].[Users] AS u
      ON u.[UserID] = us.[UserID] 
    INNER JOIN [dbo].[Answers] AS a
      ON a.[AnswerID] = uq.[AnswerID]
    GROUP BY u.[DisplayName];

Le ORDER BY n'a pas de sens et ne devrait donc même pas être inclus.


Pour illustrer, en utilisant AdventureWorks2012, voici un exemple:

CREATE VIEW dbo.SillyView
AS
  SELECT TOP 100 PERCENT 
    SalesOrderID, OrderDate, CustomerID , AccountNumber, TotalDue
  FROM Sales.SalesOrderHeader
  ORDER BY CustomerID;
GO

SELECT SalesOrderID, OrderDate, CustomerID, AccountNumber, TotalDue
FROM dbo.SillyView;

Résultats:

SalesOrderID   OrderDate   CustomerID   AccountNumber   TotalDue
------------   ----------  ----------   --------------  ----------
43659          2005-07-01  29825        10-4020-000676  23153.2339
43660          2005-07-01  29672        10-4020-000117  1457.3288
43661          2005-07-01  29734        10-4020-000442  36865.8012
43662          2005-07-01  29994        10-4020-000227  32474.9324
43663          2005-07-01  29565        10-4020-000510  472.3108

Et vous pouvez voir d'après le plan d'exécution que les variables TOP et ORDER BY ont été absolument ignorées et optimisées par SQL Server:

enter image description here

Il n'y a pas du tout d'opérateur TOP et pas de tri. SQL Server les a optimisés complètement.

Maintenant, si vous changez la vue pour dire ORDER BY SalesID, vous obtiendrez simplement l'ordre que la vue affiche, mais seulement - comme mentionné précédemment - par hasard.

Mais si vous modifiez votre requête externe pour exécuter le ORDER BY que vous vouliez:

SELECT SalesOrderID, OrderDate, CustomerID, AccountNumber, TotalDue
FROM dbo.SillyView
ORDER BY CustomerID;

Vous obtenez les résultats ordonnés comme vous le souhaitez:

SalesOrderID   OrderDate   CustomerID   AccountNumber   TotalDue
------------   ----------  ----------   --------------  ----------
43793          2005-07-22  11000        10-4030-011000  3756.989
51522          2007-07-22  11000        10-4030-011000  2587.8769
57418          2007-11-04  11000        10-4030-011000  2770.2682
51493          2007-07-20  11001        10-4030-011001  2674.0227
43767          2005-07-18  11001        10-4030-011001  3729.364

Et le plan a toujours optimisé la TOP/ORDER BY dans la vue, mais une sorte est ajoutée (sans aucun coût, remarquez) pour présenter les résultats ordonnés par CustomerID:

enter image description here

Donc, morale de l'histoire, ne mettez pas ORDER BY en vue. Placez ORDER BY dans les requêtes qui les référencent. Et si le tri coûte cher, vous pouvez envisager d’ajouter/modifier un index pour le prendre en charge.

69
Aaron Bertrand

J'ai réussi à forcer la vue à être commandée à l'aide de

SELECT TOP 9999999 ... ORDER BY something

Malheureusement, utiliser SELECT TOP 100 PERCENT ne fonctionne pas à cause du problème ici .

À partir de SQL 2012, vous pouvez forcer la commande dans les vues et les sous-requêtes avec OFFSET.

SELECT      C.CustomerID,
            C.CustomerName,
            C.CustomerAge
FROM        dbo.Customer C
ORDER BY    CustomerAge OFFSET 0 ROWS;

Attention: ceci ne devrait être utilisé que sur de petites listes car OFFSET force l'évaluation de la vue complète même si d'autres jointures ou filtres sur la vue réduisent sa taille!

Il n'y a pas de bon moyen de forcer la commande dans une vue sans effet secondaire vraiment et pour une bonne raison.

2
Tom Deloford

Comme l'un des commentaires de cette publication suggère d'utiliser des procédures stockées pour renvoyer les données ... Je pense que c'est la meilleure réponse. Dans mon cas, ce que j'ai fait est d'écrire une View pour encapsuler la logique de requête et les jointures, puis un Stored Proc pour renvoyer les données triées et le proc inclut également d'autres fonctionnalités d'amélioration telles que des paramètres de filtrage des données. 

Vous devez maintenant pouvoir interroger la vue, ce qui vous permet de manipuler davantage les données. Ou vous avez la possibilité d'exécuter le proc stocké, qui est une sortie plus rapide et plus précise. 

STORED PROC Exécution pour interroger des données

 exec [olap].[uspUsageStatsLogSessionsRollup]

VIEW Définition

USE [DBA]
GO

/****** Object:  View [olap].[vwUsageStatsLogSessionsRollup]    Script Date: 2/19/2019 10:10:06 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


--USE DBA
-- select * from olap.UsageStatsLog_GCOP039 where CubeCommand='[ORDER_HISTORY]'
;

ALTER VIEW [olap].[vwUsageStatsLogSessionsRollup] as
(
    SELECT --*
        t1.UsageStatsLogDate
        , COALESCE(CAST(t1.UsageStatsLogDate AS nvarchar(100)), 'TOTAL- DATES:') AS UsageStatsLogDate_Totals
        , t1.ADUserNameDisplayNEW
        , COALESCE(t1.ADUserNameDisplayNEW, 'TOTAL- USERS:') AS ADUserNameDisplay_Totals
        , t1.CubeCommandNEW
        , COALESCE(t1.CubeCommandNEW, 'TOTAL- CUBES:') AS CubeCommand_Totals
        , t1.SessionsCount
        , t1.UsersCount
        , t1.CubesCount
    FROM
    (
        select 
            CAST(olapUSL.UsageStatsLogTime as date) as UsageStatsLogDate
            , olapUSL.ADUserNameDisplayNEW
            , olapUSL.CubeCommandNEW
            , count(*) SessionsCount
            , count(distinct olapUSL.ADUserNameDisplayNEW) UsersCount
            , count(distinct olapUSL.CubeCommandNEW) CubesCount
        from 
            olap.vwUsageStatsLog olapUSL
        where CubeCommandNEW != '[]'
        GROUP BY CUBE(CAST(olapUSL.UsageStatsLogTime as date), olapUSL.ADUserNameDisplayNEW, olapUSL.CubeCommandNEW )
            ----GROUP BY 
            ------GROUP BY GROUPING SETS
            --------GROUP BY ROLLUP
    ) t1

    --ORDER BY
    --  t1.UsageStatsLogDate DESC
    --  , t1.ADUserNameDisplayNEW
    --  , t1.CubeCommandNEW
)
;


GO

STORED PROC Définition

USE [DBA]
GO

/****** Object:  StoredProcedure [olap].[uspUsageStatsLogSessionsRollup]    Script Date: 2/19/2019 9:39:31 AM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO


-- =============================================
-- Author:      BRIAN LOFTON
-- Create date: 2/19/2019
-- Description: This proceedured returns data from a view with sorted results and an optional date range filter.
-- =============================================
ALTER PROCEDURE [olap].[uspUsageStatsLogSessionsRollup]
    -- Add the parameters for the stored procedure here
    @paramStartDate date = NULL,
    @paramEndDate date = NULL,
    @paramDateTotalExcluded as int = 0,
    @paramUserTotalExcluded as int = 0,
    @paramCubeTotalExcluded as int = 0
AS

BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
    SET NOCOUNT ON;

    DECLARE @varStartDate as date 
        = CASE  
            WHEN @paramStartDate IS NULL THEN '1900-01-01' 
            ELSE @paramStartDate 
        END
    DECLARE @varEndDate as date 
        = CASE  
            WHEN @paramEndDate IS NULL THEN '2100-01-01' 
            ELSE @paramStartDate 
        END

    -- Return Data from this statement
    SELECT 
        t1.UsageStatsLogDate_Totals
        , t1.ADUserNameDisplay_Totals
        , t1.CubeCommand_Totals
        , t1.SessionsCount
        , t1.UsersCount
        , t1.CubesCount
        -- Fields with NULL in the totals
            --  , t1.CubeCommandNEW
            --  , t1.ADUserNameDisplayNEW
            --  , t1.UsageStatsLogDate
    FROM 
        olap.vwUsageStatsLogSessionsRollup t1
    WHERE

        (
            --t1.UsageStatsLogDate BETWEEN @varStartDate AND @varEndDate
            t1.UsageStatsLogDate BETWEEN '1900-01-01' AND '2100-01-01'
            OR t1.UsageStatsLogDate IS NULL
        )
        AND
        (
            @paramDateTotalExcluded=0
            OR (@paramDateTotalExcluded=1 AND UsageStatsLogDate_Totals NOT LIKE '%TOTAL-%')
        )
        AND
        (
            @paramDateTotalExcluded=0
            OR (@paramUserTotalExcluded=1 AND ADUserNameDisplay_Totals NOT LIKE '%TOTAL-%')
        )
        AND
        (
            @paramCubeTotalExcluded=0
            OR (@paramCubeTotalExcluded=1 AND CubeCommand_Totals NOT LIKE '%TOTAL-%')
        )
    ORDER BY
            t1.UsageStatsLogDate DESC
            , t1.ADUserNameDisplayNEW
            , t1.CubeCommandNEW

END


GO
1
SherlockSpreadsheets

Il suffit d’utiliser TOP 100% dans la sélection:

     CREATE VIEW [schema].[VIEWNAME] (
         [COLUMN1],
         [COLUMN2],
         [COLUMN3],
         [COLUMN4])
     AS 
        SELECT TOP 100 PERCENT 
         alias.[COLUMN1],
         alias.[COLUMN2],
         alias.[COLUMN3],
         alias.[COLUMN4]
        FROM 
           [schema].[TABLENAME] AS alias
          ORDER BY alias.COLUMN1
     GO
0
Code.IT