How to nest CTE properly - sql-server

This question was asked few other times, but I still did not manage to sort out the right answer or proper way to do this:
...
;WITH CTE AS
(
SELECT * FROM ...
)
SELECT *, [dbo].[udf_BetaInv](A, B, C, D) AS 'Loss'
FROM CTE
WHERE (Loss >= #MinRetention)
This does not work and I cannot create the stored procedure, clearly I cannot use Loss in the WHERE because does not exist in that scope.
I would like to use another CTE to wrap this one so I can put the WHERE on the outer one but not does not seem to work, tried this:
;WITH CTE AS
(
SELECT * FROM ...
)
SELECT *, [dbo].[udf_BetaInv(A, B, C, D) AS 'Loss'
FROM CTE,
RESULTS AS
(SELECT * FROM CTE)
SELECT *
FROM RESULTS
WHERE (Loss >= #MinRetention)
But it does not compile in SQL Server, I get an error that a '(' is misplaces many rows above but has nothing to do, if I remove the second CTE it works fine.
I only want to avoid code duplication, not want to call my [udf_BetaInv] twice in the select and also in the where.

You have an intermediate SELECT that you should not have. This should work:
;WITH CTE AS
(
SELECT * FROM ...
),
RESULTS AS
(
SELECT *, [dbo].[udf_BetaInv(A, B, C, D) AS 'Loss'
FROM CTE
)
SELECT *
FROM RESULTS
WHERE (Loss >= #MinRetention)

Obviously the problem with the first query is that 'Loss' is just a column alias and can't be used in a WHERE clause. You're right that using it in a CTE would avoid duplicating the expression. Here's how you'd do that;
WITH CTE AS
(
SELECT * FROM ...
),
CteWithLoss AS (
SELECT *, [dbo].[udf_BetaInv](A, B, C, D) AS 'Loss'
FROM CTE
)
SELECT *
FROM CteWithLoss
WHERE (Loss >= #MinRetention);
On a side note: See if you can break the habit of starting your CTE definitions with ;WITH and instead get into the habit of ending all your SQL statements with a semi-colon. It's more readable and better practice.

Below is the example of nested CTE.
with cte_data as
(
Select * from [HumanResources].[Department]
),cte_data1 as
(
Select * from cte_data
)
select * from cte_data1

Related

How to filter on set of ID values without using subquery

I was wondering if someone can help me, I need to show this in oracle.
For that I use this select:
SELECT m.idMedicamento, m.nombre, m.precio
FROM medicamento m
WHERE m.idMedicamento IN (
SELECT idMedicamento
FROM (
SELECT idMedicamento
FROM medicamento
ORDER BY precio ASC)
WHERE ROWNUM <=3
)
OR m.idMedicamento IN (
SELECT idMedicamento
FROM (
SELECT idMedicamento
FROM medicamento
ORDER BY precio DESC)
WHERE ROWNUM <=3
)
ORDER BY m.precio DESC;
but the problem is that I can't use subselects I need to use functions or procedures, and I thought in this function:
CREATE OR REPLACE FUNCTION MAXI RETURN FLOAT IS
total INT := 0;
CURSOR ANIO IS
SELECT idMedicamento
FROM medicamento
ORDER BY precio DESC;
a anio%ROWTYPE;
BEGIN
OPEN ANIO;
LOOP
FETCH ANIO INTO a;
EXIT WHEN ANIO%NOTFOUND;
END LOOP;
CLOSE ANIO;
RETURN ROUND(a ,2);
END;
This is a function just for return the maximum, but I can't return the cursor. I dont know if you can understand me, thanks for your time.
No, you cannot return a cursor and use that in a SQL statement WHERE clause the way you seem to want to do.
You could write a function to return the id values you are looking for as a TABLE OF NUMBER but you would still need to use a subquery to access its results in your main query. I.e.,
WHERE idMedicament IN ( SELECT * FROM TABLE(my_custom_function()) )
So, I think a function to returns the values of idMedicamento that you want to filter on is not going to work well with your "no subqueries" limitation.
Alternate approach #1
Incidentally, on Oracle 12c, you can write your query with no subqueries and no functions like this:
SELECT m.idMedicamento, m.nombre, m.precio,
case when rownum() over ( order by idMedicamento asc ) <= 3
or rownum() over ( order by idMedicamento desc ) <= 3 THEN 'Y' ELSE null END include_flag
FROM medicamento m
ORDER BY include_flag nulls last, m.precio DESC
FETCH FIRST 6 ROWS ONLY;
Alternate approach #2
I assume you cannot use subqueries because you are using a tool or a framework that is building your SQL for you. If that is the case, perhaps you cannot use windowing functions (i.e., rownum() over (...)) either. You can get around this by making a view and then writing your query against the view.
Like this:
CREATE OR REPLACE VIEW medicamento_v AS
SELECT m.idMedicamento, m.nombre, m.precio,
rownum() over ( order by idMedicamento asc) asc_order,
rownum() over ( order by idMedicamento desc) desc_order
FROM medicamento m;
SELECT m.idMedicamento, m.nombre, m.precio
FROM medicameno_v m
WHERE ( asc_order <= 3 OR desc_order <= 3 )
ORDER BY m.precio DESC;

Finding point of interest on a square wave using sql

Good day,
I have a sql table with the following setup:
DataPoints{ DateTime timeStampUtc , bit value}
The points are on a minute interval, and store either a 1(on) or a 0(off).
I need to write a stored procedure to find the points of interest from all the data points.
I have a simplified drawing below:
I need to find the corner points only. Please note that there may be many data points between a value change. For example:
{0,0,0,0,0,0,0,1,1,1,1,0,0,0}
This is my thinking atm (high level)
Select timeStampUtc, Value
From Data Points
Where Value before or value after differs by 1 or -1
I am struggling to convert this concept to sql, and I also have a feeling there is an more elegant mathematical solution that I am not aware off. This must be a common problem in electronics?
I have wrapped the table into a CTE. Then, I am joining every row in the CTE to the next row of itself. Also, I've added a condition that the consequent rows should differ in the value.
This would return you all rows where the value changes.
;WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY TimeStampUTC) AS id, VALUE, TIMESTAMPUTC
FROM DataPoints
)
SELECT CTE.TimeStampUTC as "Time when the value changes", CTE.id, *
FROM CTE
INNER JOIN CTE as CTE2
ON CTE.id = CTE2.id + 1
AND CTE.Value != CTE2.Value
Here's a working fiddle: http://sqlfiddle.com/#!6/a0ddc/3
If I got it correct, you are looking for something like this:
with cte as (
select * from (values (1,0),(2,0),(3,1),(4,1),(5,0),(6,1),(7,0),(8,0),(9,1)) t(a,b)
)
select
min(a), b
from (
select
a, b, sum(c) over (order by a rows unbounded preceding) grp
from (
select
*, iif(b = lag(b) over (order by a), 0, 1) c
from
cte
) t
) t
group by b, grp

TSQL self join to get results

I run the following query
Select * From
(
Select
GUID,
MFG_CODE,
STK_NAME,
parentid,
masteritem,
ROW_NUMBER() over(order by guid) r
From Fstock Where MasterItem=1 OR isNull(parentID, '')=''
) a
Where r between 4716 And 4716
And I get following results
GUID MFG_CODE parentid masteritem r
31955 369553 0 1 4717
As you can see GUID 31955 is actually a parentITEM & I need to bring in all the children of this parent item within the same query.
For example if I do:
Select * From Fstock where parentID = 31955
It returns 3 children of it
GUID
31956
31957
31958
So is there a way to combine these two queries together, I only want to return fixed amount of rows using row_number() function, however those returned rows sometimes contain a Parent ITem, I would like to return the children for those parent items as well within same query.
Performance is very important for me.
--- EDIT ----
I got it to work with following query, does anyone have other ideas?
With CTE
As
(
Select
GUID,
Manufacturer,
SELL_PRICE,
MFG_CODE,
parentid,
masteritem,
ROW_NUMBER() over(order by GUID) r
From Fstock Where MasterItem=1 OR isNull(parentID, '')=''
)
Select A.*,F.parentID From
(
Select * From CTE
Where r between 4717 And 6000
) A
Left join Fstock F on F.parentID = A.GUID
Order by A.r
This is crude and untested, but I believe you're looking for a recursive Common Table Expression (CTE) that will combine the parent-child relationships for you. Now, natively, this does not integrate any row limitations you mentioned in terms of returning a "fixed number of rows," which I was not precisely sure how to interpret, but the basic query below should be a start for you.
With Products(GUID, MFG_CODE,STK_NAME, parentid,masteritem)
as
(
Select GUID,MFG_CODE,STK_NAME,parentid,masteritem
from fstock
where masteritem=1 OR isNull(parentID, '')=''
Union all
Select f.GUID,f.MFG_CODE,f.STK_NAME,f.parentid,f.masteritem
from fstock f
inner join products g
on f.parentid=g.guid
)

Getting "Subquery returned more than 1 value error message " while using Case statement inside Where Not In

If I do
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
SELECT t1.nai_grant_number FROM #temp t1
)
This works fine.
But if I do
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
CASE WHEN
#AutoRenewalChk = 1 THEN
(
SELECT t1.nai_grant_number FROM #temp t1
) END
)
getting error
Msg 512, Level 16, State 1, Line 33 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
What is the reason? Cannot we use a case statement like the above?
Thanks
SELECT * FROM cte1 AS c
WHERE NOT EXISTS
(
SELECT 1 FROM #temp AS t1
WHERE t1.nai_grant_number = CASE #AutoRenewalChk
WHEN 1 THEN c.GrantNumber END
);
Here is why NOT IN will not work here. Try these two queries. Would you expect the same result in each case?
SELECT x FROM (SELECT x = 1) AS x WHERE x NOT IN
(SELECT y = 2);
SELECT x FROM (SELECT x = 1) AS x WHERE x NOT IN
(SELECT y = 2 UNION ALL SELECT NULL);
Adding a possible NULL here (which Dan P suggested as an ELSE in his answer, or if #temp can contain even a single row where nai_grant_number is NULL) completely changes the semantics of NOT IN. Therefore just about any time you are thinking about writing a NOT IN query, you should re-think it as a NOT EXISTS (or LEFT OUTER JOIN, or other struct).
Just like the error says, your query iS returning more than one row. If you change your subs select to select top 1... It will work
Try this instead as others have mentioned the CASE When works on a single column or value not a rowset. Assuming c.GrantNumber is never null this should work for you.
SELECT * FROM cte1 c
WHERE c.GrantNumber NOT IN
(
SELECT DISTINCT
CASE WHEN #AutoRenewalChk = 1
THEN t1.nai_grant_number
ELSE NULL
END
FROM #temp t1
)

How to retrieve the total row count of a query with TOP

I have a SQL Server 2008 query
SELECT TOP 10 *
FROM T
WHERE ...
ORDER BY ...
I'd like to get also the total number of the rows. The obious way is to make a second query
SELECT COUNT(*)
FROM T
WHERE ...
ORDER BY ...
Is there an efficient method?
Thanks
Do you want a second query?
SELECT TOP 10
*, foo.bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
OR
DECLARE #bar int
SELECT #bar = COUNT(*) AS bar FROM T WHERE ...
SELECT TOP 10
*, #bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
Or (Edit: using WITH)
WITH cTotal AS
(
SELECT COUNT(*) AS bar FROM T WHERE ...)
)
SELECT TOP 10
*, cTotal .bar
FROM
T
WHERE
...
ORDER BY
...
What is in this answer seems to work:
https://stackoverflow.com/a/19125458/16241
Basically you do a:
SELECT top 100 YourColumns, TotalCount = Count(*) Over()
From YourTable
Where SomeValue = 32
TotalCount will have the total number of rows. It is listed on each row though.
When I tested this the query plan showed the table only being hit once.
Remove the ORDER BY clause from the 2nd query as well.
No.
SQL Server doesn't keep COUNT(*) in metadata like MyISAM, it calculates it every time.
UPDATE: If you need an estimate, you can use statistics metadata:
SELECT rows
FROM dbo.sysindexes
WHERE name = #primary_key,
where #primary_key is your table's primary key name.
This will return the COUNT(*) from last statistics update.
SELECT TOP (2) *,
(SELECT COUNT(*) AS Expr1 FROM T) AS C
FROM T

Resources