Dynamically Pivot unknown Data - c#

Trying to pivot dynamic data using LINQ or LAMBDA in C#/MVC4 and have pretty much come to the conclusion that its very difficult to do..
This is basically what I want to do:
I have been able to get this to work with known column names using this example: http://geekswithblogs.net/malisancube/archive/2009/04/21/using-lambda-or-linq-for-pivot-tables.aspx
But I cant find any examples for doing this with dynamic columns.
By dynamic columns I mean that there could be a new row with a different Name and FieldType that has not been in the table before at any time that also needs to be turned into a column.. any pointers?.

I don't know LINQ so I will give you a version that can be used in a SQL Server stored procedure. This type of data transformation is known as a PIVOT. Since you are using SQL Server 2008+, you can use the function.
If you know the values that you want to transform, then you can hard-code the values:
SELECT nodeid, rowid,[FirstName], [LastName], [Title]
FROM
(
SELECT nodeid, rowid, name, value
FROM yourTable
) x
PIVOT
(
max(value)
for name in ([FirstName], [LastName], [Title])
)p
See SQL Fiddle with Demo
Then if you have an unknown number of values, you can implement dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(name)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT nodeid, rowid,' + #cols + ' from
(
SELECT nodeid, rowid, name, value
FROM yourTable
) x
pivot
(
max(value)
for name in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
Both return the results:
| NODEID | ROWID | FIRSTNAME | LASTNAME | TITLE |
--------------------------------------------------
| 1 | 1 | Alfred | Beagle | (null) |
| 1 | 2 | Freddy | (null) | (null) |
| 1 | 3 | (null) | Grey | Sir. |

Related

function set table name as parameter and get columns name and table name from XML file in sqlcommand c# [duplicate]

Here is my SQL query below. I want to select values from the column names given as variables. Is there any appropriate way of doing this except using a dynamic query?
SELECT EPV.EmployeeCode, #RateOfEmployee, #RateOfEmployer
FROM [HR_EmployeeProvisions] EPV
One way to do this without using dynamic sql is using CASE statement
But this is ugly
SELECT EPV.EmployeeCode, case #RateOfEmployee when 'RateOfEmployee' then RateOfEmployee
when 'X' then X
..
end , case #RateOfEmployer when 'RateOfEmployer' then RateOfEmployer
when 'Y' then Y
..
end
FROM [HR_EmployeeProvisions] EPV
You have to check all the column's in CASE statement.
You can't parameterize identifiers in Sql server, and I doubt it's possible in any other relational database.
Your best choice is to use dynamic Sql.
Note that dynamic sql is very often a security hazard and you must defend your code from sql injection attacks.
I would probably do something like this:
Declare #Sql nvarchar(500)
Declare numberOfColumns int;
select #numberOfColumns = count(1)
from information_schema.columns
where table_name = 'HR_EmployeeProvisions'
and column_name IN(#RateOfEmployee, #RateOfEmployer)
if #numberOfColumns = 2 begin
Select #Sql = 'SELECT EmployeeCode, '+ QUOTENAME(#RateOfEmployee) +' ,'+ QUOTENAME(#RateOfEmployer) +
'FROM HR_EmployeeProvisions'
exec(#Sql)
end
This way you make sure that the column names actually exists in the table, as well as using QUOTENAME as another layer of safety.
Note: in your presentation layer you should handle the option that the select will not be performed since the column names are invalid.
Have a look at UNPIVOT clause - I'm not sure it is applicable for your case but in some circumstances it can be used to query a value by the column name without dynamic SQL:
create table t1 (
a int,
b int,
c int
);
insert into t1 values
(1, 11, 111),
(2, 22, 222),
(3, 33, 333);
select a, col_name, col_value from t1
unpivot (col_value for col_name in (b, c)) as dt;
Result:
| a | col_name | col_value |
|---|----------|-----------|
| 1 | b | 11 |
| 1 | c | 111 |
| 2 | b | 22 |
| 2 | c | 222 |
| 3 | b | 33 |
| 3 | c | 333 |
(SQL Fiddle)
If you only need a value in a depending on some condition on (dynamically) either b or c, you can build the condition on that. If you need either values in column b or c, you can add ... WHERE col_name = ?. If you need more columns, you'd probably need to filter the column values on the un-pivoted table than pivot it again to get the values back in columns.

Select Data with Column's Description not null, and then replace column name with description

I have a table 'PurchaseOrder'. I want select data when column's description is not null. and then replace the column's name with it's description.
table PurchaseOrder
ID | Product | Use | Price | Comment
-----------------------------------------------------
1 | Cellphone | true | 100 |
2 | Car | false | 10 |
3 | Table | true | 200 |
Column
Name | Description
---------------------------
ID | Index
Produce | Name
Use |
Price | Sale
Comment |
The Select result will be
Index | Name | Sale
--------------------------------
1 | Cellphone | 100
2 | Car | 10
3 | Table | 200
Now, I just get the column with description
if object_id('tempdb..#dt') is not null drop table #dt
select
sc.column_id [Position],
sc.name [Column],
sc.max_length [Length],
sep.value [Description]
into #dt
from sys.tables st
inner join sys.columns sc on st.object_id = sc.object_id
left join sys.extended_properties sep on st.object_id = sep.major_id
and sc.column_id = sep.minor_id
and sep.name = 'MS_Description'
where st.name = 'PurchaseOrder' and sep.value is not null
How to generate the final data table?
DECLARE
#TABLE_NAME NVARCHAR(50) = 'PurchaseOrderDetail',
#TABLE_SCHEMA Nvarchar(50) = 'Purchasing',
#COL_LIST NVARCHAR(MAX),
#SQL NVARCHAR(4000),
#COL NVARCHAR(500)
DECLARE c_cursor CURSOR FOR
SELECT sc.name + ' as ' + QUOTENAME(CAST(sep.value AS varchar)) AS Expr1
FROM sys.tables AS st INNER JOIN
sys.columns AS sc ON st.object_id = sc.object_id INNER JOIN
sys.extended_properties AS sep ON st.object_id = sep.major_id AND sc.column_id = sep.minor_id AND sep.name = 'MS_Description'
WHERE st.name = #TABLE_NAME AND sep.value IS NOT NULL
OPEN c_cursor;
FETCH NEXT
FROM c_cursor INTO #COL;
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
SELECT #COL_LIST = COALESCE(#COL_LIST+',' ,'') + #COL
FETCH NEXT FROM c_cursor
INTO #col;
END CLOSE c_cursor;
DEALLOCATE c_cursor;
SET #SQL = ' SELECT ' + #COL_LIST +' FROM ' + QUOTENAME(#TABLE_SCHEMA)+'.'+QUOTENAME(#TABLE_NAME)
PRINT #SQL;
EXEC (#sql);

Is it possible to produce a report matrix as a result set in sql query given a singe database table

Given the following SQL table:
GroupAvailable, GroupAssigned, Title
----------------------------------------
GroupA,GroupA,Widget1
GroupA,GroupB,Widget1
GroupB,GroupA,Widget1
Need a report to look like:
Widget1
GroupA GroupB
GroupA X X
GroupB X
Widget2
etc.
Is this possible to do in query? Or something pretty close? Or would a combination of code and sql be better?
The PIVOT function will get you close to the result:
select name,
coalesce(GroupA, '') GroupA,
coalesce(GroupB, '') GroupB
from
(
select title +' '+ groupavailable name,
groupassigned,
'X' as flag
from yourtable
) d
pivot
(
max(flag)
for groupassigned in (GroupA, GroupB)
) piv;
See SQL Fiddle with Demo.
If you have an unknown number of groups, then you could use dynamic SQL to get the result:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(GroupAvailable)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT distinct ', coalesce(' + QUOTENAME(GroupAvailable)+', '''') as '+QUOTENAME(GroupAvailable)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #colsNull + '
from
(
select title +'' ''+ groupavailable name,
groupassigned,
''X'' as flag
from yourtable
) x
pivot
(
max(flag)
for groupassigned in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo
Both versions will give a result:
| NAME | GROUPA | GROUPB |
------------------------------------
| Widget1 GroupA | X | X |
| Widget1 GroupB | X | |
| Widget2 GroupB | X | |

combine multiple sql rows with different columns

Okay so say I have something like this:
ID | Name | Address
1 | Bob | 123 Fake Street
1 | Bob | 221 Other Street
done by doing something like:
select p.ID, p.Name a.Address from People p
inner join Addresses a on a.OwnerID = p.ID
Is there any way to turn that into
ID | Name | Address_1 | Address_2 | etc...
1 | Bob | 123 Fake Street | 221 Other street | etc
I've seen things that do comma separated values in one column but I don't want that I want distinct columns. I am querying this using MSSQL and C# I don't know if that changes anything. Also this is a made up scenario that is just similar to what I'm doing so the actual structure of the tables can't be changed.
Anyone have any suggestions?
You can use the PIVOT function to get the result but you will also have to implement using a row_number() so you can convert multiple addresses per person into columns.
If you had a known number of addresses, then you would hard-code the query:
select id, name, address_1, address_2
from
(
select p.id, p.name, a.address,
'Address_'+cast(row_number() over(partition by p.id
order by a.ownerid) as varchar(10)) rn
from people p
inner join addresses a
on p.id = a.ownerid
) d
pivot
(
max(address)
for rn in (address_1, address_2)
) piv;
See SQL Fiddle with Demo.
But if your case, you will have an unknown number of addresses per person so you will want to use dynamic SQL and place it into a stored procedure to execute:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Address_'+d.rn)
from
(
select cast(row_number() over(partition by a.ownerid
order by a.ownerid) as varchar(10)) rn
from Addresses a
) d
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT id, name, ' + #cols + '
from
(
select p.id, p.name, a.address,
''Address_''+cast(row_number() over(partition by p.id
order by a.ownerid) as varchar(10)) rn
from people p
inner join addresses a
on p.id = a.ownerid
) d
pivot
(
max(address)
for rn in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. These both give a result:
| ID | NAME | ADDRESS_1 | ADDRESS_2 | ADDRESS_3 |
----------------------------------------------------------------
| 1 | Bob | 123 Fake Street | 221 Other Street | (null) |
| 2 | Jim | 123 e main street | (null) | (null) |
| 3 | Tim | 489 North Drive | 56 June Street | 415 Lost |

Query results as comma-delimited string without using PIVOT?

i want to create a query from multiple records as one record , but i don't want to use Pivot, FYI , i have > 260 Records , it is impossible if i Write Manual PIVOT,
is there any Simple solutions?
here's the table Example :
ID Element_Name Value
1 Parmitha 100
2 Anggun 200
3 Chandra 300
4 BagusofTerror 400
5 Laras 500
6 Jessica 600
7 Aisyah 700
......
200 Sonya 20000
and i want the result is like this :
paramitha , anggun, chandra , bagusofterror, Laras , Jessica , Aisyah, ..... , Sonya
100 , 200, 300, 400,500,600,700,....,20000
In reference to http://blog.sqlauthority.com/2009/12/21/sql-server-comma-separated-values-csv-from-table-column-part-2/ you could use the following SQL query
I havent run this, so I'm not 100% sure it'll work
SELECT STUFF(
(SELECT ',' + s.ElementName
FROM tableName s
FOR XML PATH('')),1,1,'')
UNION ALL
SELECT STUFF(
(SELECT ',' + s.Value
FROM tableName s
FOR XML PATH('')),1,1,'')
We used to call this a cross-tab query. Not sure if it still is...
SELECT SUM(CASE WHEN id=1 THEN value ELSE 0 END) AS parmitha,
SUM(CASE WHEN id=2 THEN value ELSE 0 END) AS anggun,
SUM(CASE WHEN id=3 THEN value ELSE 0 END) AS chandra,
...etc...
SUM(value) AS total
FROM My_Table
WHERE ...etc...
I don't know c#, but I'm sure you can get it to loop over the column to prepare the SUM().
How about this that I tried on the sample dataset you provided.
declare #intFlag int
Declare #AnsString nvarchar(2000)
SET #intFlag=1
SET #AnsString=''
While (#intFlag<=200)
Begin
Select #AnsString=#AnsString
+CONVERT(Varchar(20),(SELECT [Element_Name]
FROM TABLE where RowID=#intFlag))
+', '
SET #intFlag=#intFlag+1
if #intFlag=199
break;
END
Print #AnsString
--Select #AnsSTring
If you want this data in separate columns, then you can use dynamic SQL to PIVOT the data in SQL Server 2005+. Your code would be similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(element_name)
from yourtable
group by element_name, id
order by id
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select '+#cols+'
from
(
select Element_name, Value
from yourtable
) p
pivot
(
max(value)
for Element_name in('+#cols+')
) piv'
execute(#query)
See SQL Fiddle with Demo. This gives the result:
| PARMITHA | ANGGUN | CHANDRA | BAGUSOFTERROR | LARAS | JESSICA | AISYAH | SONYA |
----------------------------------------------------------------------------------
| 100 | 200 | 300 | 400 | 500 | 600 | 700 | 20000 |

Categories