Datatype mismatch in anchor and recursive part of a recursive CTE - sql

I am running this SQL code on SQL Server to print alphabets from A to Z:
;with alphaCte as
(
select 'A' as letter
union all
select char(ascii(letter)+1)
from alphaCte
where letter < 'Z'
)
select * from alphaCte
I get this error:
Types don't match between the anchor and the recursive part in column "letter" of recursive query "alphaCte".
To rectify it, I have to make below change.
;with alphaCte as
(
select char(ascii('A')) as letter
union all
select char(ascii(letter)+1)
from alphaCte
where letter < 'Z'
)
select * from alphaCte
which works fine.
Could anyone please explain why my original code is throwing this datatype mismatch error?

To expand on my comment
Select column_ordinal
,name
,system_type_name
From sys.dm_exec_describe_first_result_set('select ''A'' as letter,char(ascii(''A'')+1) as letter2',null,null )
Results
column_ordinal name system_type_name
1 letter varchar(1)
2 letter2 char(1)
EDIT: Just an aside... Recursive CTEs are great but datasets are better :)
Select Top 26 C=char(64+Row_Number() Over (Order By (Select NULL)) )
From master..spt_values n

Related

Change the casing of my letters in string using sql function

I want to convert my string into opposite casing in sql using function.
can someone help me??
I am able to do the first letter of the string but not middle letters
http://www.sql-server-helper.com/functions/initcap.aspx
Example:
input:
"hI mY Name IS Joe"
output:
"Hi My nAME is jOE"
If your sql-server version support (sql-server-2017 and above), you can use TRANSLATE function
SELECT TRANSLATE ('hI mY Name IS Joe' COLLATE Latin1_general_CS_AS
,'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
,'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
Result:
Hi My nAME is jOE
A completely set-based approach:
DECLARE #TheLinkTable TABLE(ID INT IDENTITY,YourText NVARCHAR(1000));
INSERT INTO #TheLinkTable VALUES('hI mY Name IS Joe');
The query:
WITH cte AS
(
SELECT t.ID
,t.YourText
,A.Nmbr
,C.SwitchedLetter
FROM #TheLinkTable t
CROSS APPLY(SELECT TOP(LEN(t.YourText)) ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) FROM master..spt_values) A(Nmbr)
CROSS APPLY(SELECT SUBSTRING(t.YourText,A.Nmbr,1)) B(TheLetter)
CROSS APPLY(SELECT CASE WHEN TheLetter LIKE '[a-zA-Z]'
THEN CHAR(ASCII(TheLetter) ^ 0x20)
ELSE CASE WHEN TheLetter=' ' THEN TheLetter END END) C(SwitchedLetter)
)
SELECT cte1.ID
,cte1.YourText
,(
SELECT SwitchedLetter AS [*]
FROM cte cte2
WHERE cte2.ID=cte1.ID
ORDER BY cte2.Nmbr
FOR XML PATH(''),TYPE).value('.','nvarchar(max)'
)
FROM cte cte1
GROUP BY cte1.ID,cte1.YourText;
The idea in short:
Using a tally-on-the-fly (in this case a ROW_NUMBER() against any bigger set with a computed TOP-clause we get a running number from 1 to n where n is the count of letters.
The second APPLY will pick each letter separately.
The third apply will switch the casing of letters from a to z simply by XORing the binary representation and re-set the significant BIT. A blank is returned as is.
The following SELECT will group by the ID and use a correlated sub-query to reconcatenate the string.
Another set-based approach implies a recursive CTE:
WITH recCTE AS
(
SELECT 1 AS position
,YourText
,CAST(CASE WHEN ASCII(TheLetter) BETWEEN 65 AND 90 THEN LOWER(TheLetter)
ELSE CASE WHEN ASCII(TheLetter) BETWEEN 97 AND 122 THEN UPPER(TheLetter) END END AS NVARCHAR(MAX)) AS SwitchLetter
FROM #TheLinkTable
CROSS APPLY(SELECT SUBSTRING(YourText,1,1)) A(TheLetter)
UNION ALL
SELECT r.position+1
,YourText
,CONCAT(r.SwitchLetter
,CASE WHEN ASCII(TheLetter) BETWEEN 65 AND 90 THEN LOWER(TheLetter)
ELSE CASE WHEN ASCII(TheLetter) BETWEEN 97 AND 122 THEN UPPER(TheLetter)
ELSE TheLetter END END) AS SwitchLetter
FROM recCTE r
CROSS APPLY(SELECT SUBSTRING(YourText,r.position+1,1)) A(TheLetter)
WHERE r.position<LEN(YourText)
)
SELECT * FROM recCte;
You must add a WHERE to pick the last one (e.g. LEN(SwitchLetter)=LEN(YourText)). I left it aside to show how it's working.

SQL STATEMENT keyword

While going through the SQL CTEs I came across this: CODE Project CTE
The code is:
WITH ShowMessage(STATEMENT, LENGTH)
AS
(
SELECT STATEMENT = CAST('I Like ' AS VARCHAR(300)), LEN('I Like ')
UNION ALL
SELECT
CAST(STATEMENT + 'CodeProject! ' AS VARCHAR(300))
, LEN(STATEMENT) FROM ShowMessage
WHERE LENGTH < 300
)
SELECT STATEMENT, LENGTH FROM ShowMessage
or even a small modified one:
WITH ShowMessage(STATEMENT, LENGTH)
AS
(
SELECT STATEMENT = 1, LEN('I Like ')
UNION ALL
SELECT
STATEMENT + 1
, LEN(STATEMENT) FROM ShowMessage
WHERE STATEMENT < 50
)
SELECT STATEMENT, LENGTH FROM ShowMessage
The above code works perfect, when I try the code as:
with k (TT,LL)
as
(
select TT= 1, 1
union all
select TT+1,1
WHERE TT < 50
)
select TT,LL from k
My code does not work, error is that COLUMN TT does not exists. After a careful observation found that the STATEMENT is a keyword (UI showed in blue color); then I started searching online for the meaning of this keyword but could not find one (Google always throws only SELECT statement - not the STATEMENT)
Could you please explain what is this STATEMENT keyword and where/how to use it. Or please point me to the right source to learn it.
Try below query :
;WITH k (TT,LL)
as
(
SELECT 1, 1
UNION ALL
SELECT TT+1,1
FROM k --- you miss that table
WHERE TT < 50
)
SELECT TT,LL FROM k
You missed to add table i.e k.
with k (TT,LL)
as
(
select TT=1 , 1
union all
select TT+1,1 From K
WHERE TT < 50
)
select TT,LL from k

Regex pattern inside REPLACE function

SELECT REPLACE('ABCTemplate1', 'Template\d+', '');
SELECT REPLACE('ABC_XYZTemplate21', 'Template\d+', '');
I am trying to remove the part Template followed by n digits from a string. The result should be
ABC
ABC_XYZ
However REPLACE is not able to read regex. I am using SQLSERVER 2008. Am I doing something wrong here? Any suggestions?
SELECT SUBSTRING('ABCTemplate1', 1, CHARINDEX('Template','ABCTemplate1')-1)
or
SELECT SUBSTRING('ABC_XYZTemplate21',1,PATINDEX('%Template[0-9]%','ABC_XYZTemplate21')-1)
More generally,
SELECT SUBSTRING(column_name,1,PATINDEX('%Template[0-9]%',column_name)-1)
FROM sometable
WHERE PATINDEX('%Template[0-9]%',column_name) > 0
You can use substring with charindex or patindex if the pattern being looked for is fixed.
select SUBSTRING('ABCTemplate1',1, CHARINDEX ( 'Template' ,'ABCTemplate1')-1)
My answer expects that "Template" is enough to determine where to cut the string:
select LEFT('ABCTemplate1', CHARINDEX('Template', 'ABCTemplate1') - 1)
Using numbers table..
;with cte
as
(select 'ABCTemplate1' as string--this can simulate your table column
)
select * from cte c
cross apply
(
select replace('ABCTemplate1','template'+cast(n as varchar(2)),'') as rplcd
from
numbers
where n<=9
)
b
where c.string<>b.rplcd
Using Recursive CTE..
;with cte
as
(
select cast(replace('ABCTemplate21','template','') as varchar(100)) as string,0 as num
union all
select cast(replace(string,cast(num as varchar(2)),'') as varchar(100)),num+1
from cte
where num<=9
)
select top 1 string from cte
order by num desc

Oracle SQL - group by char cast

Using the a char-cast in a group-by clause results something unexpected:
select cast(col as char(2)) from (
select 'Abc' as col from dual
union all
select 'Abc' as col from dual
) group by cast(col as char(10));
The result is 'Abc ' (10 characters long).
Intuitively, I would have expected Oracle to return one of the following:
An error: 'not a group-by expression', as the group-by clause is another than the selection clause
A result of length 2 'Ab'.
Replacing cast(col as char(2)) with cast(col as char(3)), Oracle returns an error 'not a group-by expression'. This, again is a very strange behavior.
How can this be explained? What's the reason behind it?
I'm using Oracle SQL 11g.
As was mentioned above, I think there is a misunderstanding going on. o.O
I can't explain why it's doing this, but here's the pattern for the type of query you have:
If you generalize it a bit like this, where [A] and [B] are integers, and [STRING] is whatever text you want:
select cast(col as char([A])) from (
select '[STRING]' as col from dual
union all
select '[STRING]' as col from dual
) group by cast(col as char([B]));
it looks like this always fails if one of the two conditions below is true (there may be others):
( LENGTH([STRING]) < [B] OR LENGTH([STRING] > [B]) and [A] = LENGTH([STRING])
( LENGTH([STRING]) = [B] AND [A] <> LENGTH([STRING]) )
Otherwise, it'll return a row.
But if you take your example that runs and use it in a CREATE TABLE statement, it's going to fail as it sets up the column width to be the 2 and can't fit the 3 character string coming in.
To add to the oddity, if you append something at the start and the end of the string like this:
select '\*'||cast(col as char([A]))||'\*' from (
select '[STRING]' as col from dual
union all
select '[STRING]' as col from dual
) group by cast(col as char([B]));
This will only work if [A] >= [B], otherwise it fails on ORA-01489: result of string concatenation is too long.
Curious...

Check palindrome without using string functions with condition

I have a table EmployeeTable.
If I want only that records where employeename have character of 1 to 5
will be palindrome and there also condition like total character is more then 10 then 4 to 8 if character less then 7 then 2 to 5 and if character less then 5 then all char will be checked and there that are palindrome then only display.
Examples :- neen will be display
neetan not selected
kiratitamara will be selected
I try this something on string function like FOR first case like name less then 5 character long
SELECT SUBSTRING(EmployeeName,1,5),* from EmaployeeTable where
REVERSE (SUBSTRING(EmployeeName,1,5))=SUBSTRING(EmployeeName,1,5)
I want to do that without string functions,
Can anyone help me on this?
You need at least SUBSTRING(), I have a solution like this:
(In SQL Server)
DECLARE #txt varchar(max) = 'abcba'
;WITH CTE (cNo, cChar) AS (
SELECT 1, SUBSTRING(#txt, 1, 1)
UNION ALL
SELECT cNo + 1, SUBSTRING(#txt, cNo + 1, 1)
FROM CTE
WHERE SUBSTRING(#txt, cNo + 1, 1) <> ''
)
SELECT COUNT(*)
FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY cNo DESC) as cRevNo
FROM CTE t1 CROSS JOIN
(SELECT Max(cNo) AS strLength FROM CTE) t2) dt
WHERE
dt.cNo <= dt.strLength / 2
AND
dt.cChar <> (SELECT dti.cChar FROM CTE dti WHERE dti.cNo = cRevNo)
The result will shows the count of differences and 0 means no differences.
Note :
Current solution is Non-Case-Sensitive for change it to a Case-Sensitive you need to check the strings in a case-sensitive collation like Latin1_General_BIN
You can use this solution as a SVF or something like that.
I dont realy understand why you dont want to use string functions in your query, but here is one solution. Compute everything beforehand:
Add Column:
ALTER TABLE EmployeeTable
ADD SubString AS
SUBSTRING(EmployeeName,
(
CASE WHEN LEN(EmployeeName)>10
THEN 4
WHEN LEN(EmployeeName)>7
THEN 2
ELSE 1 END
)
,
(
CASE WHEN LEN(EmployeeName)>10
THEN 8
WHEN LEN(EmployeeName)>7
THEN 5
ELSE 5 END
)
PERSISTED
GO
ALTER TABLE EmployeeTable
ADD Palindrome AS
REVERSE(SUBSTRING(EmployeeName,
(
CASE WHEN LEN(EmployeeName)>10
THEN 4
WHEN LEN(EmployeeName)>7
THEN 2
ELSE 1 END
)
,
(
CASE WHEN LEN(EmployeeName)>10
THEN 8
WHEN LEN(EmployeeName)>7
THEN 5
ELSE 5 END
)) PERSISTED
GO
Then your query will looks like:
SELECT * from EmaployeeTable
where Palindrome = SubString
BUT!
This is not a good idea. Please tell us, why you dont want to use string functios.
You could do it building a list of palindrome words using a recursive query that generates palindrome words till a length o n characters and then selects employees with the name matching a palindrome word. This may be a really inefficient way, but it does the trick
This is a sample query for Oracle, PostgreSQL should support this feature as well with little differences on syntax. I don't know about other RDBMS.
with EmployeeTable AS (
SELECT 'ADA' AS employeename
FROM DUAL
UNION ALL
SELECT 'IDA' AS employeename
FROM DUAL
UNION ALL
SELECT 'JACK' AS employeename
FROM DUAL
), letters as (
select chr(ascii('A') + rownum - 1) as letter
from dual
connect by ascii('A') + rownum - 1 <= ascii('Z')
), palindromes(word, len ) as (
SELECT WORD, LEN
FROM (
select CAST(NULL AS VARCHAR2(100)) as word, 0 as len
from DUAL
union all
select letter as word, 1 as len
from letters
)
union all
select l.letter||p.word||l.letter AS WORD, len + 1 AS LEN
from palindromes p
cross join letters l
where len <= 4
)
SEARCH BREADTH FIRST BY word SET order1
CYCLE word SET is_cycle TO 'Y' DEFAULT 'N'
select *
from EmployeeTable
WHERE employeename IN (
SELECT WORD
FROM palindromes
)
DECLARE #cPalindrome VARCHAR(100) = 'SUBI NO ONIBUS'
SET #cPalindrome = REPLACE(#cPalindrome, ' ', '')
;WITH tPalindromo (iNo) AS (
SELECT 1
WHERE SUBSTRING(#cPalindrome, 1, 1) = SUBSTRING(#cPalindrome, LEN(#cPalindrome), 1)
UNION ALL
SELECT iNo + 1
FROM tPalindromo
WHERE SUBSTRING(#cPalindrome, iNo + 1, 1) = SUBSTRING(#cPalindrome, LEN(#cPalindrome) - iNo, 1)
AND LEN(#cPalindrome) > iNo
)
SELECT IIF(MAX(iNo) = LEN(#cPalindrome), 'PALINDROME', 'NOT PALINDROME')
FROM tPalindromo