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.
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:
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
:
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.
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.
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
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
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