2

I have a Sql query which returns something like below:

Rating as at    Rating type    Rating
--------------------------------------
6/7/2012        Type1          A
6/7/2012        Type2          A+
6/7/2012        Type3          B
8/7/2012        Type1          C
8/7/2012        Type2          C+
8/7/2012        Type3          B
8/7/2012        Type4          A

As you can see the rating type is dynamic and I would like to display it in a pivot but I really do not know how to achieve this. The end result I would like is something like below:

Rating as at    Type1   Type2   Type3   Type4
6/7/2012        A       A+       B
8/7/2012        C       C       C+       A

I want to know how I can achieve this using sql. Or best how I would do it using LINQ C#??

Help would be appreciated. Thanks.

Bat_Programmer
  • 6,717
  • 10
  • 56
  • 67
  • possible duplicate of [Is it possible to Pivot data using LINQ?](http://stackoverflow.com/questions/167304/is-it-possible-to-pivot-data-using-linq) – Austin Salonen Jul 08 '12 at 00:35
  • 2
    @AustinSalonen in that post columns are specified as, Jan, Feb, Mar..In my case columns can be dynamic, that is the rating type is dynamic, how would i do that? – Bat_Programmer Jul 08 '12 at 00:50
  • 1
    This is *not* a duplicate of that answer. That "duplicate" does not say how to do so *dynamically*. –  Jul 08 '12 at 00:52

3 Answers3

1
select "Rating as at", 
max(case when Rating_Type = 'Type1' then Rating else 0 end) as Type1,
max(case when Rating_Type = 'Type2' then Rating else 0 end) as Type2,
max(case when Rating_Type = 'Type3' then Rating else 0 end) as Type3,
max(case when Rating_Type = 'Type4' then Rating else 0 end) as Type4,
from Table
group by "Rating as at"
iruvar
  • 22,736
  • 7
  • 53
  • 82
  • thats SQL, its clearly stated: `how I would do it using LINQ C#??` – Jeremy Thompson Jul 08 '12 at 01:10
  • paraphrasing the OP "I want to know how I can achieve this using sql. Or best how I would do it using LINQ C#??" Sounds like SQL is an option. – iruvar Jul 08 '12 at 01:11
  • sorry I noticed that, I was even going to paste in a query like yours, but then the duplicate diverted my attention. – Jeremy Thompson Jul 08 '12 at 01:13
  • Thanks but again in this case the columns are predefined. In my case there would be dynamic number of rating types (columns) existing for which I have to show in tabular format. I hope you understand what I am trying to achieve here. – Bat_Programmer Jul 08 '12 at 02:56
  • understood. dynamic column support is not to be found even with databases that natively support pivot/unpivot operators. If you need to solve this problem in the SQL domain you may have to take recourse to dynamic SQL. – iruvar Jul 08 '12 at 03:21
1

Updated answer based on OP's comment below - This update will now convert results to letter grades

The following uses a combination of SQL Server's Pivot operator (MSDN) and well as the EXEC statement (MSDN).

The T-SQL solution will handle completely dynamic columns.

--Create a temp table to hold values
CREATE TABLE #MyTable (
[Rating as at] DATE,
[Rating type] Nvarchar(30),
Rating FLOAT )

INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('6/7/2012','Type1', 1)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('6/7/2012','Type2', 3)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('6/7/2012','Type3', 5)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('8/7/2012','Type1', 5)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('8/7/2012','Type2', 2)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('8/7/2012','Type3', 4)
INSERT INTO #MyTable ([Rating as at],[Rating type],Rating) VALUES ('8/7/2012','Type4', 1)
SELECT DISTINCT [Rating type] INTO #MyTableDistinct FROM #MyTable

--Create a string of dynamic fields
DECLARE @MyVar1 nvarchar(max),@MyVar2 nvarchar(max)
SELECT @MyVar1 = COALESCE(@MyVar1,'') + '[' + [Rating type] + ']' + ', '  FROM #MyTableDistinct 
SELECT @MyVar1 = LEFT(@MyVar1, LEN(@MyVar1) - 1)

--Create a string of dynamic CASE statements to be used to convert pivoted results to letter grades
--Update the CASE steatement to handle different grade types
SELECT @MyVar2 = COALESCE(@MyVar2,'') + 'CASE WHEN [' + [Rating type] + '] IS NULL THEN ''N/A'' WHEN [' + [Rating type] + '] > 4 THEN ''A'' WHEN [' + [Rating type] + '] > 3 THEN ''B'' WHEN [' + [Rating type] + '] > 2 THEN ''C'' ELSE ''F'' END AS [' + [Rating type] + '], '  FROM #MyTableDistinct 
SELECT @MyVar2 = LEFT(@MyVar2, LEN(@MyVar2) - 1)

--Build a SQL string to be later execute
--This is where all of the PIVOT magic happens
DECLARE @MySQLStatement nvarchar(max)
SET @MySQLStatement = 'SELECT [Rating as at],' + @MyVar1 +  ' INTO #MyPivotTable FROM 
    (SELECT [Rating as at],[Rating type],Rating from #MyTable) AS p1
    PIVOT (
        avg(Rating) FOR [Rating type] IN (' + @MyVar1 + ') 
    ) as p2;SELECT [Rating as at], ' + @MyVar2 + ' FROM #MyPivotTable;DROP TABLE #MyPivotTable;'

--Execute the SQL string
EXEC(@MySQLStatement)


DROP TABLE #MyTableDistinct
DROP TABLE #MyTable
NakedBrunch
  • 48,713
  • 13
  • 73
  • 98
  • ok. This works. But in the real scenario the rating figure is a text like e.g. A+, A, etc..I dont think we can average that and it would throw an error. – Bat_Programmer Jul 09 '12 at 04:08
  • 3
    It's isn't very fair to others when people work hard on giving you a solid answer to your problem and give you the exact solution you requested and then you say "This isn't what I really want." Why don't you update your question saying exactly what you need so that people can give you exactly what you need. Now, for your particular case, it sounds like you need to assign an integer value to each grade and then take the average values and convert back to a grade. So, if the average of grades is 2.3, for example, then this would be rounded down to 2 and you could assign a B+. – NakedBrunch Jul 09 '12 at 11:07
  • sorry for the inconvenience caused. I have edited my question accordingly. – Bat_Programmer Jul 09 '12 at 20:31
  • Not an inconvenience at all. It's really to your benefit to have the most accurate question possible so that you can get the best answer possible. – NakedBrunch Jul 09 '12 at 21:03
  • Your edit still leaves some open questions: what happens if one day has more than one grade? For example, if one day has an A+ and the same day has a C then what would that day show when pivoted? If each day has only one possible grade and no more then the solution will be simple to implement. – NakedBrunch Jul 09 '12 at 21:06
  • Yea each day there will be a single rating only. So in this case the above solution would work. I'll mark it as a answer, as it helped me in solving my problem in all dimensions. Thanks once again. – Bat_Programmer Jul 10 '12 at 10:11
0

It might be easiest to get the data as you have and convert it using linq. You can never really get out an object with properties of Type1, Type2 etc because you need to do this at runtime but you can get an array of the values. You would first need to get a list of all rating types and then have a second query to pivot the data. Something like this:

public class SomeClass
{
    public DateTime RatingDate;
    public string RatingType;
    public int Rating;
}

var data = new SomeClass[]
{
    new SomeClass() { RatingDate = DateTime.Parse("6/7/2012"), RatingType = "Type1", Rating = 1 },
    new SomeClass() { RatingDate = DateTime.Parse("6/7/2012"), RatingType = "Type2", Rating = 3 },
    new SomeClass() { RatingDate = DateTime.Parse("6/7/2012"), RatingType = "Type3", Rating = 5 },
    new SomeClass() { RatingDate = DateTime.Parse("8/7/2012"), RatingType = "Type1", Rating = 5 },
    new SomeClass() { RatingDate = DateTime.Parse("8/7/2012"), RatingType = "Type2", Rating = 2 },
    new SomeClass() { RatingDate = DateTime.Parse("8/7/2012"), RatingType = "Type3", Rating = 4 },
    new SomeClass() { RatingDate = DateTime.Parse("8/7/2012"), RatingType = "Type4", Rating = 1 }
};

var ratingTypes = data.Select(i => i.RatingType)
    .Distinct().OrderBy(i => i).ToArray();

var results = data.GroupBy(i => i.RatingDate)
    .Select(g => new { RatingDate = g.Key, Ratings = ratingTypes.GroupJoin(g, o => o, i => i.RatingType, (o, i) => i.Select(x => x.Rating).Sum()).ToArray() })
    .ToArray();

You could break the group join out into a separate function to make it a bit more readable if you like:

Func<IEnumerable<SomeClass>, int[]> func
    = group => ratingTypes.GroupJoin(group, o => o, i => i.RatingType, (o, i) => i.Select(x => x.Rating).Sum()).ToArray();

var results = data.GroupBy(i => i.RatingDate)
    .Select(g => new { RatingDate = g.Key, Ratings = func(g) })
    .ToArray();
MikeKulls
  • 2,979
  • 2
  • 25
  • 30