Parse through delimiters, calc combinations

Hi,
Given this table where each class is delimited by a pipe:

STUDENT     CLASSES
Jack              Algebra 1|Spanish|Literature
Jill                AP Calc| Spanish 2| History| Anatomy
James           Study Hall
Jill                Study Hall
Jill                AP Calc

I need to find the total number of classes each student has and the total number of distinct permutations. It is NOT guaranteed that a row is distinct or that a class will not appear in another row for the same student.

Example Results:
Jill would have a total of 5 classes (AP Calc, Spanish 2, History, Anatomy, and Study Hall)

Total Student to Class Permutations: 9

Two separate SQL queries would be fine.

Thanks!
LVL 2
c0feeAsked:
Who is Participating?

Improve company productivity with a Business Account.Sign Up

x
 
Brian CroweConnect With a Mentor Database AdministratorCommented:
Unfortunately the APPLY operator requires a compatibility level of 90 (SQL Server 2008) so it will not work for you in 2005.  There are many examples of split functions on the web that would work depending on how much flexibility you want.

http://www.biitb.com/index.php/t-sql-split-function/

An alternative to the APPLY operator would be to use a CURSOR and cycle through each entry in your table using the split function and inserting those rows in a temp table.
0
 
Brian CroweDatabase AdministratorCommented:
You will need a table-valued split function to break down the delimited class column then use CROSS APPLY.  Could you define what you mean by "Total Student to Class Permutations"?

DECLARE       @t TABLE
(
      Student            VARCHAR(50),
      Classes            VARCHAR(250)
)

INSERT INTO @t (Student, Classes)
VALUES ('Jack', 'Algebra 1|Spanish|Literature'),
      ('Jill', 'AP Calc| Spanish 2| History| Anatomy'),
      ('James', 'Study Hall'),
      ('Jill', 'Study Hall'),
      ('Jill', 'AP Calc')

SELECT t.Student, COUNT(DISTINCT SplitClasses.Item)
FROM @t AS t
      CROSS APPLY dbo.Split(t.Classes, '|') AS SplitClasses
GROUP BY t.Student
ORDER BY t.Student
0
 
c0feeAuthor Commented:
Hi BriCrowe - How do I create the function? I am using a SQL 2005 server (will also need to apply this 2008). I don't see it as an available function to call.

Also, by Total Student to Class Permutation - I meant that I need to know all possible combinations in the table. Jill has 5 combinations, Jack has 3, and James has 1 for a total of 9.

Thanks!
0
A proven path to a career in data science

At Springboard, we know how to get you a job in data science. With Springboard’s Data Science Career Track, you’ll master data science  with a curriculum built by industry experts. You’ll work on real projects, and get 1-on-1 mentorship from a data scientist.

 
Scott PletcherSenior DBACommented:
CROSS APPLY works in SQL 2005.
0
 
Brian CroweDatabase AdministratorCommented:
Thanks, I was off...i forgot that 2008 and 2008R2 where the same compatibility level (100)
0
 
Scott PletcherConnect With a Mentor Senior DBACommented:
For good performance, you want to use an inline table function, not a multi-statement table function, and one that's been tuned for performance.

You can create the function in a shared database and call it from any/all other dbs.


CREATE FUNCTION [dbo].[SplitStringIntoTable]  (
    @string varchar(8000),
    @delimiter varchar(5)
    )
RETURNS TABLE WITH SCHEMABINDING
AS
--SELECT ss.* FROM (SELECT 'ab/c/def/ghijklm/no/prq/////st/u//' AS string) AS test_values CROSS APPLY <db_name>.dbo.SplitStringIntoTable(test_values.string, '/') AS ss
RETURN
--Inline-CTE-Driven "tally table" produces values from 0 to 9999 ... enough to cover varchar(8000)
WITH cteDigits AS (
    SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
    SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
cteTally AS (
    SELECT [1000s].digit * 1000 + [100s].digit * 100 + [10s].digit * 10 + [1s].digit AS num
    FROM cteDigits [1s] --0 thru 9
    CROSS JOIN cteDigits [10s] --thru 99
    CROSS JOIN cteDigits [100s] --thru 999
    CROSS JOIN cteDigits [1000s] --thru 9999
),
cteBase(num) AS ( --Limit the number of rows up front, for both a performance gain and prevention of accidental "overruns"
        SELECT 0 UNION ALL
        SELECT TOP (DATALENGTH(ISNULL(@string, 1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM cteTally
),
cteStart(num1) AS ( --This returns num + 1 (starting position of each "item" just once for each delimiter)
        SELECT b.num + 1
        FROM cteBase b
        WHERE (SUBSTRING(@string, b.num, 1) = @delimiter OR b.num = 0)
)
--Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ROW_NUMBER() OVER(ORDER BY s.num1) AS id,
       LTRIM(RTRIM(SUBSTRING(@string, s.num1, ISNULL(NULLIF(CHARINDEX(@delimiter, @string, s.num1), 0) - s.num1, 8000)))) AS value
FROM cteStart s;
GO
0
 
Scott PletcherSenior DBACommented:
SELECT t.Student, COUNT(DISTINCT ss.Value)
FROM @t AS t
      CROSS APPLY <shared_db>.dbo.SplitStringIntoTable(t.Classes, '|') AS ss
GROUP BY t.Student
ORDER BY t.Student
0
 
c0feeAuthor Commented:
Thank you both! I used BriCrowe's solution for what I needed - just a temporary analysis. But I appreciate having the other solution as well. I'm sure it will come in handy.
0
 
c0feeAuthor Commented:
Thank you both! I used BriCrowe's solution for what I needed - just a temporary analysis. But I appreciate having the other solution as well. I'm sure it will come in handy.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.