Should SQL CTE's hold constant values? - sql

I came across an interesting piece of SQL code in a view today where a CTE was being used to hold constant values used within sub-queries. It made raise an eyebrow as I've never seen this practice before, though I can understand the thought process behind wanting to reduce the duplicate occurrences of a string value . I'm more of a .Net guy, so I would love to hear thoughts from some SQL folks on whether this pattern is a good or bad practice.
WITH myConstants AS (
SELECT 'myValue' AS myValue
)
SELECT
.......
(SELECT .......
WHERE x.myValue = mc.myValue
) AS mySubQuery
FROM
.......
INNER JOIN myConstants mc ON 1=1

You could use a CTE to store a constant in this way for scoping as a kind of "local variable" (though I've never seen one used for this purpose). After you've completed a statement using the CTE that holds the constant, you can't access it later within the same batch (unless your statement stored it somewhere else). A variable on the other hand, you could access repeatedly throughout the same batch.
e.g.
DECLARE #myVar VARCHAR(20) = 'test';
WITH myConstants AS (
SELECT 'myValue' AS myValue
)
SELECT myValue
FROM myConstants
-- can't access myConstants CTE or its content at this point
-- but can access the variable within the same batch...
SELECT #myVar AS MyVar
-- ...as many times as needed
SELECT #myVar AS MyVar
GO

I use a CTE for constant values all the time. And YES, you can use the CTE values just like you would a variable/parameter. Note this query:
WITH x(c1,c2,c3) AS (SELECT 1,2,3)
SELECT x.c2 AS xyz FROM x
UNION ALL
SELECT x.c1 FROM x
EXCEPT
SELECT x.c3 FROM x;
Returns:
xyz
----
2
1
The nuance is that you must include a reference to the CTE each time you do. Each time I "call" a "constant" I must include FROM x.
In short, a CTE is simply cleaner syntax for a subquery. The main difference between the two is that CTEs can be recursive, subqueries can't be. Subqueries, on the other hand, can be correlated. Note these three queries that produce the exact same results and exact same execution plan:
DECLARE #sometable TABLE (SomeId INT);
INSERT #sometable VALUES(1),(1),(3),(4),(5);
-- CTE
WITH myConstants(c1,c2,c3) AS
(
SELECT 1, 2, 3
)
SELECT t.SomeId, Total = COUNT(*)
FROM #sometable AS t
CROSS JOIN myConstants AS m
WHERE t.SomeId IN (c1,c2,c3)
GROUP BY t.SomeId;
-- Subquery
SELECT t.SomeId, Total = COUNT(*)
FROM #sometable AS t
CROSS JOIN (SELECT 1,2,3) AS myConstants(c1,c2,c3)
WHERE t.SomeId IN (c1,c2,c3)
GROUP BY t.SomeId;
-- VALUES Constructor
SELECT t.SomeId, Total = COUNT(*)
FROM #sometable AS t
CROSS JOIN (VALUES(1,2,3)) AS myConstants(c1,c2,c3)
WHERE t.SomeId IN (c1,c2,c3)
GROUP BY t.SomeId;
The benefit of a CTE vs a subquery is when you need to perform nesting. Consider this query (which uses the same temp table from above):
SELECT t.SomeId
FROM
(
SELECT t.SomeId
FROM #sometable AS t
WHERE t.SomeId < 10
) AS logic1
JOIN #sometable AS t
ON logic1.SomeId = t.SomeId
UNION ALL
SELECT TOP(2) logic3.SomeId
FROM
(
SELECT logic2.SomeId
FROM
(
SELECT t.SomeId
FROM
(
SELECT t.SomeId
FROM #sometable AS t
WHERE t.SomeId < 10
) AS logic1
JOIN #sometable AS t
ON logic1.SomeId = t.SomeId
) AS logic2
) AS logic3;
This can be simplified like so:
WITH
logic1 AS
(
SELECT t.SomeId
FROM #sometable AS t
WHERE t.SomeId < 10
),
logic2 AS
(
SELECT t.SomeId
FROM logic1 AS m
JOIN #sometable AS t
ON t.SomeId = m.SomeId
),
logic3 AS
(
SELECT TOP(2) m.SomeId
FROM logic2 AS m
)
SELECT m.SomeId
FROM logic2 AS m
UNION ALL
SELECT m.SomeId
FROM logic3 AS m;
Again, both return the exact same results and produce the exact same execution plan.

Related

Use row value from CTE in function

I'm trying to use row values from CTE expressions to pass to a function.
Something like:
WITH
start_number(x) as (VALUES(1)),
end_number(y) as (VALUES(10)),
z AS (SELECT * FROM generate_series(start_number.x, end_number.y))
SELECT z.*
What's the syntax?
Notes: start_number & end_number may be the result of a query, typically returning a single row.
You need to reference the two CTEs in the FROM clause:
WITH start_number(x) as (
VALUES(1)
), end_number(y) as (
VALUES(10)
)
SELECT *
FROM start_number, end_number, generate_series(start_number.x, end_number.y))
You can simplify that by using only a single CTE for the numbers:
WITH params(start_number, end_number) as (
VALUES (1, 10)
)
SELECT *
FROM params p, generate_series(p.start_number, p.end_number))
You need a from clause so you can get x and y:
WITH start_number(x) as (
VALUES (1)
),
end_number(y) as (
VALUES (10)
),
z AS (
SELECT gs.z
FROM start_number CROSS JOIN
end_number CROSS JOIN LATERAL
generate_series(start_number.x, end_number.y) gs(z)
)
SELECT z.*
FROM z;
Merely defining a CTE does not make it available to subsequent code and CTEs in the query. You need to include the CTE in a FROM clause.
In addition, you should be in the habit of naming the columns in CTEs. In your code z has a column with no name.
The above is one method. You could also use subqueries as well:
WITH start_number(x) as (
VALUES(1)
),
end_number(y) as (
VALUES(10)
),
z AS (
SELECT *
FROM generate_series((SELECT x FROM start_number), (SELECT y FROM end_number))
)
SELECT z.*
FROM z;
Here is a db<>fiddle.

Missing rows when converting select to subquery

I am flabbergasted by these results. When wrapping a query in a sub-query the group by clause suddenly drops all rows with a certain value.
Could anyone help me figure out what this might happen?
Here is my problem in a nutshell, I do not understand last result:
with my_cte as (
select complex stuff from tables
)
select checktype from my_cte group by checktype
This returns 2 rows: PRE,POST.
with my_cte as (
select complex stuff from tables
)
select distinct checktype from my_cte
This also returns 2 rows: PRE,POST.
with my_cte as (
select complex stuff from tables
)
select * from (
select distinct checktype from my_cte
)
This also returns 2 rows: PRE,POST
with my_cte as (
select complex stuff from tables
)
select * from (
select checktype from my_cte group by checktype
)
This only returns 1 rows! PRE. Why?
The same thing happens if I use another CTE instead of a sub-query.
Why would a subquery in oracle suddenly drop all rows of a certain value?
Oracle version:
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
After digging around I found that in my CTEs I use a TABLE function together with a UNION ALL, this seems to be the cause of the trouble:
WITX X AS (
SELECT DISTINCT
T.TS,
TRIM(REGEXP_SUBSTR(TREPSUMMARY, '.+', 1, LEVELS.COLUMN_VALUE)) AS MISSING,
DATE
FROM
BASE_POST_EXCEPTIONS T,
TABLE(CAST(MULTISET(SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= LENGTH (REGEXP_REPLACE( T.CMPLEXTREPSUMMARY, '.+')) + 1) AS ODCINUMBERLIST)) LEVELS
WHERE REGEXP_SUBSTR(T.CMPLEXTREPSUMMARY, '.+', 1, LEVELS.COLUMN_VALUE) LIKE 'my query'
)
Y AS (
SELECT TS, MISSING, DATE FROM G
),
MY_UNION AS (
SELECT * FROM X /* CAUSED TROUBLE SOMEHOW */
UNOIN ALL
SELECT * FROM Y
)
In order to get around the bug I had to hint to the query planner to materilize the tables before the UNION ALL
MY_UNION AS (
SELECT /*+ materialize */ * FROM X
UNOIN ALL
SELECT /*+ materialize */ * FROM Y
)
No idea why this happens. Will try to reverse engineer and create a simple reproducible test case.

CTE inside CTE in SQL Server

Please don't mark this question as duplicate of CTE within a CTE .. I checked that question and answer ... but that answer does not satisfy my need.
I want to run Nested CTE query like this
Drop Table #Temp
Create Table #Temp(name1 text, name2 text)
Insert INTO #Temp Values ('test','test')
Insert INTO #Temp Values ('test','test')
;WITH CTE1 AS (
With CTE2 as ( Select * from #Temp)
)
Select * from CTE1
or
;WITH CTE1 AS (
Select * From (With CTE2 as ( Select * from #Temp))
)
Select * from CTE1
In our structure... the inner CTE2 query have been provided by other system .. so I can't control
inner part of the query... so.. here my duty is only select values from inner query and form new CTE in my system ...
And please imagine this
;WITH CTE1 AS (
"Query Provide by Other System"
)
In some cases the "Query Provide by Other System" start with CTE..this may or may not be the CTE query... that is the exact problem for I can't use like below
;WITH CTE1 AS (
Select * From
)
,With CTE2 as
( Select * from #Temp))
pls help anyone to prcoeed this, I guess my need is too dynamic
Just to have an idea:
;WITH cte1 AS
(
SELECT * FROM ...
),
cte2 as
(
SELECT * FROM ...
),
cte3 as
(
SELECT * FROM ... INNER JOIN cte2 ON...
),
SELECT *
FROM
cte1
INNER JOIN cte3 ON ...
Separate your CTEs with ,s rather than nesting them.
;
WITH
CTE2 AS
(
SELECT * FROM #Temp
)
,
CTE1 AS
(
SELECT * FROM CTE2
)
SELECT
*
FROM
CTE1
EDIT : Following your additional comments
As I understand it, you are being provided with a system generated query that you then want to embed in another query. Sometimes that system generated query uses a CTE, sometimes it doesn't; you don't know in advance the format of that query.
Unfortunately for you this means that you can not embed this within another CTE.
One option could be to use real views.
CREATE VIEW xxx AS
<system generated code here>
;
SELECT
*
FROM
xxx
;
You do then, however, have to be very careful about concurrency; two concurrent users trying to create the same view with the same name.
The better solution would be to approach the vendor of the system with is creating the system generated query and ask them how they propose you use it.
;with BASE AS (
SELECT * FROM table1
), BASE2 AS (
SELECT * from table2
), BASE3 AS (
SELECT * FROM table3
) SELECT * FROM BASE INNER JOIN BASE3 ...
I guess this is what you are trying to do.
If your system generated query uses db qualified object names you can hack this by using OPENQUERY:
WITH CTE AS
( SELECT *
FROM OPENQUERY([Your Server], 'Query Provide by Other System')
)
SELECT *
FROM CTE;
You may need to configure your server for data access:
EXEC sp_serveroption 'your server', 'DATA ACCESS', TRUE;

Can use select into with multiple cte

Can use select into with multiple cte? for example in the below code the result of the first cte cte_table is inserted into dbo.table1, then the other cte is defined. is this possible?
WITH cte_table
AS
(
SELECT *
FROM dbo.table
)
INSERT INTO dbo.table1
SELECT *
FROM [cte_table]
, cte_table2
AS
(
SELECT *
FROM dbo.table2
)
Chain all your CTEs and THEN do the select into.
WITH First_CTE AS
(
SELECT
Columns
FROM
Schema.Table
WHERE
Conditions
),
Second_CTE AS
(
SELECT
Columns
FROM
Schema.OtherTable
WHERE
Conditions
)
SELECT
Variables
INTO
NewTable
FROM
First_CTE A
JOIN
Second_CTE B
ON
A.MatchVar = B.MatchVar
This can be helpful if you have no need of the CTEs later but prefer a simpler method than subqueries for your ETL.
If your case is Re-usability of the record set, in that case use a Temp Table or Table variable.
e.g.
Select * Into #temp1 From dbo.table
INSERT INTO dbo.table1
SELECT * FROM #temp1
SELECT * FROM #temp1 ..... and do some other re-usability operations.
A chained Cte work as under (just an example)
;With Cte1 As ( Select * from table1)
,Cte2 As (Select * from table2)
select c1.*,c2.*
from cte1 c1, cte2 c2
Hope you understand when to use what and how.
No you can't: you get an error as INTO is not allowed, and, as others have pointed out, it makes sense as the CTE is intended to be a repeatable (and thereby static) reference.
And I recall reading somewhere that is/was in large part syntactical sugar, in so far as the cte is resolved out into a derived table when the sql is executed.
No You cant use select into in CTE. And it actually does not make any sense also.

TSQL: query with optional join

I have a search ui with 3 all optional search criteria.
2 of them are simple criteria for a where statement, that I should be able to solve with this: Stored Procedure with optional "WHERE" parameters.
The last criterion is using full text search where I join the result from ContainsTable. Is there some trick I can use to put everything in one stored procedure? Or should I make two queries, one with the full text search and one without?
Edited: I should have put my query here as well, sorry here it is
select Table1.* from Table1
join
(
select [Key], SUM(Rank) as Rank from
(
SELECT [Key], Rank*3 as Rank FROM Table1ShortSearch(#Keywords) union all
SELECT [Key], Rank*2 as Rank FROM Table1LongSearch(#Keywords)
) as RankingTbl
group by [Key]
) as r
on Table1.Id = r.[Key]
where ( #Status_Id Is Null Or Status_Id = #Status_Id )
order by r.Rank Desc
Thanks.
you can put everything in one stored procedure and have an IF statement - http://msdn.microsoft.com/en-us/library/ms182717.aspx
Original Answer:
You can use an EXISTS function like so:
Select ..
From ..
Where ( #Status_Id Is Null Or Status_Id = #Status_Id )
And (#Date Is Null Or [Date] = #Date )
And (#Criteria Is Null Or Exists(
Select 1
From ContainsTable(TableName, Column1,...,ColumnN, #Criteria..) As SearchTable1
Where SearchTable1.PK = OuterTable.PK
) )
After question revision:
The revised query is of a completely different nature than the original query. In the original query, you simply wanted to return results from Table1 and additionally filter those results if #Keywords was not null. In this query, you are outputting to the SELECT clause the freetext ranking. What would display for the ranking if #Keywords was passed as null?
If freetext ranking is not needed and you simply want to return results if either of the searches on #Keywords finds something, then you would do something like:
Select ...
From Table1
Where ( #Status_Id Is Null Or Status_Id = #Status_Id )
And ...
And (
#Keywords Is Null
Or Exists (
Select 1
From Table1ShortSearch(#Keywords) As T1
Where T1.Key = Table1.Key
)
Or Exists (
Select 1
From Table1LongSearch(#Keywords) As T2
Where T2.Key = Table1.Key
)
)
If you want to display the freetext ranking then your original query or perhaps a CTE would be the solution however you will need to use a Left Join to your subquery if you want to account for #Keywords being null. That would make your query read:
Select ...
From Table1
Left Join (
Select [Key], Sum(Rank) as Rank
From (
Select [Key], Rank*3 As Rank
From Table1ShortSearch(#Keywords)
Union All
Select [Key], Rank*2 As Rank
From Table1LongSearch(#Keywords)
) As RankingTbl
Group By [Key]
) as R
On R.[Key] = Table1.Id
Where ( #Status_Id Is Null Or Status_Id = #Status_Id )
And ...
And ( #Keywords Is Null Or R.Key Is Not Null )
Order By R.Rank Desc