Using a WITH inside of a RECURSIVE WITH in PostgreSQL [duplicate] - sql

This question already has answers here:
Issue with recursive CTE in PostgreSQL
(1 answer)
How to use multiple CTEs in a single SQL query?
(4 answers)
Closed 9 years ago.
In PostgreSQL, a WITH can be used by another WITH, for example:
WITH num AS (
VALUES (50)
), num2 AS (
SELECT column1 * 2 AS value FROM num
)
SELECT value FROM num2;
And then there are RECURSIVE WITHs, which can be done like so:
WITH RECURSIVE t(n) AS (
VALUES (1)
UNION ALL
SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;
But so far, I have not found a way for a RECURSIVE WITH to use a previous WITH. I would think that it should be something like this:
WITH num AS (
VALUES (50)
), num2 AS (
SELECT column1 * 2 AS value FROM num
), RECURSIVE t(n) AS (
VALUES (1)
UNION ALL
SELECT n+1 FROM t WHERE n < (SELECT * FROM num2)
)
SELECT sum(n) FROM t;
But this does not work. So is there a way to do this? If so, how?

Start with WITH RECURSIVE. You can still add non-recursive CTEs:
WITH RECURSIVE
num AS (VALUES (50))
, num2 AS (SELECT column1 * 2 AS value FROM num)
, t(n) AS (
VALUES (1)
UNION ALL
SELECT n+1 FROM t WHERE n < (SELECT value FROM num2)
)
SELECT sum(n) FROM t;
sqlfiddle
See:
How to use multiple CTEs in a single SQL query?

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.

Must the with recursive statement always be on top of a with statements chain?

Must with recursive always be on top of a with statements chain?
with a as(
values(1)
), recursive t(n) as(
values(1)
union all
select n+1
from t
where n<10
), u as(
select *
from t
)
select *
from u
In this example the with recursive goes wrong, and the statement with u is okay.
Assuming postgresql, since of the 3 tags applied to the question this most closely reflects the syntax you have used - The first CTE doesn't have to be recursive, but if any CTE is recursive you have to define it at the start, so your example would become:
with recursive a as(
values (1)
), t(n) as(
values (1)
union all
select n+1
from t
where n<10
), u as(
select *
from t
)
select *
from u
Example on DB Fiddle
If the SQL Server tag was correct, then the answer is still that the recursive cte does not have to appear first, e.g.
with a as(
select n from (values (1)) n (N)
), t(n) as(
select n from (values (1)) n (N)
union all
select n+1
from t
where n<10
), u as(
select *
from t
)
select *
from u;
SQL Server example

Flattening nested record in postgres

How to flatten the foo column in the outer select (in PostgreSQL)?
WITH RECURSIVE t AS (
SELECT row(d.*) as foo FROM some_multicolumn_table as d
UNION ALL
SELECT foo FROM t WHERE random() < .5
)
SELECT foo FROM t
What I want is to output all the columns (horizontally, i.e. as a row of multiple columns) of some_multicolumn_table in the outer select, not just a single "record" column.
How to do that?
You don't need the ROW constructor there, and so you can expand the record by using (foo).*:
WITH RECURSIVE t AS (
SELECT d as foo FROM some_multicolumn_table as d
UNION ALL
SELECT foo FROM t WHERE random() < .5
)
SELECT (foo).* FROM t;
Although this query could be simple written as:
WITH RECURSIVE t AS (
SELECT d.* FROM some_multicolumn_table as d
UNION ALL
SELECT t.* FROM t WHERE random() < .5
)
SELECT * FROM t;
And I recommend trying to keep it as simple as possible. But I'm assuming it was just an exemplification.

how to count each characters in a string and display each instance in a table format

my table master_schedule
cable_no
D110772
D110773
D110774
D110775
D110776
D110777
D110778
D110779
D110880
I would like to create a loop so that each character in the string counted and displayed
D 9
1 18
2 1
3 1
AND SO ON .......
how can i modify in these sql query mentioned below:
select (sum(LEN(cable_no) - LEN(REPLACE(cable_no, 'D', '')))*2) as FERRUL_qtyx2
from MASTER_schedule
Something like this:
select substring(cable_no, n.n, 1) as letter, count(*) as cnt
from FERRUL_qtyx2 t cross join
(select 1 as n union all select 2 union all select 3 union all select 4 union all
select 5 union all select 6 union all select 7
) n
group by substring(cable_no, n.n, 1);
This creates a sequence of numbers n up to the length of the string. It then uses cross join and substring() to extract the nth character of each cable_no.
In general, this will be faster than doing a union all seven times. The union all approach will typically scan the table 7 times. This will scan the table only once.
you can use recursive common table expression:
with cte(symbol, cable_no) as (
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from Table1
union all
select
left(cable_no, 1), right(cable_no, len(cable_no) - 1)
from cte
where cable_no <> ''
)
select symbol, count(*)
from cte
group by symbol
=> sql fiddle demo
Another approach (made after Gordon Linoff solution):
;with cte(n) as (
select 1
union all
select n + 1 from cte where n < 7
)
select substring(t.cable_no, n.n, 1) as letter, count(*) as cnt
from #Table1 as t
cross join cte as n
group by substring(t.cable_no, n.n, 1);
=> sql fiddle demo

SQL Select 'n' records without a Table

Is there a way of selecting a specific number of rows without creating a table. e.g. if i use the following:
SELECT 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
It will give me 10 across, I want 10 New Rows.
Thanks
You can use a recursive CTE to generate an arbitrary sequence of numbers in T-SQL like so:
DECLARE #start INT = 1;
DECLARE #end INT = 10;
WITH numbers AS (
SELECT #start AS number
UNION ALL
SELECT number + 1
FROM numbers
WHERE number < #end
)
SELECT *
FROM numbers
OPTION (MAXRECURSION 0);
If you have a fixed number of rows, you can try:
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
UNION
SELECT 5
UNION
SELECT 6
UNION
SELECT 7
UNION
SELECT 8
UNION
SELECT 9
UNION
SELECT 10
This is a good way if you need a long list (so you don't need lots of UNIONstatements:
WITH CTE_Numbers AS (
SELECT n = 1
UNION ALL
SELECT n + 1 FROM CTE_Numbers WHERE n < 10
)
SELECT n FROM CTE_Numbers
The Recursive CTE approach - is realy good.
Be just aware of performance difference. Let's play with a million of records:
Recursive CTE approach. Duration = 14 seconds
declare #start int = 1;
declare #end int = 999999;
with numbers as
(
select #start as number
union all
select number + 1 from numbers where number < #end
)
select * from numbers option(maxrecursion 0);
Union All + Cross Join approach. Duration = 6 seconds
with N(n) as
(
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all
select 1 union all select 1 union all select 1 union all select 1
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Table Value Constructor + Cross Join approach. Duration = 6 seconds
(if SQL Server >= 2008)
with N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Recursive CTE + Cross Join approach. :) Duration = 6 seconds
with N(n) as
(
select 1
union all
select n + 1 from N where n < 10
)
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
We will get more amazing effect if we try to INSERT result into a table variable:
INSERT INTO with Recursive CTE approach. Duration = 17 seconds
declare #R table (Id int primary key clustered);
with numbers as
(
select 1 as number
union all
select number + 1 from numbers where number < 999999
)
insert into #R
select * from numbers option(maxrecursion 0);
INSERT INTO with Cross Join approach. Duration = 1 second
declare #C table (Id int primary key clustered);
with N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
)
insert into #C
select top 999999
row_number() over(order by (select 1)) as number
from
N n1, N n2, N n3, N n4, N n5, N n6;
Here is an interesting article about Tally Tables
SELECT 1
UNION
SELECT 2
UNION
...
UNION
SELECT 10 ;
Using spt_values table:
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY number)
FROM [master]..spt_values ORDER BY n;
Or if the value needed is less than 1k:
SELECT DISTINCT n = number FROM master..[spt_values] WHERE number BETWEEN 1 AND 1000;
This is a table that is used by internal stored procedures for various purposes. Its use online seems to be quite prevalent, even though it is undocumented, unsupported, it may disappear one day, and because it only contains a finite, non-unique, and non-contiguous set of values. There are 2,164 unique and 2,508 total values in SQL Server 2008 R2; in 2012 there are 2,167 unique and 2,515 total. This includes duplicates, negative values, and even if using DISTINCT, plenty of gaps once you get beyond the number 2,048. So the workaround is to use ROW_NUMBER() to generate a contiguous sequence, starting at 1, based on the values in the table.
In addition, to aid more values than 2k records, you could join the table with itself, but in common cases, that table itself is enough.
Performance wise, it shouldn't be too bad (generating a million records, it took 10 seconds on my laptop), and the query is quite easy to read.
Source: http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
Using PIVOT (for some cases it would be overkill)
DECLARE #Items TABLE(a int, b int, c int, d int, e int);
INSERT INTO #Items
VALUES(1, 2, 3, 4, 5)
SELECT Items
FROM #Items as p
UNPIVOT
(Items FOR Seq IN
([a], [b], [c], [d], [e]) ) AS unpvt
;WITH nums AS
(SELECT 1 AS value
UNION ALL
SELECT value + 1 AS value
FROM nums
WHERE nums.value <= 99)
SELECT *
FROM nums
Using GENERATE_SERIES - SQL Server 2022
Generates a series of numbers within a given interval. The interval and the step between series values are defined by the user.
SELECT value
FROM GENERATE_SERIES(START = 1, STOP = 10);