remove subquery from view to make it Indexed view - sql-server

i want to create a Index View for full text search.
the only problem i,m facing with subquery, because index views does not allow subquery.
below is my query
ALTER VIEW [dbo].[Demo] with SCHEMABINDING AS
select distinct a.ID,a.Title, a.Description ,b.Name as Recipe, c.Name as Taste , d.Name as CuisineType,
STUFF((SELECT ',' + Name FROM dbo.Ingredients where ID in (select IngredientID from dbo.listingIngredients
where listingid = a.ID ) FOR XML PATH('')), 1, 1, '') as Ingredients
from dbo.Listing as a
inner join dbo.RecipeType b on a.RecipeTypeID = b.ID
inner join dbo.taste c on a.tasteID = c.ID
inner join dbo.CuisineType d on a.CuisineTypeID = d.ID
inner join dbo.listingIngredients e on a.ID = e.listingID
GO
I,m using subquery to get ingredients as concatenate string from Ingredients table using STUFF.
can some one please let me know how can i remove this subquery and have ingredients as contented string.
please let me know
regards
manish

The XML part of the query will cause problems, even if you did manage to remove the sub-selected.
However, all is not lost. You could rewrite the view into a part that can be indexed and another part that is cheaper, but can't. For example, you could write:
ALTER VIEW [dbo].[Demo_Part] with SCHEMABINDING AS
select a.ID,a.Title
, a.Description
, b.Name as Recipe
, c.Name as Taste
, d.Name as CuisineType
, e.name
from dbo.Listing as a
inner join dbo.RecipeType b on a.RecipeTypeID = b.ID
inner join dbo.taste c on a.tasteID = c.ID
inner join dbo.CuisineType d on a.CuisineTypeID = d.ID
inner join dbo.listingIngredients e on a.ID = e.listingID
GROUP BY a.ID,a.Title
, a.Description
, b.Name as Recipe
, c.Name as Taste
, d.Name as CuisineType
, e.name
Depending on your data model, you may not even need the group by. This view can be indexed
And then write another view that is not indexed, but which replaces your original view
CREATE VIEW [dbo].[Demo]
SELECT ...
STUFF (...)
FROM [dbo].[Demo_Part]
As a meta-answer I would add that if you need to index a view like this (and use the DISTINCT), chances are that your data modeller made a pretty big mistake with the data model or that your data access code is very inefficient. Everything about this smells like you are trying to work around poor coding and modelling practices.

Related

TSQL - Return all record ids and the missing id's we need to populate

I have a job I need to automate to make sure a cache for some entities in my database is populated. I have the query below using CTE and CROSS JOIN but it doesn't run very quickly so I'm sure it can be improved.
The issue:
I have a database of employees
Each employee has a report of data compiled each month.
Each report has a set of 'components' and each of those components 'data' is pulled from an external source and cached in my database
The goal:
I want to set up a job to take a group of component Ids for 'this months report' and pre-cache the data if it doesn't exist.
I need to get a list of employees and the components they are missing in the cache for this months report. I will then set up a CRON job to process the queue.
The Question
My query below is slow - Is there a more efficient way to return a list of employees and the component ids that are missing in the cache?
The current SQL:
declare #reportDate datetime2 = '2019-10-01'; //the report publish date
declare #componentIds table (id int); // store the ids of each cachable component
insert #componentIds(id) values(1),(2),(3),(4),(5);
;WITH cteCounts
AS (SELECT r.Id as reportId, cs.componentId,
COUNT(1) AS ComponentCount
FROM EmployeeReports r
LEFT OUTER JOIN CacheStore cs on r.Id = cs.reportId and cs.componentId in (SELECT id FROM #componentIds)
GROUP BY r.Id, cs.componentId)
SELECT e.Id, e.name, _c.id as componentId, r.Id as reportId
FROM Employees e
INNER JOIN EmployeeReports r on e.Id = r.employeeId and r.reportDate = #reportDate
CROSS JOIN #componentIds _c
LEFT OUTER JOIN cteCounts AS cn
ON _c.Id = cn.componentId AND r.Id = cn.reportId
WHERE cn.ComponentCount is null
2 things I can suggest doing:
Use NOT EXISTS instead of a LEFT JOIN + IS NULL. The execution plan is prone to be different when you tell the engine that you want records that don't have any occurrence in a particular set Vs. joining and making sure that the joined column is null.
SELECT e.Id, e.name, _c.id as componentId, r.Id as reportId
FROM Employees e
INNER JOIN EmployeeReports r on e.Id = r.employeeId and r.reportDate = #reportDate
CROSS JOIN #componentIds _c
WHERE
NOT EXISTS (SELECT 'no record' FROM cteCounts AS cn
WHERE _c.Id = cn.componentId AND r.Id = cn.reportId)
Use temporary tables instead of CTE and/or variable tables. If you have to handle many rows, variable tables don't actually have statistics on and some complex CTE's might actually make lousy execution plans. Try using temporary tables instead of these 2 and see if the performance boosts. Also try creating relevant indexes on them if your row count is high.

Table order change when puting the query result into a new table

when I run my query I get the result I'm after, however when I try to input that into a new table (INTO new_table_name) and review it after, the order of the original query is scrambled.
This is my query. Is it because the INTO-statment is before the ORDER BY-statment?
SELECT t.TABLE_SCHEMA AS [Parent],
t.TABLE_NAME AS [Object],
t.COLUMN_NAME AS [Type],
cd.value AS [Description]
FROM INFORMATION_SCHEMA.COLUMNS t
INTO new_table_name
INNER JOIN syscolumns c
ON c.name = t.COLUMN_NAME
LEFT OUTER JOIN sys.extended_properties cd
ON cd.major_id = c.id
AND cd.minor_id = c.colid
AND cd.name = 'MS_Description'
ORDER BY t.TABLE_NAME
If you want to see the data in order then use Order by while selecting the new table.
Though you used order by while inserting records into table that doesn't mean the records are stored in the data pages in same order or will be retrieved in that order.
Use Order by while selecting to see the result in order
select * from new_table_name
Order by Object

Joining view and tables in sql server

I have the following tables.
Customer Customer_id, Customer_name
Customer_details Customer_id,Phone_no,Address
Order_details Order_id,Customer_id,Order_type
I have created a view as following
Create view Orders_Analysis
Select c.Customer_id,cd.phone_no,od.order_id
From customer c
inner join order_details od
on c. Customer_id=od. Customer_id
Inner join Where c. Customer_id=cd. Customer_id
cd. Customer_id=c.Customer_id
Now using the above view and pre mentioned tables I have to extract only those records in the view which are of a particular order_type.
Can you guys suggest me a method.
First of all, You have some mistake in View, This is the correct View after fix mistakes
Create view Orders_Analysis
Select c.Customer_id,cd.phone_no,od.order_id
From customer c
INNER JOIN order_details od
ON c.Customer_id = od.Customer_id
INNER JOIN Customer_details CD
ON c.Customer_id = cd.Customer_id
Now You want to extract only those records in the view which are of a particular order_type.
Solution One:- Because you dont have column order_type in View then use INNER JOIN
SELECT *
FROM Orders_Analysis OA
INNER JOIN Order_details OD
ON OA.order_id = OD.order_id
WHERE OD.Order_type = your value here
Solution Two:-
Otherwise Add Order-Type column in View
Create view Orders_Analysis
Select c.Customer_id,cd.phone_no,od.order_id,od.order_type
From customer c
INNER JOIN order_details od
ON c.Customer_id = od.Customer_id
INNER JOIN Customer_details CD
ON c.Customer_id = cd.Customer_id
and use
Select * from Orders_Analysis where Order_Type = your value here
You need to add the field, od.order_type, to the Select statement results set in your view definition:
SELECT c.Customer_id,cd.phone_no,od.order_id,od.Order_type
Then, if you run a select against the view, specify a WHERE clause on the order_type field for the value you are looking for.
Update your view and select also order type
Select c.Customer_id,cd.phone_no,od.order_id, od.Order_type
You can now execute select query using the view you created
Select * from Orders_Analysis where Order_Type = "any value"

Refactor SQL to avoid duplicate joins

I need suggestions on how to re-factor my SQL Server query to improve performance. As you can see I am repeating Joins and would like to avoid it. This is just a snippet of my code so I am not including the JOIN ON, WHERE etc.
The task is to display all houses owned by a person in a single row as a concatenated string instead of 1 row for each house owned by a person. the database schema is organized this way that requires me to do multiple joins to get the info I need and unfortunately schema wont change.
select
distinct View1_outer.ID as PersonID
, stuff
(
(select ', ' + Name
from View1 View1_inner
inner join View2 View2_inner
inner join View3 View3_inner
inner join View4 View4_inner
where View1_inner.ID = View1_outer.ID
for XML path('')
)
,1,2,' ') as Houses
from View1 View1_outer
inner join View2 View2_outer
inner join View3 View3_outer
inner join View4 View4_outer
where View1_outer.ID = 'XXX'
This is one way of rewriting my initial query. I don't think I need to repeat the joins in my outer query.
select
distinct View1_outer.ID as PersonID
, stuff
(
(select ', ' + Name
from View1 View1_inner
inner join View2 View2_inner
inner join View3 View3_inner
inner join View4 View4_inner
where View1_inner.ID = View1_outer.ID
for XML path('')
)
,1,2,' ') as Houses
from View1 View1_outer
where View1_outer.ID = 'XXX'

Alternative methodology instead of NOT IN

Employee Table
EmployeeId
E001
E002
E003
E004
Service Table
ServiceDate EmployeeId
4/07/2014 E001
3/07/2014 E002
3/01/2014 E004
I want to list the details of employee(s) who has/have never done any car service.
The result should be:
EmployeeId
E003
I tried outer join LEFT OUTER JOIN but it got messed up.
You can use a LEFT JOIN instead of using NOT IN, like so:
select e.*
from employee e
left join service s on e.employeeid = s.employeeid
where s.employeeid is null
The LEFT JOIN ensures that you get a result set where every employee may or may not have a matching entry in Service table. The WHERE then filters that result set to retain only those employees which do not have a corresponding entry in Service, which is equivalent to your NOT IN method.
You should understand that there is nothing wrong with using a subquery in this case, but using a join may be preferable for performance reasons. This question provides an excellent analysis of joins vs subqueries.
didNot tested, but I think it can work:
select *
from (select distinct E.*, S.employeeid serviceEmployeeid
from employee E left outer join service S on e.employeeid = S.employeeid)
where serviceEmployeeid is null
added distinct for the situation a single employee had multiple services.
I think this would produce your solution.
SELECT EmployeeId
FROM
EMPLOYEE
LEFT OUTER JOIN
SERVICE
ON (EMPLOYEE.EmployeeId = SERVICE.EmployeeId)
WHERE
SERVICE.ServiceId IS NULL
;
SELECT *
FROM Employee e
WHERE (
SELECT COUNT(*)
FROM service s
WHERE e.EmployeeId = s.EmployeeId
) = 0;
I do not recommend doing this, though.
Another solution with better performance (from Conffusion's comment)
SELECT *
FROM Employee e
WHERE NOT EXISTS (
SELECT *
FROM service s
WHERE e.EmployeeId = s.EmployeeId
);

Resources