Why isn't a CTE evaluated one time? - sql

If we take the following:
WITH tbl AS (
SELECT RAND() AS a
) SELECT * FROM tbl AS tbl1, tbl AS tbl2
Why does this return two values instead of one? I thought a CTE is basically evaluated one time at the start and then it is used wherever it is needed? Or is my understanding of persistency of a CTE incorrect? Or does the implementation from db to db differ?
Compared with MySQL where it returns a single persisted-value:

According to the docs
BigQuery only materializes the results of recursive CTEs, but does not materialize the results of non-recursive CTEs inside the WITH clause. If a non-recursive CTE is referenced in multiple places in a query, then the CTE is executed once for each reference.
So what you see is expected at least for BigQuery.

Related

SQL - WITH RECURSIVE doesn't work, when there is another query before

I have a (postgresql) query, that goes like
with
recursive account_tree as (<recursive function depending on path>), -- 1
path as (<some other query>) -- 2
select * from account_tree;
This works perfectly fine.
But when I reorder the with queries to that
with
path as (<some other query>), -- 2
recursive account_tree as (<recursive function depending on path>) -- 1
select * from account_tree;
it suddenly shows a syntax error. This behaviour doesn't occur, when I have standard non-recursive queries. With non-recursive queries, I can order them, as they please me.
Why does it do that?
The recursive keyword always goes right behind WITH regardless which of the CTE is actually the recursive one:
with recursive path as (
<some other query>
), account_tree as (
<recursive function depending on path>
)
select *
from account_tree;
recursive refers to the entire with clause. So just use:
with recursive path as (<some other query>), -- 2
What recursive really means is that Postgres will search for "table" names first as CTEs and then as tables/views in the recursive part of the CTE. The issue is really resolving identifiers. It has no effect on other CTEs.

Window functions in view called with "where" gives bad execution plan

I have a view that uses LAG.
CREATE VIEW V_ImportedReadingDay2
AS
SELECT
ID,
PlacementID,
LAG(Reading, 1) OVER (PARTITION BY MeterNumber ORDER BY Date) AS Val
FROM dbo.ImportedReadingDay
If I call it using "WHERE" it gets an execution plan much worse than if just calling the query.
SELECT
ID,
PlacementID,
LAG(Reading, 1) OVER (PARTITION BY MeterNumber ORDER BY Date) AS Val
FROM dbo.ImportedReadingDay
WHERE (PlacementID = 12404)
SELECT *
FROM V_ImportedReadingDay2
WHERE (PlacementID = 12404)
This is a known problem. You can google the problem.
I have found two solutions. Either use a table valued function or move the LAG outside of the view.
BUT I'd like to know if there are any other solutions since none of these work for me since I have to use the view in a client software.
Your two queries aren't logically the same. So, of course, they don't get the same execution plan.
Consider these queries:
select name,LAG(column_id) OVER (ORDER BY system_type_id) as cid
from sys.columns
where name='name'
select * from (
select name,LAG(column_id) OVER (ORDER BY system_type_id) as cid
from sys.columns
) t
where name='name'
Because of the logical processing order of queries, the WHERE clause is processed before the SELECT clause. So, for the first query, we first filter the sys.columns table to only retrieve rows with a particular name, and then we apply the LAG() function just on this filtered set (so, the lagged value will definitely come from another row which matches the filter).
For the second query, we first (logically) process the subquery. We're performing the LAG() function across the whole set of rows (because the subquery doesn't have any filters/WHERE clause) and then (in the outer query) we're filtering the set of rows. Importantly, that means that the lagged value may have been pulled from a row which doesn't match the final filter.
Well, when you use a view, it's similar to my second query. The value of Val retrieved when you use your view is not guaranteed to be from a row with a PlacementID equal to 12404.
This was a simplified view just for this example.
In the real one I partition the LAG.
I found out that partitioning the LAG with the same as used in the "WHERE" (in this case PlacementID) solved the performance issue.

Confused syntax in Where clause

what does the line (rowid,0) mean in the following query
select * from emp
WHERE (ROWID,0) in (
select rowid, mod(rownum,2) from emp
);
i dont get the line WHERE (ROWID,0).
what is it?
thanx in advance
IN clause in Oracle SQL can support column groups. You can do things like this:
select ...
from tab1
where (tab1.col1, tab1.col2) in (
select tab2.refcol1, tab2.refcol2
from tab2
)
That can be useful in many cases.
In your particular case, the subquery use for the second expression mod(rownum,2). Since there is no order by, that means that rownum will be in whichever order the database retrieves the rows - that might be a full table scan or a fast full index scan.
Then by using mod every other row in the subquery gets the value 0, every other row gets the value 1.
The IN clause then filters on second value in the subquery being equal to 0. The end result is that this query retrieves half of your employees. Which half will depend on which access path the optimizer chooses.
Not sure what dialect of sql you're using, but it appears that since the subquery in the IN clause has two columns in the select list, then the (ROWID,0) indicates which columns align with the subquery. I have never seen multiple columns in an IN statment's select list before.
This is a syntax used by some databases (but not all) that allows you to do in with multiple values.
With in, this is the same as:
where exists (select 1
from emp e2
where e2.rowid = emp.rowid and
mod(rownum, 2) = 0
)
I should note that if you are using Oracle (which allows this syntax), then you are using rownum in a subquery with no order by. The results are going to be rather arbitrary. However, the intention seems to be to return every other row, in some sense.

Not Able to Query Multiple Times from Multiple Common Table Expressions (WITH)?

I was doing some querying today in T-SQL, SQL-Server-2008 and stumbled upon something weird that I didn't understand. Using the query windows, I am trying to query from two common table expressions like so (I stripped out a lot of code to make it more obvious what I was doing):
;WITH temp1 AS (SELECT * FROM dbo.Log)
, temp2 AS (SELECT * FROM dbo.SignalCodeItems300_tbl)
SELECT * FROM temp1
SELECT * FROM temp2
However, only one of the select statements will run, the FIRST one. Regardless of which is which, only the first runs. I assume this is some sort of syntax thing that I'm missing maybe? I get the error "Invalid object name 'temp2'".
Could someone shed some light on this problem? Are there any workarounds for this?
No, this works as it should. A CTE (Common Table Expression) is only available for the first statement after the definition. So in other words, after select * from temp1, they both become unavailable.
The fix would be this:
;WITH temp1 AS (SELECT * FROM dbo.Log)
SELECT * FROM temp1
;WITH temp2 AS (SELECT * FROM dbo.SignalCodeItems300_tbl)
SELECT * FROM temp2
You might want to take a look at the MSDN documentation.
Especially:
Multiple CTE query definitions can be defined in a nonrecursive CTE.
The definitions must be combined by one of these set operators:
UNION ALL, UNION, INTERSECT, or EXCEPT.
You cannot mix and match two different schemas though, as this essentially runs as one query.
Use a view or a user-defined, table-valued function to house your query if you don't want to repeat it explicitly.

How to refer to a variable create in the course of executing a query in T-SQL, in the WHERE clause?

What the title says, really.
If I SELECT [statement] AS whatever, why can't I refer to the whatever column in the body of the WHERE clause? Is there some kind of a workaround? It's driving me crazy.
As far as I'm aware, you can't directly do this in SQL Server.
If you REALLY have to use your column alias in the WHERE clause, you can do this, but it seems like overkill to use a subquery just for the alias:
SELECT *
FROM
(
SELECT [YourColumn] AS YourAlias, etc...
FROM Whatever
) YourSubquery
WHERE YourAlias > 2
You're almost certainly better off just using the contents of the original column in your WHERE clause.
It has to do with the way a SELECT statement gets translated into an abstract query tree: the 'whatever' only appears in the query result projection part of the tree, which is above the filtering part of the tree, so the WHERE clause cannot understand the 'whatever'. This is not some internal implementation detail, it is a fundamental behavior of relational queries: the projection of the result occurs after the evaluation of the joins and filters.
IS really trivial to work around the 'problem' by making the hierarchy of the query explicit:
select ...
from (
select [something] as whatever
from ...
) as subquery
WHERE whatever = ...;
A common table expression can also server the same purpose:
with cte as (
select [something] as whatever
from ...)
select ... from cte
WHERE whatever = ...;
It's to do with the order of operations in the select statement. The WHERE clause is evaluated before the SELECT clause so this information isn't available. Although it is available in the ORDER BY clause as this is processed last.
As others have mentioned, a sub-query will get around this problem.