SQL Server Indexed View Error - sql-server

I realize this is a very contrived example, but I've simplified the full version down to the following which demonstrates the problem:
CREATE VIEW model.Appointments_Partition1
WITH SCHEMABINDING AS
SELECT CONVERT(varchar(15), AppointmentId) as Id,
ap.AppTypeId as AppointmentTypeId,
ap.Duration as DurationMinutes,
ap.AppointmentId as EncounterId,
COUNT_BIG(*) as __count_big
FROM dbo.Appointments ap
JOIN dbo.PracticeCodeTable pct ON SUBSTRING(pct.Code, 1, 1) = ap.ScheduleStatus
AND pct.ReferenceType = 'AppointmentStatus'
WHERE ap.AppTime > 0
GROUP BY CONVERT(varchar(15), AppointmentId), ap.AppTypeId, ap.Duration, ap.AppointmentId
CREATE UNIQUE CLUSTERED INDEX [IX_Appointments_Partition1_Id]
ON model.Appointments_Partition1 ([Id]);
I get:
Msg 8668, Level 16, State 0, Line 12
Cannot create the clustered index 'IX_Appointments_Partition1_Id' on view 'PracticeRepository.model.Appointments_Partition1' because the select list of the view contains an expression on result of aggregate function or grouping column. Consider removing expression on result of aggregate function or grouping column from select list.
I'm including count_big...so why is the group by a problem?....and how can I resolve the error?

Here is the same error message with some boolean logic applied to it:
Cannot create the clustered index '...' on view '...' because the
select list of the view contains an expression on a grouping column.
Consider removing expression on a grouping column from the select list.
You need to remove the CONVERT in CONVERT(varchar(15), AppointmentId)

I find this reason on one of the blogs, seems reasonable to me
No, you can't use schema binding on a view that has an aggregate. And you can't index a view unless you use schema binding. You also can't bind an index that uses outer or left joins. Basically, you can only bind a view that contains a simple select statement.
http://www.tek-tips.com/viewthread.cfm?qid=1401646
You can go through the blog and see if it exactly matched your scenario.
http://technet.microsoft.com/en-us/library/cc917715.aspx
If you want to build index on views, then you must create views with schema binding, in the above link it is explained in detail. Go through the section of Design Considerations

Related

How to use a recursive CTE in a check constraint?

I'm trying to create a check constraint on a table so that ParentID is never a descendant of current record
For instance, I have a table Categories, with the following fields ID, Name, ParentID
I have the following CTE
WITH Children AS (SELECT ID AS AncestorID, ID, ParentID AS NextAncestorID FROM Categories UNION ALL SELECT Categories.ID, Children.ID, Categories.ParentID FROM Categories JOIN Children ON Categories.ID = Children.NextAncestorID) SELECT ID FROM Children where AncestorID =99
The results here are correct, but when I try to add it as a constraint to the table like this:
ALTER TABLE dbo.Categories ADD CONSTRAINT CK_Categories CHECK (ParentID NOT IN(WITH Children AS (SELECT ID AS AncestorID, ID, ParentID AS NextAncestorID FROM Categories UNION ALL SELECT Categories.ID, Children.ID, Categories.ParentID FROM Categories JOIN Children ON Categories.ID = Children.NextAncestorID) SELECT ID FROM Children where AncestorID =ID))
I get the following error:
Incorrect syntax near the keyword 'with'. If this statement is a
common table expression, an xmlnamespaces clause or a change tracking
context clause, the previous statement must be terminated with a
semicolon.
Adding a semicolon before the WITH, didn't help.
What would be the correct way to do this?
Thanks!
Per the SQL Server documentation on column constraints:
CHECK
Is a constraint that enforces domain integrity by limiting the possible values that can be entered into a column or columns.
logical_expression
Is a logical expression used in a CHECK constraint and returns TRUE or FALSE. logical_expression used with CHECK constraints cannot reference another table but can reference other columns in the same table for the same row. The expression cannot reference an alias data type.
(The above was quoted from the SQL Server 2017 version of the documentation, but the general principle applies to all previous versions as well, and you didn't state what version you are working with.)
The important part here is the "cannot reference another table, but can reference other columns in the same table for the same row" (emphasis added). A CTE would count as another table.
As such, you can't have a complex query like a CTE used for a CHECK constraint.
Like Saman suggested, if you want to check against existing data in other rows, and it must be in the DB layer, you could do it as a trigger.
However, triggers have their own drawbacks (e.g. issues with discoverability, behavior that is unexpected by those who are unaware of the trigger's presence).
As Sami suggested in their comment, another option is a UDF, but that's not w/o its own issues, potentially with both performance and stability according to the answers on this question about this approach in SQL Server 2008. It's probably still applicable to later versions as well.
If possible, I would say it's usually best to move that logic into the application layer. I see from your comment that you already have "client-side" validation. If there is an app server between that client and the database server (such as in a web app), I would suggest putting that additional validation there (in the app server) instead of in the database.

Order BY is not supported in view in sql server

i am trying to create a view in sql server.
create view distinct_product as
select distinct name from stg_user_dtlprod_allignmnt_vw order by name;
this is showing an error.
error message is:
The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified.
plz help me out where i am wrong.
You could use TOP with a number that is greater than the number of records:
CREATE VIEW [dbo].[distinct_product]
AS
SELECT DISTINCT TOP 10000000 name
FROM stg_user_dtlprod_allignmnt_vw
ORDER BY name
You cannot use TOP 100 PERCENT since the optimizer recognizes that TOP 100 PERCENT qualifies all rows and does not need to be computed at all, so the ORDER BY wouldn't be guaranteed.
A view cannot be sorted with an ORDER BY clause. You need to put the ORDER BY clause into any query that references the view.
A view is not materialized - the data isn't stored, so how could it be sorted? A view is kind of like a stored procedure that just contains a SELECT with no parameters... it doesn't hold data, it just holds the definition of the query. Since different references to the view could need data sorted in different ways, the way that you do this - just like selecting from a table, which is also an unsorted collection of rows, by definition - is to include the order by on the outer query.
You can't order a view like that when it's created as the message states, unless you follow the other answers from Tim / Raphael, but you can order results selected from a view:
So create it in step 1:
create view distinct_product as
select distinct name
from stg_user_dtlprod_allignmnt_vw
Then order it when you retrieve data:
select *
from distinct_product
order by name

Hierarchical SQL select-query

I'm using MS SqlServer 2008. And I have a table 'Users'. This table has the key field ID of bigint. And also a field Parents of varchar which encodes all chain of user's parent IDs.
For example:
User table:
ID | Parents
1 | null
2 | ..
3 | ..
4 | 3,2,1
Here user 1 has no parents and user 4 has a chain of parents 3->2->1. I created a function which parses the user's Parents field and returns result table with user IDs of bigint.
Now I need a query which will select and join IDs of some requested users and theirs parents (order of users and theirs parents is not important). I'm not an SQL expert so all I could come up with is the following:
WITH CTE AS(
SELECT
ID,
Parents
FROM
[Users]
WHERE
(
[Users].Name = 'John'
)
UNION ALL
SELECT
[Users].Id,
[Users].Parents
FROM [Users], CTE
WHERE
(
[Users].ID in (SELECT * FROM GetUserParents(CTE.ID, CTE.Parents) )
))
SELECT * FROM CTE
And basically it works. But performance of this query is very poor. I believe WHERE .. IN .. expression here is a bottle neck. As I understand - instead of just joining the first subquery of CTE (ID's of found users) with results of GetUserParents (ID's of user parents) it has to enumerate all users in the Users table and check whether the each of them is a part of the function's result (and judging on execution plan - Sql Server does distinct order of the result to improve performance of WHERE .. IN .. statement - which is logical by itself but in general is not required for my goal. But this distinct order takes 70% of execution time of the query). So I wonder how this query could be improved or perhaps somebody could suggest some another approach to solve this problem at all?
Thanks for any help!
The recursive query in the question looks redundant since you already form the list of IDs needed in GetUserParents. Maybe change this into SELECT from Users and GetUserParents() with WHERE/JOIN.
select Users.*
from Users join
(select ParentId
from (SELECT * FROM Users where Users.Name='John') as U
cross apply [GetDocumentParents](U.ID, U.Family, U.Parents))
as gup
on Users.ID = gup.ParentId
Since GetDocumentParents expects scalars and select... where produces a table, we need to apply the function to each row of the table (even if we "know" there's only one). That's what apply does.
I used indents to emphasize the conceptual parts of the query. (select...) as gup is the entity Users is join'd with; (select...) as U cross apply fn() is the argument to FROM.
The key knowledge to understanding this query is to know how the cross apply works:
it's a part of a FROM clause (quite unexpectedly; so the syntax is at FROM (Transact-SQL))
it transforms the table expression left of it, and the result becomes the argument for the FROM (i emphasized this with indent)
The transformation is: for each row, it
runs a table expression right of it (in this case, a call of a table-valued function), using this row
adds to the result set the columns from the row, followed by the columns from the call. (In our case, the table returned from the function has a single column named ParentId)
So, if the call returns multiple rows, the added records will be the same row from the table appended with each row from the function.
This is a cross apply so rows will only be added if the function returns anything. If this was the other flavor, outer apply, a single row would be added anyway, followed by a NULL in the function's column if it returned nothing.
This "parsing" thing violates even the 1NF. Make Parents field contain only the immediate parent (preferably, a foreign key), then an entire subtree can be retrieved with a recursive query.

SQL Server Indexed View Column Precision

I'm trying to create an indexed view and get the following error creating the index:
Cannot create index on view
....'
because column 'Amount' that is referenced by the view in the
WHERE or GROUP BY clause is imprecise. Consider eliminating the column
from the view, or altering the column to be precise.
The column in question has a data type of real which I guess is the problem?
What's the appropriate way of resolving this? Can I do a convert in the view SQL to eliminate the "impreciseness"?
The view SQL is specified below:
EXEC('
CREATE VIEW model.ReceivableBillableParties
WITH SCHEMABINDING
AS
SELECT pf.Id AS Id
, pf.InsuranceId AS InsuranceId
, pf.FinancialInsType AS InsuranceType
, pr.ReceivableId
FROM dbo.Receivables pr
INNER JOIN dbo.Demographics pd ON pd.PersonId = pr.PersonId
INNER JOIN dbo.Appointments ap ON ap.AppointmentId = pr.AppointmentId
INNER JOIN dbo.Financiasl pf ON pf.PersonId = pf.PersonId
INNER JOIN dbo.PracticeInsurers pri ON pri.InsurerId = pf.FinancialInsurerId
WHERE pri.Amount = 0
')
EXEC('
CREATE UNIQUE CLUSTERED INDEX [IX_ReceivableBillableParties]
ON model.ReceivableBillableParties ([Id]);
')
The documentation does indicate that the problem lies with the real data type (see Precision Requirements). If you want to use that column in the WHERE clause of your view, and index that view, you'll need to alter the column to a precise data type (i.e., DECIMAL(9, 2)).
EDIT
This documentation provides a clearer explanation for why this restriction exists. From the section "Deterministic Functions":
Even if an expression is deterministic, if it contains float
expressions, the exact result may depend on the processor architecture
or version of microcode. To ensure data integrity, such expressions
can participate only as non-key columns of indexed views.
Deterministic expressions that do not contain float expressions are
called precise. Only precise deterministic expressions can participate
in key columns and in WHERE or GROUP BY clauses of indexed views.
Hope that helps.

SQL Updatable View with joined tables

I have a view that looks similar to this,
SELECT dbo.Staff.StaffId, dbo.Staff.StaffName, dbo.StaffPreferences.filter_type
FROM dbo.Staff LEFT OUTER JOIN
dbo.StaffPreferences ON dbo.Staff.StaffId = dbo.StaffPreferences.StaffId
I'm trying to update StaffPreferences.filter_type using,
UPDATE vw_Staff SET filter_type=1 WHERE StaffId=25
I have read this in an MSDN article,
Any modifications, including UPDATE, INSERT, and DELETE statements,
must reference columns from only one base table.
Does this mean that I can only update fields in dbo.Staff (which is all I can currently achieve) In this context does the definition of 'base table' not extend to any subsequently joined tables?
Your statement should work just fine since you are only modifying column(s) from one table (StaffPreferences).
If you tried to update a columns from different tables in the same update statement you would get an error.
Msg 4405, Level 16, State 1, Line 7
View or function 'v_ViewName' is not updatable because the modification affects multiple base tables.
The rules for updatable join views are as follows:
General Rule
Any INSERT, UPDATE, or DELETE operation on a join view can modify only
one underlying base table at a time.
UPDATE Rule All updatable columns of a join view must map to
columns of a key-preserved table. See "Key-Preserved Tables" for a
discussion of key-preserved tables. If the view is defined with the
WITH CHECK OPTION clause, then all join columns and all columns of
repeated tables are non-updatable.
DELETE Rule
Rows from a join view can be deleted as long as there is exactly one
key-preserved table in the join. If the view is defined with the WITH
CHECK OPTION clause and the key preserved table is repeated, then the
rows cannot be deleted from the view.
INSERT Rule An INSERT statement must not explicitly or
implicitly refer to the columns of a nonkey preserved table. If the
join view is defined with the WITH CHECK OPTION clause, INSERT
statements are not permitted.
http://download.oracle.com/docs/cd/B10501_01/server.920/a96521/views.htm#391
I think you can see some of the problems that might occur if there's a row in Staff with StaffId 25, but no matching row in StaffPreferences. There are various right things you could do (preserve the appearance that this is a table, perform an insert in StaffPreferences; reject the update; etc).
I think at this point, the SQL Server engine will give up, and you'll have to write a trigger that implements the behaviour you want (whatever that may be. You need to consider all of the cases for the join working/not working)
Here is how I solved it.
In my case it was table, not a view, but I needed to find the schema id that referenced the table in the data construction in a reference table, say called our_schema.
I ran the following:
select schemaid from our_schema where name = "MY:Form"
This gave me the id as 778 (example)
Then I looked where this ID was showing up with a prefix of T, B, or H.
In our case we have Table, Base and History tables where the data is stored.
I then ran:
delete from T778
delete from B778
delete from H778
This allowed me to delete the data and bypass that restriction.

Resources