Subqueries are not allowed in this context. Only scalar expressions are allowed.
A computed column is computed from an expression. But the expression cannot be a subquery.
If you need to execute SELECT statement to gather required data for the computed column value expression, then a user-defined function can be used.
You can build a user-defined function which can run SELECT statements.
But if you decide to create or alter your calculated column to be persisted, you may experience some other problems relating with determinism especially for XML data types and XQuery.
You may solve the problem for creating a persisted computed column by defining the user defined function (udf) which is used in the expression by using the "WITH SCHEMABINDING" with user sql function.
Cannot Create Persisted Computed Columns using User-Defined Functions with Select From Other Tables
You can not create calculated columns / computed columns as persisted if there is a user-defined function in the computed column expression and this function runs SELECT queries from database tables.
This is because if you are using a user defined sql function in the calculated column expression, to make the computed column persisted, udf must be deterministic.
For determinism for such a sql function, you must define the user defined t-sql function with "WITH SCHEMABINDING" .
In this case, schema binding will require the table names in two-part format.
And when you alter the sql function with tables names in two part format including the schema name and "WITH SCHEMABINDING" hint, this time the function will not be able for use in an expression of a persisted column.
Because then the column will be defined with user data access which is not suitable for a persisted computed column.
T-SQL Computed Column Sample Codes for SQL Server Tables
Here you can find samples where computed column properties are being illustrated with sample t-sql code.
CREATE TABLE Authors
(
AuthorId int IDENTITY(1,1) NOT NULL,
FirstName nvarchar(100),
LastName nvarchar(100),
FullName AS (FirstName + SPACE(1) + LastName) -- computed column
)
The above table sample has the FullName computed column defined as the concatenation of two other column values in the same table.
This is a simple expression sample. We define the computed column by "AS" clause and see that we do not define a column type since the type is defined implicitly by the calculation expression.
INSERT INTO Authors (FirstName, LastName) VALUES (N'Roger', N'Wolter')
INSERT INTO Authors (FirstName, LastName) VALUES (N'Dejan', N'Sarka')
SELECT * FROM Authors
Let's add an other computed column where a CASE expression is used in the definition of the calculated column value.
ALTER TABLE Authors ADD FullName2 AS (CASE WHEN AuthorId < 3 THEN LastName + SPACE(1) + FirstName ELSE FirstName + SPACE(1) + LastName END)
Now insert some data into the sql table, and see the results.
INSERT INTO Authors (FirstName, LastName) VALUES (N'Itzik', N'Ben-Gan')
SELECT * FROM Authors
Let's now create a subsidiary SQL Server database table which we will use in our following samples.
Create Table DBParameters (
ParameterId int Identity(1,1) NOT NULL,
ParameterDescription nvarchar(1000),
ParameterValue nvarchar(1000)
)
GO
insert into DBParameters select N'First Name is before Last Name', N'1'
And now we will try to use a sub-select query in the computed column expression.
ALTER TABLE Authors ADD FullName3 AS (CASE WHEN EXISTS(SELECT * FROM DBParameters) THEN LastName + SPACE(1) + FirstName ELSE FirstName + SPACE(1) + LastName END)
And the error that the SQL Engine will throw is:
Msg 1046, Level 15, State 1, Line 1
Subqueries are not allowed in this context. Only scalar expressions are allowed.
Since subselects are not allowed, I will create a user-defined function to implement the same control.
CREATE FUNCTION GetBooleanParameterValue(
@ParameterId as int
) RETURNS bit
AS
BEGIN
DECLARE @result bit
SELECT @result = CAST(ParameterValue AS bit) FROM DBParameters WHERE ParameterId = @ParameterId
RETURN @result
END
GO
SELECT dbo.GetBooleanParameterValue(1)
Now, I'll try using the user-defined function instead of the sub-select query.
ALTER TABLE Authors ADD FullName3 AS (CASE WHEN dbo.GetBooleanParameterValue(1) = 1 THEN (FirstName + SPACE(1) + LastName) ELSE (LastName + SPACE(1) + FirstName) END)
UPDATE DBParameters SET ParameterValue = 1 WHERE ParameterId = 1
SELECT FirstName, LastName, FullName3 FROM Authors
UPDATE DBParameters SET ParameterValue = 0 WHERE ParameterId = 1
SELECT FirstName, LastName, FullName3 from Authors
The first select displays the firstname before the lastname in the fullname.
And after the parameter table is updated, the second select displays the lastnames before the last.
Let's create an other table which will help us in the next computed columns.
CREATE TABLE [Author Expertise]
(
Id int Identity(1,1) NOT NULL,
AuthorId int,
Expertise nvarchar(1000) NOT NULL
)
GO
INSERT INTO [Author Expertise] SELECT 1, N'T-SQL'
INSERT INTO [Author Expertise] SELECT 1, N'CLR'
INSERT INTO [Author Expertise] SELECT 2, N'CLR'
INSERT INTO [Author Expertise] SELECT 2, N'XML'
INSERT INTO [Author Expertise] SELECT 3, N'T-SQL'
INSERT INTO [Author Expertise] SELECT 3, N'CLR'
INSERT INTO [Author Expertise] SELECT 3, N'Service Broker'
GO
CREATE FUNCTION GetAuthorExpertiseCount(
@AuthorId as int
) RETURNS int
AS
BEGIN
DECLARE @result int
SELECT @result = COUNT(*) FROM [Author Expertise] WHERE AuthorId = @AuthorId
RETURN @result
END
GO
SELECT dbo.GetAuthorExpertiseCount(3)
ALTER TABLE Authors ADD Expertise_Topics_Count AS ( dbo.GetAuthorExpertiseCount(AuthorId) )
SELECT * FROM Authors
ALTER TABLE Authors ADD BooksInfo XML
GO
UPDATE Authors
SET BooksInfo = N'
Microsoft SQL Server 2005
Microsoft SQL Server 2008
'
WHERE AuthorId = 3
GO
CREATE FUNCTION GetLastBookTitle (
@BooksInfo XML
) RETURNS nvarchar(1000)
AS
BEGIN
DECLARE @Title nvarchar(1000)
select
@Title = i.value('@title', 'nvarchar(1000)')
from @BooksInfo.nodes('/books/book') n(i)
order by CONVERT(datetime, i.value('./publishdate[1]/.', 'nvarchar(1000)'), 103) desc
RETURN @Title
END
GO
ALTER TABLE Authors ADD LastBookTitle AS dbo.GetLastBookTitle(BooksInfo)
GO
SELECT AuthorId, Fullname3, BooksInfo, LastBookTitle FROM Authors
ALTER TABLE Authors ADD BirthDate datetime
GO
UPDATE Authors SET BirthDate = '19700101' WHERE AuthorId = 1
GO
ALTER TABLE Authors ADD Age AS DATEDIFF(yy, BirthDate, GETDATE())
Let's add an other age table which is depending on another calculated column.
ALTER TABLE Authors ADD Age2 AS DATEADD(yy, Age, BirthDate)
Unfortunately, a computed column can not be based upon another calculated field.
The SQL Server error message will be:
Msg 1759, Level 16, State 0, Line 1
Computed column 'Age' in table 'Authors' is not allowed to be used in another computed-column definition.
Also a user- defined function which is using the computed table column in its t-sql code cannot be used in another computed column expression.
CREATE FUNCTION TestUDF(
@Age int,
@BirthDate datetime
) RETURNS datetime
AS
BEGIN
RETURN DATEADD(yy, @Age, @BirthDate)
END
GO
ALTER TABLE Authors ADD Age2 AS dbo.TestUDF(Age, BirthDate)
The SQL Engine will throw the following error message in this ALTER TABLE command.
Msg 1759, Level 16, State 0, Line 1
Computed column 'Age' in table 'Authors' is not allowed to be used in another computed-column definition.
What about if we want to define the computed column as PERSISTED column in order to create indexes on these columns.
ALTER TABLE Authors ADD LastBookTitlePersisted AS dbo.GetLastBookTitle(BooksInfo) Persisted
Msg 4936, Level 16, State 1, Line 1
Computed column 'LastBookTitlePersisted' in table 'Authors' cannot be persisted because the column is non-deterministic.
The reason while we can not define the LastBookTitlePersisted column as PERSISTED is the computed column is non-deterministic because of the user-defined function dbo.GetLastBookTitle() is an XML based function.
But the solution for this problem is simple for this udf.
If we alter the procedure by adding the "WITH SCHEMABINDING" hint, we can manage creating the related column persisted.
CREATE FUNCTION GetLastBookTitle2 (
@BooksInfo XML
) RETURNS nvarchar(1000)
WITH SCHEMABINDING -- The "WITH SCHEMABINDING" is needed if I want to the column a persisted computed column
AS
BEGIN
DECLARE @Title nvarchar(1000)
select
@Title = i.value('@title', 'nvarchar(1000)')
from @BooksInfo.nodes('/books/book') n(i)
order by CONVERT(datetime, i.value('./publishdate[1]/.', 'nvarchar(1000)'), 103) desc
RETURN @Title
END
Now we can successfully create the column as persisted.
ALTER TABLE Authors ADD LastBookTitlePersisted AS dbo.GetLastBookTitle2(BooksInfo) Persisted