What is wrong in the below cte - sql-server-2005

I have
;with cte as
(
select rn=1, name = CAST('name'as varchar(50))
union all
select rn+1, CAST(name as varchar(50))+ CAST( (rn+1) as varchar(50))
from cte where rn<100)
select * from cte
error
Msg 240, Level 16, State 1, Line 1
Types don't match between the anchor and the recursive part in column "name" of recursive query "cte".

Try something like
;with cte as
(
select rn=1,
name = CAST('name'as varchar(100))
union all
select rn+1,
CAST(name as varchar(50))+ CAST( (rn+1) as varchar(50))
from cte where rn<100)
select * from cte
You have to remember that all fields in the anchor and the recursive part have to be of the same type. That also goes for varchar fields.

Related

SQL Recursive CTE replace statement too slow

I have a recursive CTE that replaces multiple values from an expression, but it is too slow when there are many expressions.
CREATE TABLE #table1(IdExpresion INT, expresion VARCHAR(MAX))
CREATE TABLE #table2(IdExpresion INT, searchExpresion VARCHAR(50), replacementExpresion VARCHAR(50))
INSERT INTO #table1(IdExpresion, expresion)
VALUES(1, 'Mary had a little lamb'),
(2, 'The new student, student_name has the following grades Math - math_grade, Science - Science_grade')
INSERT INTO #table2(IdExpresion, searchExpresion, replacementExpresion)
VALUES(1, 'lamb','dog'),
(2, 'student_name','Joe Smith'),
(2, 'math_grade','A'),
(2, 'Science_grade','B+')
;WITH cte(IdExpresion, expresion, lvl) AS
(
SELECT t1.IdExpresion, t1.expresion, 1
FROM #table1 t1
UNION ALL
SELECT cte.IdExpresion, REPLACE(cte.expresion, t2.searchExpresion, t2.replacementExpresion), cte.lvl + 1
FROM cte
INNER JOIN #table2 t2
ON cte.IdExpresion = t2.IdExpresion
AND CHARINDEX(t2.searchExpresion, cte.expresion) > 0
)
SELECT DISTINCT c2.expresion
FROM (SELECT IdExpresion, MAX(lvl) AS lvl
FROM cte
GROUP BY IdExpresion) c1
INNER JOIN cte c2
ON c1.IdExpresion = c2.IdExpresion
AND c1.lvl = c2.lvl
OPTION (MAXRECURSION 0);
Anyone have any advice? I am using SQL Server by the way
Not sure if any more performant, but here is a brute force approach just for fun.
Already +1 LukStorm's answer, I suspect that is the way to go.
Example
Declare #S varchar(max) = (Select IdExpresion,expresion = replace(' '+expresion,' ',concat(' ',IdExpresion,'|||')) From #Table1 For XML Raw )
Select #S = replace(#S,concat(IdExpresion,'|||',searchExpresion),replacementExpresion) From #table2
Select IdExpresion = B.i.value('#IdExpresion', 'int')
,expresion = ltrim(replace(B.i.value('#expresion', 'varchar(max)'),B.i.value('#IdExpresion', 'varchar(25)')+'|||',''))
From (Select x = Cast(#S as xml).query('.')) as A
Cross Apply x.nodes('row') AS B(i)
Returns
IdExpresion expresion
1 Mary had a little dog
2 The new student, Joe Smith has the following grades Math - A, Science - B+
You could add another CTE to it that gets a row_number for each replacement, partitioned by the IdExpresion.
Then in the recursive CTE, instead of counting up, count down till there's no match with the replacement row_number.
The last entry in the CTE, that had all replacements, will have Lvl 0 then.
;WITH SEARCH AS (
SELECT
IdExpresion,
row_number() over (partition by IdExpresion order by searchExpresion) as rn,
searchExpresion, replacementExpresion
FROM #table2
), CTE(IdExpresion, expresion, lvl) AS
(
SELECT t1.IdExpresion, t1.expresion, count(*)
FROM #table1 t1
JOIN #table2 t2 ON t2.IdExpresion = t1.IdExpresion
GROUP BY t1.IdExpresion, t1.expresion
UNION ALL
SELECT c.IdExpresion, REPLACE(c.expresion, s.searchExpresion, s.replacementExpresion), c.lvl - 1
FROM CTE c
JOIN SEARCH s
ON s.IdExpresion = c.IdExpresion AND s.rn = c.lvl
)
SELECT IdExpresion, expresion
FROM CTE
WHERE lvl = 0
OPTION (MAXRECURSION 0);
This way, each REPLACE is only done once per IdExpresion.
And that without having to use CHARINDEX.
You could also replace that SEARCH cte with a temporary table.
One that has the records from #table2 with that row_number.
This has the benefit that with a table you can add a compound index.
On a large table it should speed up the recursive join to the replacements.
Test on rextester here
CREATE TABLE #tmpSearch (
IdExpresion INT,
rn INT,
searchExpresion VARCHAR(50),
replacementExpresion VARCHAR(50),
primary key (IdExpresion, rn));
insert into #tmpSearch (IdExpresion, rn, searchExpresion, replacementExpresion)
select
IdExpresion,
row_number() over (partition by IdExpresion order by searchExpresion) as rn,
searchExpresion,
replacementExpresion
from #table2
order by IdExpresion, searchExpresion;
;WITH CTE(IdExpresion, expresion, lvl) AS
(
SELECT t1.IdExpresion, t1.expresion, max(s.rn)
FROM #table1 t1
JOIN #tmpSearch s ON s.IdExpresion = t1.IdExpresion
GROUP BY t1.IdExpresion, t1.expresion
UNION ALL
SELECT c.IdExpresion, REPLACE(c.expresion, s.searchExpresion, s.replacementExpresion), c.lvl - 1
FROM CTE c
JOIN #tmpSearch s
ON s.IdExpresion = c.IdExpresion AND s.rn = c.lvl
)
SELECT IdExpresion, expresion
FROM CTE
WHERE lvl = 0
OPTION (MAXRECURSION 0);
Good day,
Here is another solution. Please check if this fit your needs. This solution does not use any loop but simple dynamic query.
DECLARE #SQLString nvarchar(MAX);
-- do not make mistake, this is simple CTE and not a recursive CTE (no Loop)
;With MyCTE as (
select R
From table1 t1
CROSS APPLY (
SELECT R = 'SELECT ' + CONVERT (NVARCHAR(MAX),t1.IdExpresion) + ' as IdExpresion,' + STRING_AGG ('REPLACE','(') + '(' + 't1.expresion,''' + STRING_AGG(t2.searchExpresion + ''',''' + t2.replacementExpresion , '''),''') + ''') as expresion FROM table1 t1 where t1.IdExpresion = ' + CONVERT (NVARCHAR(MAX),t1.IdExpresion)
from table2 t2
where t2.IdExpresion = t1.IdExpresion
) C
)
SELECT #SQLString = STRING_AGG(R,'
UNION ALL
')
FROM MyCTE
--PRINT #SQLString
EXECUTE sp_executesql #SQLString
GO
Note! I recommend to execute some tests to confirm that this solves all cases
Note! I am using the function STRING_AGG which was added to SQL Server 2017. In older version you can get the exact same solution using FOR XML statement.
Since we don't have the real DDL+DML we cannot really discuss about performance, but the difference in the execution plans of the solutions is 10% to 90% (In general, You should check IO and Time statistics in production in addition, before choosing your solution)
So... here is the Execution Plans Image (above query is my dynamic SQL solution and bellow is LukStorms solution using recursive CTE = Loop)

Split Comma seperated value other than split function

FOR EACH LINE THERE IS AN VALID Patient_id ASSOCIATED WITH IT in Parent table
i try to validate whether correct Patient_id linked with line.
problem:
In child table Patient_id is comma seperated.
if i am using function to split value, the query is not completing even after
15 hours.
can anyone suggest some other alternate, without using finction Please if possible
DECLARE #PARENT_TABLE TABLE
(
Line VARCHAR (50),
Patient_id VARCHAR (50)
)
INSERT #PARENT_TABLE
SELECT 'ABSRB', 'E001' UNION ALL
SELECT 'ABSRB', 'E067' UNION ALL
SELECT 'ABSRB', 'E068'
DECLARE #CHILD_TABLE TABLE
(
LINE VARCHAR (50),
SKU VARCHAR (50),
Patient_id VARCHAR (50)
)
INSERT #CHILD_TABLE
SELECT 'ABSRB', 'PRS317580', NULL UNION ALL
SELECT 'ABSRB', 'RANRS7371', 'E001' UNION ALL
SELECT 'ABSRB', 'KYBKG54327', 'E067' UNION ALL
SELECT 'ABSRB', 'KYB344615', 'V048, V055, E068' UNION ALL
SELECT 'ABSRB', 'ZZZZ15245', 'S128, V048, V055'
SELECT * FROM #CHILD_TABLE
can anyone suggest some other alternate, without using finction Please if possible
i have not included split function code here.
Code:
CREATE FUNCTION [dbo].[fnSplitString](
#string NVARCHAR(MAX),
#delimiter CHAR(1),
#keylist nvarchar(50))
RETURNS TABLE
AS
RETURN(
WITH CTE AS(
SELECT 1 as n
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
),CTE2 AS(
SELECT ROW_NUMBER() OVER(ORDER BY c.n) as n
FROM CTE c
CROSS JOIN CTE c2
CROSS JOIN CTE c3
CROSS JOIN CTE c4
CROSS JOIN CTE c5
CROSS JOIN CTE c6
)
SELECT TOP(LEN(COALESCE(#string,'')))
splitdata = ltrim(rtrim(
SUBSTRING(#string, n, CHARINDEX(#delimiter, #string + #delimiter, n)-n)
)),
keyname= #keylist
FROM CTE2
WHERE n<= LEN(#string)
AND SUBSTRING(#Delimiter + #string , n, LEN(#Delimiter)) = #Delimiter
)
DECLARE #SplittedTable TABLE
(
splitted VARCHAR (50),
keys VARCHAR (50)
)
INSERT INTO #SplittedTable(splitted,keys)
SELECT ss.splitdata, ss.keyname
FROM #CHILD_TABLE ct
CROSS APPLY dbo.fnSplitString(ct.Patient_id,',',ct.Patient_id) ss
SELECT * FROM #CHILD_TABLE ct WHERE ct.Patient_id IN (
SELECT DISTINCT st.keys
FROM #SplittedTable AS st
WHERE st.splitted NOT IN (SELECT Patient_id FROM #Parent_table m ));
SELECT DISTINCT ct.*
FROM #CHILD_TABLE ct
CROSS APPLY dbo.fnSplitString(ct.Patient_id,',',ct.Patient_id) ss
WHERE ss.splitdata NOT IN (SELECT Patient_id FROM #Parent_table)
The below code took more than 15 hours to execute. table size: 20GB
CODE:
--selecting by keys and finding them into master
SELECT * FROM #child ct WHERE ct.patientid IN (
SELECT DISTINCT st.keys
FROM #SplittedTable AS st
WHERE st.splitted NOT IN (SELECT patientid FROM #parent m ));
--Use CROSS APPLY to get this done for all rows.
SELECT DISTINCT ct.*
FROM #child ct
CROSS APPLY dbo.fnSplitString(ct.patientid,',',ct.patientid) ss
WHERE ss.splitdata NOT IN (SELECT patientid FROM #parent)

How to group through a string part?

I've a table which contains logs from a web portal, it contains the url visited, the request duration, the referer...
One of these columns is the path info and contains strings like following:
/admin/
/export/
/project2/
/project1/news
/project1/users
/user/id/1
/user/id/1/history
/user/id/2
/forum/topic/14/post/456
I would like to calculate with sql queries some stats based on this column, so I would like to know how can I create aggregate based on the first part of the path info?
It'd let me count number of url starting by /admin/, /export/, /project1/, /project2/, /user/, /forum/, ...
Making it with a programming language would be easy with regex, but I read that similar function does not exists on SQLServer.
I would use CHARINDEX() to find the first occurrence of the "/" starting AFTER the leading first character '/', so anything AFTER the second is stripped off.
select
LEFT( pathInfo, CHARINDEX( '/', pathInfo, 2 )) as RootLevelPath,
count(*) as Hits
from
temp
group by
LEFT( pathInfo, CHARINDEX( '/', pathInfo, 2 ))
Working result from SQLFiddle
DRapp's is perfect for grouping on the first fragment of the URL. If you need to group by other levels it might get unwieldy to manage the nested LEFT/CHARINDEX statements.
Here's one way to group by a parameterized level:
declare #t table (pathId int identity(1,1) primary key, somePath varchar(100));
insert into #t
select '/admin/' union all
select '/export/' union all
select '/project2/' union all
select '/project1/news' union all
select '/project1/users' union all
select '/user/id/1' union all
select '/user/id/1/history' union all
select '/user/id/2' union all
select '/forum/topic/14/post/456' union all
select '/forum/topic/14/post/789' union all
select '/forum/topic/14/post/789'
declare #level int =1;
;with fragments as
( select pathId,
[n] = x.query('.'),
[Fragment] = x.value('.', 'varchar(100)')
from ( select PathId,
cast('<r>' + replace(stuff(somePath, 1, 1, ''), '/', '</r><r>') + '</r>' as xml)
.query('r[position()<=sql:variable("#level")]')
from #t
) d (PathId, X)
)
select count(*), [path] = max(r.v)
from fragments f
cross
apply ( select '/' + p.n.value('.', 'varchar(100)')
from fragments
cross
apply n.nodes('r')p(n)
where PathId = f.PathId
for xml path('')
) r(v)
group
by fragment;

SQL query problem

Let´s say I have two tables, "Garden" and "Flowers". There is a 1:n-relationship between these tables, because in a garden can be many flowers. Is it possible to write an SQL query which returns a result with the following structure:
GardenName Flower1Name Flower2Name .... (as long as there are entries in flowers)
myGarden rose tulip
CREATE TABLE #Garden (Id INT, Name VARCHAR(20))
INSERT INTO #Garden
SELECT 1, 'myGarden' UNION ALL
SELECT 2, 'yourGarden'
CREATE TABLE #Flowers (GardenId INT, Flower VARCHAR(20))
INSERT INTO #Flowers
SELECT 1, 'rose' UNION ALL
SELECT 1, 'tulip' UNION ALL
SELECT 2, 'thistle'
DECLARE #ColList nvarchar(max)
SELECT #ColList = ISNULL(#ColList + ',','') + QUOTENAME('Flower' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS VARCHAR))
FROM #Flowers WHERE GardenId = (
SELECT TOP 1 GardenId
FROM #Flowers
ORDER BY COUNT(*) OVER (PARTITION BY GardenId) DESC
)
EXEC (N'
;WITH cte As
(
SELECT *, ''Flower'' + CAST(ROW_NUMBER() OVER (PARTITION BY GardenId ORDER BY (SELECT 0)) AS VARCHAR) RN
FROM #Flowers F
)
SELECT Name,' + #ColList + N'
FROM cte
JOIN #Garden g ON g.Id = GardenId
PIVOT (MAX(Flower) FOR RN IN (' + #ColList + N')) Pvt')
DROP TABLE #Garden
DROP TABLE #Flowers
Returns
Name Flower1 Flower2
-------------------- -------------------- --------------------
myGarden rose tulip
yourGarden thistle NULL
Look at using Pivot in SQL Server. Here is a good link that goes over how it works:
http://www.kodyaz.com/articles/t-sql-pivot-tables-in-sql-server-tutorial-with-examples.aspx
Ok, i think i got it working. Try this:
with temp as
(
select 'myGarden' as name, 'test1' as flower
union
select 'myGarden','test2'
union
select 'myGarden','test5'
union
select 'abeGarden','test4'
union
select 'abeGarden','test5'
union
select 'martinGarden', 'test2'
)
select* from temp
pivot
(
max(flower)
for flower in (test1,test2,test3,test4,test5)
) PivotTable
You could also make the values in the in clause dynamic. Since this is a CTE i can't in my example.
Dynamic SQL with a cursor is the only way I can think of, and it won't be pretty.
If you only want the results for one garden at a time this would give you the data:
select gardenName from tblGarden where gardenid = 1
Union ALL
select tblFLowers.flowerName from tblFlowers where gardenid = 1

CTE error: "Types don't match between the anchor and the recursive part"

I am executing the following statement:
;WITH cte AS (
SELECT
1 as rn,
'name1' as nm
UNION ALL
SELECT
rn + 1,
nm = 'name' + CAST((rn + 1) as varchar(255))
FROM cte a WHERE rn < 10)
SELECT *
FROM cte
...which finishes with the error...
Msg 240, Level 16, State 1, Line 2
Types don't match between the anchor and the recursive part in column "nm" of recursive query "cte".
Where am I making the mistake?
Exactly what it says:
'name1' has a different data type to 'name' + CAST((rn+1) as varchar(255))
Try this (untested)
;with cte as
(
select 1 as rn, CAST('name1' as varchar(259)) as nm
union all
select rn+1,nm = 'name' + CAST((rn+1) as varchar(255))
from cte a where rn<10)
select * from cte
Basically, you have to ensure the length matches too. For the recursive bit, you may have to use CAST('name' AS varchar(4)) if it fails again
You need to cast both nm fields
;with cte as
(
select 1 as rn,
CAST('name1' AS VARCHAR(255)) as nm
union all
select rn+1,
nm = CAST('name' + CAST((rn+1) as varchar(255)) AS VARCHAR(255))
from cte a where rn<10)
select * from cte
For me problem was in different collation.
Only this helped me:
;WITH cte AS (
SELECT
1 AS rn,
CAST('name1' AS NVARCHAR(4000)) COLLATE DATABASE_DEFAULT AS nm
UNION ALL
SELECT
rn + 1,
nm = CAST('name' + CAST((rn + 1) AS NVARCHAR(255)) AS NVARCHAR(4000)) COLLATE DATABASE_DEFAULT
FROM cte a WHERE rn < 10)
SELECT *
FROM cte;
Hope it can help someone else.
;with cte as
(
select 1 as rn, 'name' + CAST(1 as varchar(255)) as nm
union all
select rn+1,nm = 'name' + CAST((rn+1) as varchar(255))
from cte a where rn<10)
select * from cte
In my case, I messed up the sequence of columns in top and bottom clauses of UNION ALL. And it turned out that a varchar column appeared 'under' an int one. An easy mistake to make of you have lots of columns
If you use CONCAT in the recursive term of a rcte, since the output type of concat is varchar(MAX), you only need to cast the column in the initial query:
WITH rcte AS (
SELECT 1 AS nr, CAST('1' AS varchar(MAX)) AS trail
UNION ALL
SELECT nr+1, CONCAT(trail, '/', nr+1)
FROM rcte
WHERE nr < 5
)
SELECT * FROM rcte;
I would recommend using nvarchar(max)
WITH CTE AS (
SELECT x,x_name FROM (VALUES (1,CAST('' AS nvarchar(MAX)))) AS test(x,x_name)
UNION ALL
SELECT x + 1 x, CONCAT(x_name,x+1) FROM CTE WHERE x < 10 )
SELECT * FROM CTE
WITH rcte AS (
SELECT 1 AS nr, CAST('1' AS varchar(MAX)) AS trail
UNION ALL
SELECT nr+1, cast(CONCAT(trail, '/', nr+1) as varchar(max))
FROM rcte
WHERE nr < 5
)
SELECT * FROM rcte;
;with tmp1(NewsId,DataItem ,HeaderText)
as
(
select NewsId, LEFT(HeaderText, CHARINDEX(',',HeaderText+',')-1),
STUFF(HeaderText, 1, CHARINDEX(',',HeaderText+','), '')
from Currentnews
union all
select NewsId, LEFT(HeaderText, CHARINDEX(',',HeaderText+',')-1),
STUFF(HeaderText, 1, CHARINDEX(',',HeaderText+','), '')
from tmp1
where HeaderText > ''
)
select NewsId, DataItem
from tmp1
order by NewsId