3

When I go to run this query on SQL Server to create an indexed view, an error occurs to remove subqueries and Union

CREATE VIEW [dbo].[view_ToolGroup] 
WITH SCHEMABINDING
AS
   SELECT 
      toolGroup.ToolGroupId,toolGroupToTool.ToolId, toolGroupApp.AppId as TGAppId,
      purposeToToolGroup.PurposeId as TGPurposeId, TGRole.RoleId as TGRoleId
   FROM
      [dbo].[toolGroup], [dbo].[purposeToTG], [dbo].[toolGroupToTool],
      [dbo].[toolGroupToeApp] as toolGroupApp,
      [dbo].[toolGroupToeAppToeRole] as toolGroupAppRole,
      [dbo].[eRole] as TGRole    
   WHERE 
      toolGroup.ToolGroupId = purposeToToolGroup.ToolGroupId
      and toolGroup.ToolGroupId = toolGroupToTool.ToolGroupId
      and toolGroup.ToolGroupId = toolGroupApp.ToolGroupId 
      and toolGroupApp.toolGroupToeApplicationID=toolGroupAppRole.toolGroupToeApplicationID
      and toolGroupAppRole.ToolgroupToeApplicationID in 
                   (select ToolgroupToeApplicationID     
                    from [dbo].[ToolgroupToeApplication])   
      and toolGroupAppRole.RoleId = TGRole.RoleId   

   UNION

   SELECT
       toolGroup.ToolGroupId, toolGroup.ToolGroupName,
       null, null, null, null, null, null, null, null
   FROM
       [dbo].[toolGroup], [dbo].[toolGroupToeApplication]
   WHERE 
       toolGroup.ToolGroupId = toolGroupToeApplication.ToolGroupId
       and toolGroup.ToolGroupId not in 
              (select PurposeToToolGroup.ToolGroupId from [dbo].[PurposeToToolGroup])
       and toolGroup.ToolGroupId in (select distinct ToolGroupId 
                                     from [dbo]. [toolGroupToeApplication] )'
GO

CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup
ON view_ToolGroup(ToolGroupId, ToolId, TGPurposeId, TGRoleId)
GO

Can anybody suggest an alternative solution to replace UNION and subqueries?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Prak
  • 815
  • 7
  • 18
  • 1
    There is **no alternative** - if you want to create an indexed view, you **must remove** the `UNION` and subqueries. The requirements for an indexed view are pretty harsh - that's true; either you can do it - or you cannot create an indexed view. – marc_s Jul 15 '14 at 14:13
  • 2
    [Bad habits to kick : using old-style JOINs](https://sqlblog.org/2009/10/08/bad-habits-to-kick-using-old-style-joins) - that old-style *comma-separated list of tables* style is discouraged since the ANSI-**92** SQL Standard (more than **20 years** ago) and should not be used – marc_s Jul 15 '14 at 14:13
  • I have used old style comma-saparated list of tables....but it results duplicate data on ToolGroupId, ToolId, TGPurposeId, TGRoleId fields and it does not allow DISTINCT in query – Prak Jul 15 '14 at 14:26
  • 3
    If your query is sensible (I'm not going to attempt to judge that at this time since I have no idea what the tables represent) and it includes a `UNION`, then it is highly likely that any alternative formulation of your query will also include a `UNION` or some other construct that is also not allowed in an indexed view. The rules for what's allowed aren't arbitrary - they're to ensure that the server can generate sane code for maintaining the view based on updates to the base tables without having to rescan entire tables. – Damien_The_Unbeliever Jul 15 '14 at 14:28
  • As @Damien_The_Unbeliever said, there are reasons why those constructs aren't allowed in an indexed view. If you can't get this query to perform any other way, then your best bet may be to physicalize it. – Andrew Jul 15 '14 at 14:56

1 Answers1

3

As per all the suggestion above, there is no direct way of doing this. However we can cheat. You could do the following

  1. Break up the statements into two views
  2. Add index to each view
  3. Replace IN with INNER JOIN
  4. User NOT IN while calling the views

As Damien suggested, it would be foolish of me to try and suggest or attempt to judge the logic at this time since we have no idea what the tables represent. I have however restructured the code as per the above which you could use as a template to structure your actual query.

Hope this helps

--Drop Index If Already Aresent
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vw_ToolGroup_One]'))
DROP VIEW dbo.vw_ToolGroup_One
GO

IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vw_ToolGroup_Two]'))
DROP VIEW dbo.vw_ToolGroup_Two
GO

--Drop Dependant Tables
IF OBJECT_ID(N'toolGroup_tmp')>0
BEGIN
    DROP TABLE toolGroup_tmp
END
CREATE TABLE toolGroup_tmp (ToolGroupId INT,ToolGroupName VARCHAR(100))
INSERT INTO toolGroup_tmp
SELECT 1,'ONE'
GO

IF OBJECT_ID(N'purposeToTG_tmp')>0
BEGIN
    DROP TABLE purposeToTG_tmp
END
CREATE TABLE purposeToTG_tmp (ToolGroupId INT,PurposeId int)
INSERT INTO purposeToTG_tmp
SELECT 1,1
GO

IF OBJECT_ID(N'toolGroupToTool_tmp')>0
BEGIN
    DROP TABLE toolGroupToTool_tmp
END
CREATE TABLE toolGroupToTool_tmp (ToolGroupId INT,ToolId INT)
INSERT INTO toolGroupToTool_tmp
SELECT 1,1
GO

IF OBJECT_ID(N'toolGroupToeApp_tmp')>0
BEGIN
    DROP TABLE toolGroupToeApp_tmp
END
CREATE TABLE toolGroupToeApp_tmp (ToolGroupId INT,AppId INT,toolGroupToeApplicationID INT)
INSERT INTO toolGroupToeApp_tmp
SELECT 1,1,1
GO

IF OBJECT_ID(N'toolGroupToeAppToeRole_tmp')>0
BEGIN
    DROP TABLE toolGroupToeAppToeRole_tmp
END
CREATE TABLE toolGroupToeAppToeRole_tmp (ToolGroupId INT,RoleId INT,toolGroupToeApplicationID INT)
INSERT INTO toolGroupToeAppToeRole_tmp
SELECT 1,1,1 
GO

IF OBJECT_ID(N'ToolgroupToeApplication_tmp')>0
BEGIN
    DROP TABLE ToolgroupToeApplication_tmp
END
CREATE TABLE ToolgroupToeApplication_tmp (ToolGroupId INT,ToolgroupToeApplicationID INT)
INSERT INTO ToolgroupToeApplication_tmp
SELECT 1,1
GO

IF OBJECT_ID(N'PurposeToToolGroup_tmp')>0
BEGIN
    DROP TABLE PurposeToToolGroup_tmp
END
CREATE TABLE PurposeToToolGroup_tmp (ToolGroupId INT)
INSERT INTO PurposeToToolGroup_tmp
SELECT 1
GO

IF OBJECT_ID(N'eRole_tmp')>0
BEGIN
    DROP TABLE eRole_tmp
END
CREATE TABLE eRole_tmp (RoleId INT)
INSERT INTO eRole_tmp
SELECT 1
GO

--Create Views
DECLARE @SQL NVARCHAR(MAX)

SET @SQL = '
CREATE VIEW dbo.vw_ToolGroup_One WITH SCHEMABINDING
AS
    SELECT
           tg.ToolGroupId,
           tg.ToolGroupName,
           tgtt.ToolId,
           tga.AppId AS TGAppId,
           pttg.PurposeId AS TGPurposeId,
           tgr.RoleId AS TGRoleId
    FROM dbo.toolGroup_tmp tg
         INNER JOIN dbo.purposeToTG_tmp pttg
             ON tg.ToolGroupId = pttg.ToolGroupId
         INNER JOIN dbo.toolGroupToTool_tmp tgtt
             ON tg.ToolGroupId = tgtt.ToolGroupId
         INNER JOIN dbo.toolGroupToeApp_tmp tga
             ON tg.ToolGroupId = tga.ToolGroupId
         INNER JOIN dbo.toolGroupToeAppToeRole_tmp tgar
             ON tga.toolGroupToeApplicationID = tgar.toolGroupToeApplicationID
         INNER JOIN dbo.ToolgroupToeApplication_tmp tgta
             ON tgta.ToolgroupToeApplicationID = tgar.ToolgroupToeApplicationID
         INNER JOIN dbo.eRole_tmp tgr
             ON tgar.RoleId = tgr.RoleId
'
EXEC SP_EXECUTESQL @SQL

SET @SQL = '
CREATE VIEW dbo.vw_ToolGroup_Two WITH SCHEMABINDING
AS

    SELECT tg.ToolGroupId,
           tg.ToolGroupName,
           NULL AS ToolId,
           NULL AS TGAppId,
           NULL AS TGPurposeId,
           NULL AS TGRoleId
    FROM dbo.toolGroup_tmp tg
         INNER JOIN dbo.ToolgroupToeApplication_tmp tgtea
             ON tg.ToolGroupId = tgtea.ToolGroupId
'
EXEC SP_EXECUTESQL @SQL

-- Create Indexes
CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup_One
ON vw_ToolGroup_One(ToolGroupId, ToolGroupName, ToolId, TGPurposeId, TGRoleId);

CREATE UNIQUE CLUSTERED INDEX IDX_view_ToolGroup_Two
ON vw_ToolGroup_Two(ToolGroupId, ToolGroupName);

GO

-- Invoke Query
SELECT * FROM vw_ToolGroup_One
UNION ALL
SELECT * FROM vw_ToolGroup_Two tgt
WHERE NOT EXISTS (  SELECT 1 
                    FROM dbo.PurposeToToolGroup_tmp pttg 
                    WHERE pttg.ToolGroupId = tgt.ToolGroupId)
Gouri Shankar Aechoor
  • 1,561
  • 1
  • 8
  • 8
  • This is the right way to go. Transform and split the query so that most work is in the indexed views. You can transform the NOT EXISTS to a LEFT JOIN. – usr Jul 18 '14 at 11:35