How to write a formula using sql recursion - sql

using SQL recursion, I am trying to get a formula from a given set of numbers.
Example, if only 1 number x is provided, the output should be x.
if 2 numbers x and y are provided, then output should be x + y + xy
if 3 numbers are provide, output should be x + y + z + xy + yz + zx + xyz
excerpt:
thanks.
Priya

I'm not 100% sure what you want the result set to really look like. But, you can do this using a recursive CTE:
with vals as (
select *
from (values ('x'), ('y'), ('z')) v(a)
),
cte as (
select convert(varchar(max), a) as a, convert(varchar(max), a) as max_a
from vals
union all
select convert(varchar(max), cte.a + vals.a) as a, convert(varchar(max), vals.a)
from cte join
vals
on vals.a > cte.max_a
)
select cte.a
from cte;
This produces the expressions on each row

Related

Recursive Formula based on previous Row's Result

Let's consider the following query:
with
init as (
select 0.1 as y0
),
cte as (
select 1 as i, 1 as x -- x_1
union all
select 2 as i, 10 as x -- x_2
union all
select 3 as i, 100 as x -- x_3
order by i asc
)
select cte.x, init.y0 -- <- ?
from cte
join init
on true
There is a CTE init specifying an inital value y_0 and a CTE cte specifying rows with a value x and an index i.
My question is whether I can write a select which realizes the following simple, recursive formula.
y_n+1 = y_n + x_n+1
So, the result should be 3 rows with values: 1.1, 11.1, 111.1 (for y_1, y_2, y_3).
Would that be possible?
write a select which realizes the following simple, recursive formula.
y_n+1 = y_n + x_n+1
Consider below
select x, y0 + sum(x) over(order by i) as y
from cte, init
if applied to sample data in your question - output is
Note: the expected result you shown in your question - does not match the formula you provided - so obviously above output is different from one in your question :o)
You need to use the “OVER” statement. You can see more documentation about the syntax.
with
init as (
select 0.1 as y0
),
cte as (
select 1 as ts, 1 as i, 1 as x -- x_1
union all
select 2, 2, 10 as x -- x_2
union all
select 3, 3, 100 as x
union all
select 4, 4, 109 as x -- x_3
union all
select 5, 5, 149 as x
order by i asc
)
SELECT *,init.y0 + SUM(i) OVER(
ORDER BY (ts)
) AS res
FROM cte join init
on true

How to generate combinations

I have a requirement to create a table with an identifier column. The identifier data will be comprised of 3 parts, the first being a letter [A-Z], the second being a number [1-42] and the third being again a number [1-6].
I was wondering the quickest and best way to go about this as I'm really stuck. The output should look like this:
A-1-1
A-1-2
A-1-3
...
Z-42-6
Thanks for your help
You should use CROSS JOIN with derived tables containing all letters/numbers needed
SELECT letters.let + '-' + numbers.num + '-' + numbers2.num
FROM(SELECT 'A' as let UNION ALL SELECT 'B' .....) letters
CROSS JOIN(SELECT '1' as num UNION ALL SELECT '2' ....) numbers -- up to 42
CROSS JOIN(SELECT '1' as num UNION ALL SELECT '2' ....) numbers2 -- up to 6
Here is a cut-down version using CROSS JOIN acrross 3 valued tables
SELECT v1.val + '-' + CAST(v2.val AS VARCHAR(5)) + '-' + cast(v3.val AS VARCHAR(5))
FROM
(VALUES ('A'),('B'),('C'),('D')) v1(val)
CROSS JOIN
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16)) v2(val)
CROSS JOIN
(VALUES (1),(2),(3),(4),(5),(6)) v3(val)
A Tally table would save you the need to write all the values one by one.
If you don't already have a tally table, read this post on the best way to create one.
SELECT Letter +'-'+ cast(fn as varchar(2)) +'-'+ cast(sn as char(1))
FROM (SELECT CHAR(Number) As Letter FROM Tally WHERE Number BETWEEN 65 AND 90) a
CROSS JOIN (SELECT Number as fn FROM Tally WHERE Number BETWEEN 1 AND 42) b
CROSS JOIN (SELECT Number as sn FROM Tally WHERE Number BETWEEN 1 AND 6) c
Just for fun, a mathematical approach:
with cte as
(
select 0 nr
union all
select nr+1 from cte where nr < 6551 --(26 * 42 * 6 = 6552 , 0 based = 6551)
)
select char(65 + (nr / 252)), 1 + ((nr / 6) % 42), 1 + nr % 6, * from cte -- Letter: divider = 6 * 42 = 252 , 65 = 'A'
option (maxrecursion 10000)
The cte only generated a stream of numbers from 0 to 6551 (could be done with other approaches as well).
After that each segment of the sequence can be calculated.
But for the record, once a sequence is created, I like Zohar's solution best :)
One more way:
;WITH cte AS (
SELECT 1 as digit
UNION ALL
SELECT digit + 1
FROM cte
WHERE digit < 90
)
SELECT CHAR(c1.digit) + '-' +
CAST(c2.digit as nvarchar(2)) + '-' +
CAST(c3.digit as nvarchar(2)) as seq
FROM cte c1
CROSS JOIN (SELECT digit FROM cte WHERE digit between 1 and 42) c2
CROSS JOIN (SELECT digit FROM cte WHERE digit between 1 and 6) c3
WHERE c1.digit between 65 and 90 --65..90 in ASCII is A..Z
Output:
seq
A-1-1
A-1-2
A-1-3
A-1-4
A-1-5
A-1-6
A-2-1
A-2-2
A-2-3
A-2-4
A-2-5
A-2-6
...
Z-42-3
Z-42-4
Z-42-5
Z-42-6

select statement to list numbers in range

In DB2, I have this query to list numbers 1-x:
select level from SYSIBM.SYSDUMMY1 connect by level <= "some number"
But this maxes out due to SQL20450N Recursion limit exceeded within a hierarchical query.
How can I generate a list of numbers between 1 and x using a select statement when x is not known at runtime?
I found an answer based on this post:
WITH d AS
(SELECT LEVEL - 1 AS dig FROM SYSIBM.SYSDUMMY1 CONNECT BY LEVEL <= 10)
SELECT t1.n
FROM (SELECT (d7.dig * 1000000) +
(d6.dig * 100000) +
(d5.dig * 10000) +
(d4.dig * 1000) +
(d3.dig * 100) +
(d2.dig * 10) +
d1.dig AS n
FROM d d1
CROSS JOIN d d2
CROSS JOIN d d3
CROSS JOIN d d4
CROSS JOIN d d5
CROSS JOIN d d6
CROSS JOIN d d7) t1
JOIN ("subselect that returns desired value as i") t2
ON t1.n <= t2.i
ORDER BY t1.n
That's how I usually create lists:
For your example
numberlist (num) as
(
select min(1) from anytable
union all
select num + 1 from numberlist
where num <= x
)
I did something like this when I wanted a list of values to correspond with months:
with t1 (mon) as (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)
select * from t1
It seems a bit kludgy, but for a small list like 1-12, or even 1-50, it did what I needed it to.
It's nice to see someone else tagging their questions with DB2.
If you have any table known to have more than x rows, you can always do:
select * from (
select row_number() over () num
from my_big_table
) where num <= x
or, per bhamby's suggestion:
select row_number() over () num
from my_big_table
fetch first X rows only
For DB2 you can use recursive common table expressions (cf. IBM documentation on recursive CTE):
with max(num) as (
select 1 from sysibm.sysdummy1
)
,result (num) as (
select num from max
union ALL
select result.num+1
from result
where result.num<=100
)
select * from result;

Issue with recursive CTE in PostgreSQL

This query generates the numbers from 1 to 4.
with recursive z(q) as (
select 1
union all
select q + 1 from z where q < 4
)
select * from z;
But, if I modify it to this,
with x as (
select 1 y
),
recursive z(q) as (
select y from x
union all
select q + 1 from z where q < 4
)
select * from z;
It gives
ERROR: syntax error at or near "z"
What did i do wrong here?
I think this is because RECURSIVE is modifier of WITH statement, not a property of common table expression z, so you can use it like this:
with recursive
x as (
select 1 y
),
z(q) as (
select y from x
union all
select q + 1 from z where q < 4
)
select * from z;
sql fiddle demo

SQL: how to get all the distinct characters in a column, across all rows

Is there an elegant way in SQL Server to find all the distinct characters in a single varchar(50) column, across all rows?
Bonus points if it can be done without cursors :)
For example, say my data contains 3 rows:
productname
-----------
product1
widget2
nicknack3
The distinct inventory of characters would be "productwigenka123"
Here's a query that returns each character as a separate row, along with the number of occurrences. Assuming your table is called 'Products'
WITH ProductChars(aChar, remain) AS (
SELECT LEFT(productName,1), RIGHT(productName, LEN(productName)-1)
FROM Products WHERE LEN(productName)>0
UNION ALL
SELECT LEFT(remain,1), RIGHT(remain, LEN(remain)-1) FROM ProductChars
WHERE LEN(remain)>0
)
SELECT aChar, COUNT(*) FROM ProductChars
GROUP BY aChar
To combine them all to a single row, (as stated in the question), change the final SELECT to
SELECT aChar AS [text()] FROM
(SELECT DISTINCT aChar FROM ProductChars) base
FOR XML PATH('')
The above uses a nice hack I found here, which emulates the GROUP_CONCAT from MySQL.
The first level of recursion is unrolled so that the query doesn't return empty strings in the output.
Use this (shall work on any CTE-capable RDBMS):
select x.v into prod from (values('product1'),('widget2'),('nicknack3')) as x(v);
Test Query:
with a as
(
select v, '' as x, 0 as n from prod
union all
select v, substring(v,n+1,1) as x, n+1 as n from a where n < len(v)
)
select v, x, n from a -- where n > 0
order by v, n
option (maxrecursion 0)
Final Query:
with a as
(
select v, '' as x, 0 as n from prod
union all
select v, substring(v,n+1,1) as x, n+1 as n from a where n < len(v)
)
select distinct x from a where n > 0
order by x
option (maxrecursion 0)
Oracle version:
with a(v,x,n) as
(
select v, '' as x, 0 as n from prod
union all
select v, substr(v,n+1,1) as x, n+1 as n from a where n < length(v)
)
select distinct x from a where n > 0
Given that your column is varchar, it means it can only store characters from codes 0 to 255, on whatever code page you have. If you only use the 32-128 ASCII code range, then you can simply see if you have any of the characters 32-128, one by one. The following query does that, looking in sys.objects.name:
with cteDigits as (
select 0 as Number
union all select 1 as Number
union all select 2 as Number
union all select 3 as Number
union all select 4 as Number
union all select 5 as Number
union all select 6 as Number
union all select 7 as Number
union all select 8 as Number
union all select 9 as Number)
, cteNumbers as (
select U.Number + T.Number*10 + H.Number*100 as Number
from cteDigits U
cross join cteDigits T
cross join cteDigits H)
, cteChars as (
select CHAR(Number) as Char
from cteNumbers
where Number between 32 and 128)
select cteChars.Char as [*]
from cteChars
cross apply (
select top(1) *
from sys.objects
where CHARINDEX(cteChars.Char, name, 0) > 0) as o
for xml path('');
If you have a Numbers or Tally table which contains a sequential list of integers you can do something like:
Select Distinct '' + Substring(Products.ProductName, N.Value, 1)
From dbo.Numbers As N
Cross Join dbo.Products
Where N.Value <= Len(Products.ProductName)
For Xml Path('')
If you are using SQL Server 2005 and beyond, you can generate your Numbers table on the fly using a CTE:
With Numbers As
(
Select Row_Number() Over ( Order By c1.object_id ) As Value
From sys.columns As c1
Cross Join sys.columns As c2
)
Select Distinct '' + Substring(Products.ProductName, N.Value, 1)
From Numbers As N
Cross Join dbo.Products
Where N.Value <= Len(Products.ProductName)
For Xml Path('')
Building on mdma's answer, this version gives you a single string, but decodes some of the changes that FOR XML will make, like & -> &.
WITH ProductChars(aChar, remain) AS (
SELECT LEFT(productName,1), RIGHT(productName, LEN(productName)-1)
FROM Products WHERE LEN(productName)>0
UNION ALL
SELECT LEFT(remain,1), RIGHT(remain, LEN(remain)-1) FROM ProductChars
WHERE LEN(remain)>0
)
SELECT STUFF((
SELECT N'' + aChar AS [text()]
FROM (SELECT DISTINCT aChar FROM Chars) base
ORDER BY aChar
FOR XML PATH, TYPE).value(N'.[1]', N'nvarchar(max)'),1, 1, N'')
-- Allow for a lot of recursion. Set to 0 for infinite recursion
OPTION (MAXRECURSION 365)