SQL split and merge string - sql

this is my problem:
I got a string from a column like this:
**0756FJ89045GJD38**.pdf
Now i have to generate a path by this string:
/home/ars/07/56/FJ/89/04/5G/JD/38/0756FJ89045GJD38.pdf
I have to take two characters and build it up to one path level from left to right.
Maybe u can help me, thanks!

This may help:
DECLARE #p nvarchar(100) = '**0756FJ89045GJD38**.pdf',
#n int = 3
;WITH cte AS (
SELECT STUFF(REPLACE(SUBSTRING(#p,1,CHARINDEX('.',#p)-1),'*',''),1,0,'/') as p, 1 [level]
UNION ALL
SELECT STUFF(p,[level]+#n,0,'/'), [level]+#n
FROM CTE
WHERE LEN(STUFF(p,[level]+#n,0,'/')) >= [level]+#n
)
SELECT TOP 1 #p = '/home/ars'+p +'/'+REPLACE(#p,'*','')
FROM cte
ORDER BY [level] DESC
SELECT #p
Output:
/home/ars/07/56/FJ/89/04/5G/JD/38/0756FJ89045GJD38.pdf
EDIT:
If there is a table with PDF file names and all names are equal size, than you can do this way:
DECLARE #n int = 3
;WITH pdf AS (
SELECT *
FROM (VALUES
('**0756FJ89045GJD38**.pdf'),
('**1729DA8CD189700A**.pdf'),
('**A6710936BCD47832**.pdf'),
('**00A764D617B93978**.pdf')
) as t(file_)
)
,cte AS (
SELECT file_, STUFF(REPLACE(SUBSTRING(file_,1,CHARINDEX('.',file_)-1),'*',''),1,0,'/') as p, 1 [level]
FROM pdf
UNION ALL
SELECT file_, STUFF(p,[level]+#n,0,'/'), [level]+#n
FROM CTE
WHERE LEN(STUFF(p,[level]+#n,0,'/')) >= [level]+#n
)
SELECT TOP 1 WITH TIES '/home/ars'+p +'/' + REPLACE(c.file_,'**','')
FROM cte c
ORDER BY ROW_NUMBER() OVER (PARTITION BY file_ ORDER BY [level]) DESC
Output:
/home/ars/00/A7/64/D6/17/B9/39/78/00A764D617B93978.pdf
/home/ars/A6/71/09/36/BC/D4/78/32/A6710936BCD47832.pdf
/home/ars/17/29/DA/8C/D1/89/70/0A/1729DA8CD189700A.pdf
/home/ars/07/56/FJ/89/04/5G/JD/38/0756FJ89045GJD38.pdf

Related

How to complete and fill in gaps between dates in SQL?

I have data in Redshift that I'm aggregating to the Year-Quarter level i.e. number of items by Year-Quarter
I need to show a continuous trend and hence I need to fill-in the gaps in Year-Quarter. The picture below should give a clearer idea of my current data and desired output.
How can I achieve this in Redshift SQL?
A query like this should do the trick:
create table test (yq int, items int);
INSERT INTO test Values (20201,10),(20204, 15),(20213, 25),(20222, 30);
with recursive quarters(q) as (
select min(yq) as q
from test
union all
select decode(right(q::text, 1), 4, q + 7, q + 1) as q
from quarters
where q < (select max(yq) from test)
)
select q as yq, decode(items is null, true,
lag(items ignore nulls) over (order by q), items) as items
from test t
right join quarters q
on t.yq = q.q
order by q;
It uses a recursive CTE to generate the quarters range needed, right joins this with the source data, and then uses a LAG() window function to populate the items if the value is NULL.
This is known as forward filling values:
CREATE TABLE #Temp
(
[YQ] nvarchar(5),
[items] int
)
INSERT INTO #Temp Values ('20201',10),('20204', 15),('20213', 25),('20222', 30)
---------------------------------------------------------------------------------
DECLARE #start int, #end int, #starty int, #endy int
SELECT #start=1, #end=4
SELECT #starty=MIN(Substring(YQ,0,5)), #endy=MIN(Substring(YQ,0,5)) from #Temp
;With cte1(y) as
(
Select #starty as y
union all
Select y + 1
from cte1
where y <= #endy + 1
)
, cte2(n) as
(
Select #start as n
union all
Select n + 1
from cte2
where n < #end
)
SELECT t1.YQ AS 'Year-Quarter',
CASE WHEN t2.items is null then (SELECT TOP 1 MAX(items) from #Temp WHERE items is not null and YQ < t1.YQ) ELSE t2.items END AS '# Items'
FROM
(
SELECT CAST(cte1.y AS nvarchar(4)) + CAST(cte2.n AS nvarchar(1)) AS YQ
FROM cte1, cte2
) t1
LEFT JOIN #Temp t2 ON t2.YQ = t1.YQ
WHERE t1.YQ <= (SELECT MAX(YQ) FROM #Temp)
ORDER BY t1.YQ, t2.items

SQL String join to table

Given few strings as
SET #Codes1 = 3,4
SET #Codes2 = 1
SET #Codes3 = --empty
Table -- TblCode
Id Code
1 A
2 B
3 C
4 D
How to convert the #Codes1, #Codes2, #Codes3 with join to the table TblCode so it returns the following output :
1. #Codes1 = CD
2. #Codes2 = A
3. #Codes3 = --empty
Note that the concatenation for the output is without the comma.
PS - This is a small example to a much larger and complex data set. Kindly ignore any wrongful design pattern here.
You can try this. I added the answer just for #Codes1, but it works with #Codes2 and #Codes3 too.
DECLARE #TblCode TABLE (Id INT, Code VARCHAR(2))
INSERT INTO #TblCode
VALUES(1, 'A'),
(2,'B'),
(3,'C'),
(4,'D')
DECLARE #Codes1 VARCHAR(10) = '3,4'
DECLARE #Codes2 VARCHAR(10) = '1'
DECLARE #Codes3 VARCHAR(10) = NULL
DECLARE #CodesOut VARCHAR(10) = ''
;WITH CTE_1 AS (
SELECT CODE= #Codes1 + ','
)
, CTE_2 AS -- It silit text to rows
(
SELECT RIGHT(CTE_1.CODE, LEN(CTE_1.CODE) - CHARINDEX(',',CTE_1.CODE)) CODE , SUBSTRING(CTE_1.CODE, 0, CHARINDEX(',',CTE_1.CODE)) ID, CHARINDEX(',',CTE_1.CODE) AS CI
FROM CTE_1
UNION ALL
SELECT RIGHT(CTE_2.CODE, LEN(CTE_2.CODE) - CHARINDEX(',',CTE_2.CODE)) CODE , SUBSTRING(CTE_2.CODE, 0, CHARINDEX(',',CTE_2.CODE)) ID, CHARINDEX(',',CTE_2.CODE) AS CI
FROM CTE_2 WHERE LEN(CTE_2.CODE) > 0
)
SELECT #CodesOut = #CodesOut + C.Code FROM CTE_2 INNER JOIN #TblCode C ON CTE_2.ID = C.Id
SELECT #CodesOut
Result:
CD
You can use a recursive CTE. Here is one method:
with c as (
select c.*, row_number() over (partition by id) as seqnum
from c
),
cte as (
select cast(#codes as varchar(max)) as str,
replace(#codes, id, code) as newstr,
1 as lev
from c
where seqnum = 1
union all
select str, replace(newstr, id, code), lev + 1
from cte join
c
on c.seqnum = cte.lev + 1
)
select top (1) newstr
from cte
order by lev desc;
If there is an error in the syntax, set up a SQL Fiddle or Rextester or something similar so it can be fixed.

SQL replace from list

I'm trying to figure our how I can replace a string using data from another table
I have a table that looks like this:
Id Translation
1 Peter
2 Sandra
3 Olga
Now I want to select all and replace the translations using a list that looks like this:
Original New
e #
r ?
lg *%
So that the select list looks like this:
Id Translation
1 P#t#?
2 Sand?a
3 O*%a
So, for each translation, I need to have a REPLACE(Translation,Original,New).
Or in other words: I need to go through every "Translation" in my first list and make another loop in my replacement table to see what to replace
Bare in mind that the first list has 25'000 rows and the second has 50'000, so I can't just type it by hand :)
EDIT
Just to clarify:
The Original and New from my look up table can be both letters and words so the table can looks like this:
Original New
one two
three fifty
sun moon
To do this in one query, you need to use a recursive CTE. Something like:
with trans as (
select t.original, t.new, row_number() over (order by t.original) as seqnum,
count(*) over () as cnt
from translations
),
t as (
select tt.id, tt.string, replace(tt.string, trans.original, trans.new) as replaced,
seqnum + 1 as seqnum, cnt
from totranslate tt join
trans
on trans.id = 1
union all
select t.id, t.string, replace(t.string, trans.original, trans.new),
seqnum + 1 as seqnum, cnt
from t join
trans
on t.seqnum = trans.id
where t.seqnum <= t.cnt
)
select t.id, t.string, t.replaced
from t
where seqnum = cnt;
You can use a UDF:
CREATE FUNCTION [dbo].[Translate]
(
-- Add the parameters for the function here
#Str nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #Result nvarchar(max) = #Str;
SELECT #Result = replace(#Result,Original,New) from dbo.Mappings order BY Pos;
RETURN #Result;
END
Here I assumed the table containing translations is called dbo.Mappings and beside the Original and New columns you need another column Pos int which will be used to determine the order in which the translations are applied (to address the problems mentioned by #Thorsten Kettner in comments)
Also with recursive cte:
DECLARE #translations TABLE
(
Id INT ,
Translation NVARCHAR(20)
)
INSERT INTO #translations
VALUES ( 1, 'Peter' ),
( 2, 'Sandra' ),
( 3, 'Olga' )
DECLARE #replacements TABLE
(
Original VARCHAR(2) ,
New VARCHAR(2)
)
INSERT INTO #replacements
VALUES ( 'e', '#' ),
( 'r', '?' ),
( 'lg', '*%' );
WITH cte1 AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT 1)) rn
FROM #translations CROSS JOIN #replacements),
cte2 AS (SELECT Id, rn, REPLACE(Translation, Original, New) AS NTranslation
FROM cte1
WHERE rn = 1
UNION ALL
SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Original, c1.New)
FROM cte1 c1
JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn)
SELECT * FROM cte2
WHERE rn = (SELECT COUNT(*) FROM #replacements)
ORDER BY Id
EDIT:
WITH cte1 AS (SELECT t.*, p.Id AS Old, p.Code, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY (SELECT 1)) rn
FROM translations t CROSS JOIN Property p),
cte2 AS (SELECT Id, rn, REPLACE(Trans, Old, Code) AS NTranslation
FROM cte1
WHERE rn = 1
UNION ALL
SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Old, c1.Code)
FROM cte1 c1
JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn)
SELECT * FROM cte2
WHERE rn = (SELECT COUNT(*) FROM Property)
ORDER BY Id
Here is something I worked out that will allow you to replace multiple characters with one specified string.
[Split2] is stolen from https://blogs.msdn.microsoft.com/amitjet/2009/12/11/convert-comma-separated-string-to-table-4-different-approaches/
USE <Your Database>
GO
CREATE FUNCTION [dbo].[Split2]
(
#strString varchar(4000)
)
RETURNS #Result TABLE
(
RID INT IDENTITY(0,1) Primary Key
,Value varchar(4000)
)
AS
BEGIN
WITH StrCTE(start, stop) AS
(
SELECT 1, CHARINDEX(',' , #strString )
UNION ALL
SELECT stop + 1, CHARINDEX(',' ,#strString , stop + 1)
FROM StrCTE
WHERE stop > 0
)
INSERT INTO #Result
SELECT SUBSTRING(#strString , start, CASE WHEN stop > 0 THEN stop - start ELSE 4000 END) AS stringValue
FROM StrCTE
RETURN
END
GO
USE <Your Database>
GO
CREATE FUNCTION [dbo].[MultiReplace]
(
#MyString varchar(MAX)
,#RepChars varchar(4000)
,#NewChars varchar(4000)
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #CurRow int = 0
DECLARE #MaxRow int
SELECT #MaxRow = MAX(RID)
FROM dbo.split2 ( #RepChars )
WHILE #CurRow <= #MaxRow
BEGIN
SELECT #MyString = REPLACE(#MyString,VALUE,#NewChars)
FROM dbo.split2 ( #RepChars )
WHERE RID = #CurRow
SET #CurRow = #CurRow + 1
END
RETURN (#MyString);
END
GO
In this example I replace each character with no space
SELECT [dbo].[MultiReplace]('6th month 2016-06 (test / requested)',',1st,2nd,3rd,4th,5th,6th,0,1,2,3,4,5,6,7,8,9,(,),/,-,+, ','')
Result:
monthtestrequested
I hope this is useful for you.

initialize and increment variable inside cte query sqlserver 2008

I am using sqlserver 2008 ,I want to initialize and increment variable (#NUMTwo) both at the same time, in my second part(Problem Line).
I am creating a cte query.
Is this possible , if yes then please let me know.
following is a sample example.I hope i am clear.
CREATE table #TempTable
(
childProductID INT,parentProductID INT,productModel varchar(50),[Num2] VARCHAR(100)
)
DECLARE #NUMTwo INT = 0
WITH tableR AS
(
-- First Part
SELECT childProductID = null,parentProductID=null,productModel from Products where productid in (#a),[Num2] = convert(varchar(100), '')
UNION ALL
--Second Part
SELECT e.childProductID,e.parentProductID,prd.productModel FROM ProductIncludes AS e
,[Num2] = convert(varchar(100),'1.' + #NUMTwo+=1 ) -- Problem line
INNER JOIN Products AS PRD ON e.childProductID = PRD.productID
WHERE parentProductID in (#a)
)
INSERT INTO #TempTable(childProductID,parentProductID,productModel,[Num2])
SELECT childProductID,parentProductID,productModel,[Num2]
END
SELECT * FROM #TempTable
You need to "Initialize" a column in the acnhor part of the query, and then "oncrement" this column in the recursive parts.
Something like
DECLARE #NUMTwo INT = 0
;WITH Test AS (
SELECT [Num2] = convert(varchar(MAX), ''),
#NUMTwo [N]
UNION ALL
SELECT [Num2] = '1.' + convert(varchar(MAX),[N]+1),
[N]+1
FROM TEst
WHERE [N] < 10
)
SELECT *
FROM Test
SQL Fiddle DEMO
If the parameter #NUMTwo is just for numbering rows you can use the ROW_NUMBER() OVER(...) instead of it like so:
WITH tableR AS
(
SELECT childProductID = NULL, parentProductID = NULL,
productModel, NUMTwo = CAST('0' AS VARCHAR(10))
FROM Products
WHERE
productid in (#a),
[Num2] = convert(varchar(100), '')
UNION ALL
SELECT e.childProductID, e.parentProductID,
prd.productModel,
NUMTwo = '1.' +
CAST( ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS VARCHAR(10))
FROM ProductIncludes AS e
INNER JOIN Products AS PRD ON e.childProductID = PRD.productID
WHERE parentProductID in (#a)
)

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