Cannot use WITH statement two times - sql-server

I wan't to create a view that is constructed like this:
(simplified)
Create VIEW viewAll AS
With TempLevel1 AS
(
SELECT statement
)
With TempLevel2 AS (SELECT * from TempLevel1)
SELECT * from TempLevel2
The problem is that I cannot use With statement like this because of
the following error:
Incorrect syntax near the keyword 'With'.
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.
I have to specify that the SELECT queries are way more complex and I do have to use With two times.
Would it be a better practice to create the first with statement as another view like viewTempLevel1 (and use it in the With TempLevel2 statement)?

From the documentation for Common table Expressions (CTE), you can
Use a comma to separate multiple CTE definitions
Example is (taken straight out of the docs)
WITH Sales_CTE (SalesPersonID, TotalSales, SalesYear)
AS
-- Define the first CTE query.
(
SELECT SalesPersonID, SUM(TotalDue) AS TotalSales, YEAR(OrderDate) AS SalesYear
FROM Sales.SalesOrderHeader
WHERE SalesPersonID IS NOT NULL
GROUP BY SalesPersonID, YEAR(OrderDate)
)
, -- Use a comma to separate multiple CTE definitions.
-- Define the second CTE query, which returns sales quota data by year for each sales person.
Sales_Quota_CTE (BusinessEntityID, SalesQuota, SalesQuotaYear)
AS
(
SELECT BusinessEntityID, SUM(SalesQuota)AS SalesQuota, YEAR(QuotaDate) AS SalesQuotaYear
FROM Sales.SalesPersonQuotaHistory
GROUP BY BusinessEntityID, YEAR(QuotaDate)
)
-- Define the outer query by referencing columns from both CTEs.
SELECT SalesPersonID...
In your case, the syntax would be...
With TempLevel1 AS
( SELECT statement [...]),
TempLevel2 AS
(SELECT * from TempLevel1)
SELECT * from TempLevel2

You don't need to repeat the WITH keyword. Separate the CTE expressions by comma:
With CTE_Level1 AS
(
SELECT statement
),
CTE_Level2 AS
(
SELECT * from CTE_Level1
)
SELECT * from CTE_Level2

Related

Table Valued Function with Recursive CTE

Just for fun, I’m trying to write a table valued function to generate a table of dates. For testing purposes, I am hard-coding values which should be passed in variables.
By itself, this works:
WITH cte AS (
SELECT cast('2021-10-01' AS date) AS date
UNION ALL
SELECT dateadd(day,1,date) FROM cte WHERE date<current_timestamp
)
SELECT * FROM cte OPTION(maxrecursion 0);
Note the OPTION at the end.
As a function, it won’t work unless I remove the OPTION clause at the end:
CREATE FUNCTION dates(#start date, #rows INT) RETURNS TABLE AS
RETURN
WITH cte AS (
SELECT cast('2021-10-01' AS date) AS date
UNION ALL
SELECT dateadd(day,1,date) FROM cte WHERE date<current_timestamp
)
SELECT * FROM cte -- OPTION(maxrecursion 0)
;
For the test data, that’s OK, but it will certainly fail if I give it date at the beginning of the year, since it involves more than 100 recursions.
Is there a correct syntax for this, or is it another Microsoft Quirk which needs a workaround?
I think this may be the answer.
In an answer to another question, #GarethD states:
If you think of a view more as a stored subquery than a stored query … and remember that its definition is expanded out into the main query …
(Incorrect Syntax Near Keyword 'OPTION' in CTE Statement).
If that’s the case, a view can’t include anything that can’t be included in a subquery. That includes the ORDER BY clause and hints such as OPTION.
I have yet to make sure, but I can guess that the same goes for a Table Valued Function. If so, the answer is no, there is no way include the OPTION clause.
Note that other DBMSs are more accommodating in what can be included in subqueries, so I don’t imagine that they have the same limitations.
The solution is to use a multi-statement Table Valued Function:
DROP FUNCTION IF EXISTS dates;
GO
CREATE FUNCTION dates(#start date, #end date) RETURNS #dates TABLE(date date) AS
BEGIN
WITH cte AS (
SELECT #start AS date
UNION ALL
SELECT dateadd(day,1,date) FROM cte WHERE date<#end
)
INSERT INTO #dates(date)
SELECT date FROM cte OPTION(MAXRECURSION 0)
RETURN;
END;
GO
SELECT * FROM dates('2020-01-01','2021-01-01');
Inline Table Valued Functions are literally inlined, and clauses such as OPTION can only appear at the very end of an SQL statement, which is not necessarily at the end of the inline function.
On the other hand, a Multi Statement Table Valued function is truly self-contained, so the OPTION clause is OK there.

Nested Subquery vs Derived table

I'm learning SQL Server and have a question between nested Subquery vs Derived table using from clause.
Example for nested Subquery where it is using the from clause.
Example was taken from the link : https://www.tutorialgateway.org/sql-subquery/
USE [SQL Tutorial]
GO
SELECT subquery.FirstName + ' ' + subquery.LastName AS [Full Name]
,subquery.[Occupation]
,subquery.[YearlyIncome]
,subquery.[Sales]
FROM (
SELECT [Id]
,[FirstName]
,[LastName]
,[Education]
,[Occupation]
,[YearlyIncome]
,[Sales]
FROM [Employee Table]
WHERE [Sales] > 500
) AS [subquery]
Example for Derived table where it is using the from clause.
Example was taken from the link : https://www.tutorialgateway.org/sql-derived-table/
USE [SQLTEST]
GO
SELECT *
FROM (
SELECT [EmpID]
,[FirstName]
,[LastName]
,[Education]
,[YearlyIncome]
,[Sales]
,[DeptID]
FROM [EmployeeDetails]
) AS [Derived Employee Details]
WHERE [Sales] > 500
what makes the nested subquery different form the derived table.
Thank you for your time.
A derived table is specifically a subquery that is used in the from clause, that returns a result set with an arbitrary number of columns and rows.
A subquery is more generic and refers to any query-within-a-query. One type of subquery, for instance, is a scalar subquery. Such a subquery returns at most one row and one column. It can be used in select and where (and some other places) where a scalar value can be used. A scalar subquery can also be used in the from clause.

Understanding CTE Semicolon Placement

When I run this CTE in SQL Server it says the syntax is incorrect by the declare statement.
;WITH cte as
(
SELECT tblKBFolders.FolderID
from tblKBFolders
where FolderID = #FolderID
UNION ALL
SELECT tblKBFolders.FolderID
FROM tblKBFolders
INNER JOIN cte
ON cte.FolderID = tblKBFolders.ParentFolderID
)
declare #tblQueryFolders as table (FolderID uniqueidentifier)
insert into #tblQueryFolders
SELECT FolderID From cte;
But if I move the declare to before the CTE, it runs just fine.
declare #tblQueryFolders as table (FolderID uniqueidentifier)
;WITH cte as
(
SELECT tblKBFolders.FolderID
from tblKBFolders
where FolderID = #FolderID
UNION ALL
SELECT tblKBFolders.FolderID
FROM tblKBFolders
INNER JOIN cte
ON cte.FolderID = tblKBFolders.ParentFolderID
)
insert into #tblQueryFolders
SELECT FolderID From cte;
Why is that?
The answer you ask for was given in a comment already: This has nothing to do with the semicolon's placement.
Important: The CTE's WITH cannot follow right after a statement without an ending semicolon. There are many statments, where a WITH-clause would add something to the end of the statement (query hints, the WITH after OPENJSON etc.). The engine would have to guess, whether this WITH adds to the statment before or if it is a CTE's start. That's the reason, why we often see
;WITH cte AS (...)
That's actually the wrong usage of a semicolon. People put it there, just not to forget about it. Anyway it is seen as better style and best practice to end T-SQL statements always with a semicolon (and do not use ;WITH, as it adds an empty statement actually).
A CTE is not much more than syntactical sugar. Putting the CTE's code within a FROM(SELECT ...) AS SomeAlias would be roughly the same. In most cases this would lead to the same execution plan. It helps in cases, where you'd have to write the same FROM(SELECT ) AS SomeAlias in multiple places. And - in general - it makes things easier to read and understand. But it is not - by any means - comparable to a temp table or a table variable. The engine will treat it as inline code and you can use it in the same statement exclusively.
So this is the same:
WITH SomeCTE AS(...some query here...)
SELECT SomeCTE.* FROM SomeCTE;
SELECT SomeAlias.*
FROM (...some query here...) AS SomeAlias;
Your example looks like you think of the CTE as kind of a temp table definition, which you can use in the following statements. But this is not correct.
After the CTE the engine expects another CTE or a final statement like SELECT or UPDATE.
WITH SomeCTE AS(...some query here...)
SELECT * FROM SomeCTE;
or
WITH SomeCTE AS( ...query... )
,AnotherCTE AS ( ...query... )
SELECT * FROM AnotherCTE;
...or another content added with the WITH clause:
WITH XMLNAMESPACES( ...namespace declarations...)
,SomeCTE AS( ...query... )
SELECT * FROM SomeCTE;
All of these examples are one single statement.
Putting a DECLARE #Something in the middle, would break this concept.

CTE inside SQL IF-ELSE structure

I want to do something like this
declare #a int=1
if (#a=1)
with cte as
(
select UserEmail from UserTable
)
else
with cte as
(
select UserID from UserTable
)
select * from cte
This is just the example, my actual query is far more complex. So I don't want to write the SELECT statement inside IF and ELSE statement twice after the CTE.
If possible, find a way to avoid the if statement entirely.
E.g. in such a trivial example as in your question:
;with CTE as (
select UserEmail from UserTable where #a = 1
union all
select UserID from UserTable where #a != 1 or #a is null
)
select /* single select statement here */
It should generally be possible to compose one or more distinct queries into a final UNION ALL cte, instead of using if - after all, both of the queries being combined must have compatible result sets anyway, for your original question to make sense.
You can't do that - the CTE must immediately be followed by exactly one SQL statement that can refer to it. You cannot split the "definition" of the CTE from the statement that uses it.
So you need to do it this way:
declare #a int=1
if (#a=1)
with cte as
(
select UserEmail from UserTable
)
select * from cte
else
with cte as
(
select UserID from UserTable
)
select * from cte
You cannot split the CTE "definition" for its usage (select * from cte)
The with cte (...) select from cte ... is a single statement. Is not 'followed' by a statement, it is part of the statement. You are asking to split the statement in two which is obviously impossible.
As a general rule, SQL is a very unfriendly language for things like DRY and avoiding code repetition. Attempting to make code more maintainable, more readable, or simply trying to save a few keystrokes can (and usually does) result in serious runtime performance penalties (eg. attempting to move the CTE into a table value function UDF). The simplest thing would be to bite the bullet (this time, and in future...) and write the CTE twice. Sometimes it makes sense to materialize the CTE into a #temp table and then operate into on the #temp table, but only sometimes.
This status-quo is unfortunate, but is everything you can expect from design by committee...

SQL Error with Order By in Subquery

I'm working with SQL Server 2005.
My query is:
SELECT (
SELECT COUNT(1) FROM Seanslar WHERE MONTH(tarihi) = 4
GROUP BY refKlinik_id
ORDER BY refKlinik_id
) as dorduncuay
And the error:
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.
How can I use ORDER BY in a sub query?
This is the error you get (emphasis mine):
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.
So, how can you avoid the error? By specifying TOP, would be one possibility, I guess.
SELECT (
SELECT TOP 100 PERCENT
COUNT(1) FROM Seanslar WHERE MONTH(tarihi) = 4
GROUP BY refKlinik_id
ORDER BY refKlinik_id
) as dorduncuay
If you're working with SQL Server 2012 or later, this is now easy to fix. Add an offset 0 rows:
SELECT (
SELECT
COUNT(1) FROM Seanslar WHERE MONTH(tarihi) = 4
GROUP BY refKlinik_id
ORDER BY refKlinik_id OFFSET 0 ROWS
) as dorduncuay
Besides the fact that order by doesn't seem to make sense in your query....
To use order by in a sub select you will need to use TOP 2147483647.
SELECT (
SELECT TOP 2147483647
COUNT(1) FROM Seanslar WHERE MONTH(tarihi) = 4
GROUP BY refKlinik_id
ORDER BY refKlinik_id
) as dorduncuay
My understanding is that "TOP 100 PERCENT" doesn't gurantee ordering anymore starting with SQL 2005:
In SQL Server 2005, the ORDER BY
clause in a view definition is used
only to determine the rows that are
returned by the TOP clause. The ORDER
BY clause does not guarantee ordered
results when the view is queried,
unless ORDER BY is also specified in
the query itself.
See SQL Server 2005 breaking changes
Hope this helps,
Patrick
If building a temp table, move the ORDER BY clause from inside the temp table code block to the outside.
Not allowed:
SELECT * FROM (
SELECT A FROM Y
ORDER BY Y.A
) X;
Allowed:
SELECT * FROM (
SELECT A FROM Y
) X
ORDER BY X.A;
You don't need order by in your sub query. Move it out into the main query, and include the column you want to order by in the subquery.
however, your query is just returning a count, so I don't see the point of the order by.
A subquery (nested view) as you have it returns a dataset that you can then order in your calling query. Ordering the subquery itself will make no (reliable) difference to the order of the results in your calling query.
As for your SQL itself:
a) I seen no reason for an order by as you are returning a single value.
b) I see no reason for the sub query anyway as you are only returning a single value.
I'm guessing there is a lot more information here that you might want to tell us in order to fix the problem you have.
Add the Top command to your sub query...
SELECT
(
SELECT TOP 100 PERCENT
COUNT(1)
FROM
Seanslar
WHERE
MONTH(tarihi) = 4
GROUP BY
refKlinik_id
ORDER BY
refKlinik_id
) as dorduncuay
:)
maybe this trick will help somebody
SELECT
[id],
[code],
[created_at]
FROM
( SELECT
[id],
[code],
[created_at],
(ROW_NUMBER() OVER (
ORDER BY
created_at DESC)) AS Row
FROM
[Code_tbl]
WHERE
[created_at] BETWEEN '2009-11-17 00:00:01' AND '2010-11-17 23:59:59'
) Rows
WHERE
Row BETWEEN 10 AND 20;
here inner subquery ordered by field created_at (could be any from your table)
In this example ordering adds no information - the COUNT of a set is the same whatever order it is in!
If you were selecting something that did depend on order, you would need to do one of the things the error message tells you - use TOP or FOR XML
Try moving the order by clause outside sub select and add the order by field in sub select
SELECT * FROM
(SELECT COUNT(1) ,refKlinik_id FROM Seanslar WHERE MONTH(tarihi) = 4 GROUP BY refKlinik_id)
as dorduncuay
ORDER BY refKlinik_id
For me this solution works fine as well:
SELECT tbl.a, tbl.b
FROM (SELECT TOP (select count(1) FROM yourtable) a,b FROM yourtable order by a) tbl
Good day
for some guys the order by in the sub-query is questionable.
the order by in sub-query is a must to use if you need to delete some records based on some sorting.
like
delete from someTable Where ID in (select top(1) from sometable where condition order by insertionstamp desc)
so that you can delete the last insertion form table.
there are three way to do this deletion actually.
however, the order by in the sub-query can be used in many cases.
for the deletion methods that uses order by in sub-query review below link
http://web.archive.org/web/20100212155407/http://blogs.msdn.com/sqlcat/archive/2009/05/21/fast-ordered-delete.aspx
i hope it helps. thanks you all
For a simple count like the OP is showing, the Order by isn't strictly needed. If they are using the result of the subquery, it may be. I am working on a similiar issue and got the same error in the following query:
-- I want the rows from the cost table with an updateddate equal to the max updateddate:
SELECT * FROM #Costs Cost
INNER JOIN
(
SELECT Entityname, costtype, MAX(updatedtime) MaxUpdatedTime
FROM #HoldCosts cost
GROUP BY Entityname, costtype
ORDER BY Entityname, costtype -- *** This causes an error***
) CostsMax
ON Costs.Entityname = CostsMax.entityname
AND Costs.Costtype = CostsMax.Costtype
AND Costs.UpdatedTime = CostsMax.MaxUpdatedtime
ORDER BY Costs.Entityname, Costs.costtype
-- *** To accomplish this, there are a few options:
-- Add an extraneous TOP clause, This seems like a bit of a hack:
SELECT * FROM #Costs Cost
INNER JOIN
(
SELECT TOP 99.999999 PERCENT Entityname, costtype, MAX(updatedtime) MaxUpdatedTime
FROM #HoldCosts cost
GROUP BY Entityname, costtype
ORDER BY Entityname, costtype
) CostsMax
ON Costs.Entityname = CostsMax.entityname
AND Costs.Costtype = CostsMax.Costtype
AND Costs.UpdatedTime = CostsMax.MaxUpdatedtime
ORDER BY Costs.Entityname, Costs.costtype
-- **** Create a temp table to order the maxCost
SELECT Entityname, costtype, MAX(updatedtime) MaxUpdatedTime
INTO #MaxCost
FROM #HoldCosts cost
GROUP BY Entityname, costtype
ORDER BY Entityname, costtype
SELECT * FROM #Costs Cost
INNER JOIN #MaxCost CostsMax
ON Costs.Entityname = CostsMax.entityname
AND Costs.Costtype = CostsMax.Costtype
AND Costs.UpdatedTime = CostsMax.MaxUpdatedtime
ORDER BY Costs.Entityname, costs.costtype
Other possible workarounds could be CTE's or table variables. But each situation requires you to determine what works best for you. I tend to look first towards a temp table. To me, it is clear and straightforward. YMMV.
On possible needs to order a subquery is when you have a UNION :
You generate a call book of all teachers and students.
SELECT name, phone FROM teachers
UNION
SELECT name, phone FROM students
You want to display it with all teachers first, followed by all students, both ordered by. So you cant apply a global order by.
One solution is to include a key to force a first order by, and then order the names :
SELECT name, phone, 1 AS orderkey FROM teachers
UNION
SELECT name, phone, 2 AS orderkey FROM students
ORDER BY orderkey, name
I think its way more clear than fake offsetting subquery result.
I Use This Code To Get Top Second Salary
I am Also Get Error Like
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.
TOP 100 I Used To Avoid The Error
select * from (
select tbl.Coloumn1 ,CONVERT(varchar, ROW_NUMBER() OVER (ORDER BY (SELECT 1))) AS Rowno from (
select top 100 * from Table1
order by Coloumn1 desc) as tbl) as tbl where tbl.Rowno=2

Resources