Is it possible to group string within a string in Teradata? - sql

The original table (exactly the one that I am using .. with all commas brackets etc)
id attributes
1 123(red), 139(red), 123(white), 123(black), 139(white),
2 123(black), 139(white), 123(green),
32 223(blue), 223(red), 553(white), 123(black),
4 323(white), 139(red),
23 523(red),
I need to group the attribute numbers so that my table looks like
id attributes
1 123(red, white, black); 139(red, white);
2 123(black, green); 139(white);
32 223(blue, red); 553(white); 123(black);
4 323(white); 139(red);
23 523(red);
How can I do this?
Unfortunately i do not have access to stored procedures and functions like oreplace .. translate. I used to deal with Oracle previously and this is an easy task given one has access to stored procedures ... here i have no idea what to do

SQL is definitely not the right language to do string processing like that :-)
I used existing code to split/create comma-delimited strings, but in TD14 it would be much easier (there's strtok_split_to_table and udfConcat).
CREATE VOLATILE TABLE vt (id INT, attrib VARCHAR(100)) ON COMMIT PRESERVE ROWS;
INSERT INTO vt(1 ,'123(red), 139(red), 123(white), 123(black), 139(white),');
INSERT INTO vt(2 ,'123(black), 139(white), 123(green),');
INSERT INTO vt(32 ,'223(blue), 223(red), 553(white), 123(black),');
INSERT INTO vt(4 ,'323(white), 139(red), ');
INSERT INTO vt(23 ,'523(red),');
WITH RECURSIVE cte
(id,
len,
remaining,
word,
pos
) AS (
SELECT
id,
POSITION(',' IN attrib || ',') - 1 AS len,
SUBSTRING(attrib || ',' FROM len + 2) AS remaining,
TRIM(SUBSTRING(attrib FROM 1 FOR len)) AS word,
1
FROM vt
UNION ALL
SELECT
id,
POSITION(',' IN remaining)- 1 AS len_new,
SUBSTRING(remaining FROM len_new + 2),
TRIM(SUBSTRING(remaining FROM 1 FOR len_new)),
pos + 1
FROM cte
WHERE remaining <> ''
)
SELECT
id,
MAX(CASE WHEN newpos = 1 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 2 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 3 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 4 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 5 THEN newgrp ELSE '' END) ||
MAX(CASE WHEN newpos = 6 THEN newgrp ELSE '' END)
-- add as many CASEs as needed
FROM
(
SELECT
id,
ROW_NUMBER()
OVER (PARTITION BY id
ORDER BY newgrp) AS newpos,
a ||
MAX(CASE WHEN pos = 1 THEN '(' || b ELSE '' END) ||
MAX(CASE WHEN pos = 2 THEN ', ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 3 THEN ', ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 4 THEN ', ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 5 THEN ', ' || b ELSE '' END) ||
MAX(CASE WHEN pos = 6 THEN ', ' || b ELSE '' END)
-- add as many CASEs as needed
|| '); ' AS newgrp
FROM
(
SELECT
id,
ROW_NUMBER()
OVER (PARTITION BY id, a
ORDER BY pos) AS pos,
SUBSTRING(word FROM 1 FOR POSITION('(' IN word) - 1) AS a,
TRIM(TRAILING ')' FROM SUBSTRING(word FROM POSITION('(' IN word) + 1)) AS b
FROM cte
WHERE word <> ''
) AS dt
GROUP BY id, a
) AS dt
GROUP BY id;

Related

Split single column into multiple columns based on Rank Number

I want to spit a column into multiple columns based on the rank by partition or column type
Sample Input Data:
STUDENT PROGRAM_ID DEG_TYPE PROGRAM_RN
1 Program1.MA MA 1
1 Program2.MA MA 1
2 Program1.PHD DOC 1
2 Program2.MA MA 2
3 Program.CERT CERT 3
3 Program1.PSYD DOC 1
3 Program2.MA MA 2
Expected Output
Student Highest Program Second Highest Program Third Highest Program
1 Program1.MA, Program2.MA
2 Program1.PSYD Program2.MA
3 Program1.PHD Program2.MA Program.CERT
I have tried using PIVOT and I am able to split the column but with that I am able to fetch only one program as Highest, second and so on. My requirement is to get all the Programs that has RN 1 listed for each student in highest program, all with RN 2 listed in Second and so on.
**CODE**
SELECT [STUDENTS_ID],[1], [2], [3]
FROM
(SELECT [STUDENTS_ID]
,[PROGRAM_ID]
,[PROGRAM_RN]
FROM [dbo].[CTE] ) AS SourceTable
PIVOT
(
MAX([PROGRAM_ID])
FOR [PROGRAM_RN] IN ([1], [2], [3])
) As PivotTable;
Current Output
Student Highest Program Second Highest Program Third Highest Program
1 Program1.MA
2 Program1.PSYD Program2.MA
3 Program1.PHD Program2.MA Program.CERT
I also want to know if there is a better way of doing this
If you are using SQL 2017 or higher version, you can use string_agg function instead of XML PATH in below query else you can use the same query to dynamically achieve the result set you want.
DECLARE #Columns AS NVARCHAR(MAX),
#Query AS NVARCHAR(MAX)
SET #Columns = STUFF((SELECT DISTINCT ',' + QUOTENAME(c.PROGRAM_RN)
FROM StudentTable c
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #Query = 'SELECT STUDENT, ' + #Columns + ' FROM
(
SELECT DISTINCT ST2.STUDENT ,PROGRAM_RN,
SUBSTRING(
(
SELECT '', ''+ST1.PROGRAM_ID AS [text()]
FROM dbo.StudentTable ST1
WHERE ST1.STUDENT = ST2.STUDENT AND ST1.PROGRAM_RN = ST2.PROGRAM_RN
ORDER BY ST1.STUDENT
FOR XML PATH ('''')
), 2, 1000) [PROGRAM_ID]
FROM dbo.StudentTable ST2
) x
PIVOT
(
MAX(PROGRAM_ID)
FOR PROGRAM_RN IN (' + #Columns + ')
) p '
EXEC(#Query)
Sort of odd that you have ties. But you can use conditional aggregation with strings:
select student,
string_agg(case when program_rn = 1 then program_id end, ', '),
string_agg(case when program_rn = 2 then program_id end, ', '),
string_agg(case when program_rn = 3 then program_id end, ', ')
from t
group by student;
If you know the maximum that need to be concatenated, you can use conditional aggregation:
select student,
concat(max(case when program_rn = 1 and seqnum = 1 then program_id + '; ' end),
max(case when program_rn = 1 and seqnum = 2 then program_id + '; ' end),
max(case when program_rn = 1 and seqnum = 3 then program_id + '; ' end)
),
concat(max(case when program_rn = 2 and seqnum = 1 then program_id + '; ' end),
max(case when program_rn = 2 and seqnum = 2 then program_id + '; ' end),
max(case when program_rn = 2 and seqnum = 3 then program_id + '; ' end)
),
concat(max(case when program_rn = 3 and seqnum = 1 then program_id + '; ' end),
max(case when program_rn = 3 and seqnum = 2 then program_id + '; ' end),
max(case when program_rn = 3 and seqnum = 3 then program_id + '; ' end)
),
from (select t.*,
row_number() over (partition by student, program_rn order by program_id) as seqnum
from t
) t
group by student;
This is cumbersome, but possibly simpler than FOR XML PATH.
Note that I changed the delimiter to a semicolon, because that seems more natural for leaving it at the end of the list. Although it can be removed, that just further complicates the logic, perhaps unnecessarily.

Can I use a WHILE loop inside a SQL SELECT Query

I need to create a formatted text (CSV) output with an even number of columns from a SQL query of two tables that do not have the same number of elements. I can't seem to pad the data to the required number of columns.
Sample Data
table Students(id, name)
values
(1, Alex),
(2, Bob),
(3, Charlie),
(4, David)
table Hobbies (studentId, hobby)
values
(2,'skating')
(2,'sailing')
(3,'reading')
(4,'video games')
(4,'paintball')
(4,'nascar')
(4,'baseball')
Desired Output
[ID][OutputString]
[0][Student,Hobby1,Hobby2,Hobby3,Hobby4]
[1][Alex,,,,]
[2][Bob,skating,sailing,]
[3][Charlie,reading,,,]
[4][David,video games,paintball,nascar,baseball]
Broken PseudoCode
DECLARE #maxHobbies INT = 4;
DECLARE #hobbyCount INT = 1;
DECLARE headerString varchar(max) = 'Student';
WHILE #hobbyCount <= #maxHobbies
BEGIN
set #headerString = #headerString + ',Hobby' + #hobbyCount
#hobbyCount = #hobbyCount + 1
END
DECLARE #outputString varchar(max)
SET #outputString = concat(#headerSting, char(13)) +
SELECT 0 AS ID, #headerString AS OutputString
UNION
SELECT
id,
name + (WHILE #hobbyCount <= #maxHobbies BEGIN
(CASE
WHEN EXISTS SELECT hobby FROM Hobbies where studentId = Students.id
THEN ',' + SELECT hobby FROM Hobbies where studentId = Students.id
ELSE ','
END)
END)
FROM Students
You want up to 4 hobbies. You can do this with conditional aggregation:
select s.id,
(s.name + ',' +
max(case when seqnum = 1 then h.hobby else '' end) + ',' +
max(case when seqnum = 2 then h.hobby else '' end) + ',' +
max(case when seqnum = 3 then h.hobby else '' end) + ',' +
max(case when seqnum = 4 then h.hobby else '' end)
) as outputstring
from students s left join
(select h.*,
row_number() over (partition by h.studentid order by (select null)) as seqnum
from hobbies h
) h
on h.studentid = s.id
group by s.id, s.name;

SQL count string matches in each row

Please take a look at this simple SQL server database :
What I want is, I want to create a summary with only 3 column, here is the code:
select ProductID, Name,
*code* as CountString
from product
where Name in ('this', 'is', 'count', 'example')
I want the result to have 3 column, and the column "CountString" is the total number of string that matches ('this','is', 'count', 'example'). Here is the result I want :
So for example, I want the Countstring for ProductID 1 is 4, because it contains all of 4 words.
If you can solve this, it would be amazing!
If I understand correctly:
select ProductID, Name,
( (case when Name like '%this%' then 1 else 0 end) +
(case when Name like '%is%' then 1 else 0 end) +
(case when Name like '%count%' then 1 else 0 end) +
(case when Name like '%example%' then 1 else 0 end)
) as CountString
from product;
Note: Any Name that has "this" also has "is".
If "words" are separated by spaces (and only spaces), you can do:
select ProductID, Name,
( (case when concat(' ', Name, ' ') like '% this %' then 1 else 0 end) +
(case when concat(' ', Name, ' ') like '% is %' then 1 else 0 end) +
(case when concat(' ', Name, ' ') like '% count %' then 1 else 0 end) +
(case when concat(' ', Name, ' ') like '% example %' then 1 else 0 end)
) as CountString
from product;
The following query should suffice your need ---
SELECT PRODUCTID,
NAME,
REGEXP_COUNT(NAME, 'this|is|count|example', 1, 'c') CountString
FROM product;
This query will result in "Case Sensitive" checking, means only "example" will be counted not "Example". If you want "Case Insensitive" checking just put 'i' instead of 'c'.

convert certain column names with comma separated string from sql table with conditions

For example , I have this table with different column names and the Boolean value below it,
case1 case2 case3 case4
1 0 1 0
What I want to retrieve,only column names with 1 value. So, my desired results from the query should only be case1,case3
Desired Output : case1,case3
there is only one row fetch from sql query
Is there any way?
If I understand correctly, you could use a big case statement:
select stuff(( (case when case1 = 1 then ',case1' else '' end) +
(case when case2 = 1 then ',case2' else '' end) +
(case when case3 = 1 then ',case3' else '' end) +
(case when case4 = 1 then ',case4' else '' end)
), 1, 1, '') as columns
In the case you have multiple rows.
Query
select stuff((
(case when count(*) = sum(cast(case1 as int)) then ',case1' else '' end) +
(case when count(*) = sum(cast(case2 as int)) then ',case2' else '' end) +
(case when count(*) = sum(cast(case3 as int)) then ',case3' else '' end) +
(case when count(*) = sum(cast(case4 as int)) then ',case4' else '' end)), 1, 1, '')
as no_zero_columns
from your_table_name;
SQL Fiddle Demo

MS sql combine columns in select

I am trying to combine multiple columns (varchar, but used to store boolean 'Y' or '') into a single column (list) with human readable text.
The Table layout is like this:
MEMBER_ID (int) | PROC (varchar) | 50K_12_MTHS (varchar) | 100K_12_MTHS (varchar)
1||||
2|Y|Y||
3|Y|Y|Y|
4|Y|||
For the output of the able sample I am trying to get:
1|
2|Proc, 50
3|Proc, 50, 100
4|Proc
I think the way to do this is with a Case (see below) but can't get it to work.
SELECT
MEMBER_ID,
Gorup =
Select(
CASE PROC
WHEN 'Y'
THEN 'Proc'
END + ', ' +
CASE 50K_12_MTHS
WHEN 'Y'
THEN '50K'
END-- + ', ' +
CASE 100K_12_MTHS
WHEN 'Y'
THEN '100K'
END + ', ' +)
from Members
Nearly...!
SELECT
MEMBER_ID,
CASE [PROC]
WHEN 'Y' THEN 'Proc, '
ELSE ''
END +
CASE [50K_12_MTHS]
WHEN 'Y' THEN '50K,'
ELSE ''
END +
CASE [100K_12_MTH]
WHEN 'Y' THEN '100K, '
ELSE ''
END as [group]
from Members
Try this
SELECT
MEMBER_ID,
(CASE [PROC] WHEN 'Y'
THEN 'Proc' ELSE ''
END +
CASE [50K_12_MTHS] WHEN 'Y'
THEN ', 50K' ELSE '' END
+ CASE [100K_12_MTHS] WHEN 'Y'
THEN ', 100K' ELSE ''
END) as [GROUP]
from Members