MS SQL Server: Reference WHERE statement stored in table? - sql

I'm utilizing a table (called REFERENCE_TABLE) and referencing values stored in a column (STATEMENT) in order to populate the CATEGORY column in a report. There are a variety of different columns in the reporting data (FIELD1, ZONENAME, DISTRICT) and any of these can be used to determine the CATEGORY. Also, schools can be in more than one DISTRICT.
How can I work with this table and basically pull in CATEGORY (like I would using a LEFT JOIN)? The actual table utilized is much larger and is constantly being updated, so pulling the data out manually and including it in the WHERE statement would not be ideal.

You need to use Dynamic SQL for this. The idea of dynamic SQL is you write your query to a string, and then call sp_executesql with your string variable as a parameter, and it runs the query.
Assuming your FIELD1, ZONENAME, and DISTRICT fields are all in the same table (called DATA_TABLE, for instance), you can get what you need with a query like this:
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = STUFF((
SELECT 'UNION SELECT d.*, ''' + r.CATEGORY + ''' AS CATEGORY, '''
+ REPLACE(r.STATEMENTFIELD, '''', '''''') + ''' AS MATCHING_CRITERIA '
+ 'FROM DATA_TABLE d WHERE ' + r.STATEMENTFIELD
FROM REFERENCE_TABLE r
FOR XML PATH('')
), 1, 6, '')
EXEC sp_executesql #sql
The SQL statement basically executes "SELECT * FROM DATA_TABLE WHERE " + each criterion from the REFERENCE_TABLE, and unions all the results together. The STUFF and FOR XML PATH clauses just turn the individual "SELECT *" queries into one big one by sticking a "UNION" between each query.
This gives you an output like this:
FIELD1 ZONENAME DISTRICT CATEGORY MATCHING_CRITERIA
0001 A NY SCHOOL1 FIELD1 IN ('0001','0002','0003')
0001 A NY SCHOOL1 ZONENAME IN ('A')
0001 A NY SCHOOL4 DISTRICT IN ('NY')
0002 A IL SCHOOL1 FIELD1 IN ('0001','0002','0003')
0002 A IL SCHOOL1 ZONENAME IN ('A')
0003 B NY SCHOOL1 FIELD1 IN ('0001','0002','0003')
0003 B NY SCHOOL3 ZONENAME IN ('B')
0003 B NY SCHOOL4 DISTRICT IN ('NY')
etc.
(Here are my sample data DDL & DML statements for testing. When I ask SQL questions, I like to include these. It makes it easier for responders to get started.)
CREATE TABLE DATA_TABLE (FIELD1 VARCHAR(4), ZONENAME CHAR(1), DISTRICT CHAR(2))
INSERT INTO DATA_TABLE VALUES ('0001', 'A', 'NY')
INSERT INTO DATA_TABLE VALUES ('0002', 'A', 'IL')
INSERT INTO DATA_TABLE VALUES ('0003', 'B', 'NY')
INSERT INTO DATA_TABLE VALUES ('0004', 'B', 'IL')
INSERT INTO DATA_TABLE VALUES ('0005', 'C', 'NY')
INSERT INTO DATA_TABLE VALUES ('0006', 'C', 'IL')
INSERT INTO DATA_TABLE VALUES ('0007', 'D', 'NY')
INSERT INTO DATA_TABLE VALUES ('0008', 'D', 'IL')
INSERT INTO DATA_TABLE VALUES ('0009', 'E', 'NY')
CREATE TABLE REFERENCE_TABLE (STATEMENTFIELD NVARCHAR(100), CATEGORY NVARCHAR(10))
INSERT INTO REFERENCE_TABLE VALUES
('FIELD1 IN (''0001'',''0002'',''0003'')', 'SCHOOL1'),
('ZONENAME IN (''A'')', 'SCHOOL1'),
('FIELD1 IN (''0004'',''0005'',''0006'')', 'SCHOOL2'),
('ZONENAME IN (''B'')', 'SCHOOL3'),
('ZONENAME IN (''C'')', 'SCHOOL4'),
('FIELD1 IN (''0007'',''0008'',''0009'')', 'SCHOOL4'),
('DISTRICT IN (''NY'')', 'SCHOOL4')
This gives you a DATA_TABLE like this, which has rows that match multiple criteria in some cases:
FIELD1 ZONENAME DISTRICT
0001 A NY
0002 A IL
0003 B NY
0004 B IL
0005 C NY
0006 C IL
0007 D NY
0008 D IL
0009 E NY
The first row matches mutiple criteria. Should that show a single row or a row for each match? Distinct rows? Not sure. I went with the output with the most information in it. You could, for instance, remove the MATCHING_CRITERIA column and select distinct rows.
Or, the following solution, for example, returns distinct records from DATA_TABLE with comma-delimited matching schools in a column:
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = 'WITH cte AS (' + STUFF((
SELECT 'UNION SELECT d.*, ''' + r.CATEGORY + ''' AS CATEGORY '
+ 'FROM DATA_TABLE d WHERE ' + r.STATEMENTFIELD
FROM REFERENCE_TABLE r
FOR XML PATH('')
), 1, 6, '') + ')
SELECT *,
STUFF((SELECT '','' + CATEGORY FROM cte c
WHERE FIELD1 = d.FIELD1 AND ZONENAME = d.ZONENAME AND DISTRICT = d.DISTRICT
FOR XML PATH('''')), 1, 1, '''') AS COMMA_DELIMITED_CATEGORIES
FROM DATA_TABLE d'
EXEC sp_executesql #sql
Returns:
FIELD1 ZONENAME DISTRICT COMMA_DELIMITED_CATEGORIES
0001 A NY SCHOOL1,SCHOOL4
0002 A IL SCHOOL1
0003 B NY SCHOOL1,SCHOOL3,SCHOOL4
0004 B IL SCHOOL2,SCHOOL3
0005 C NY SCHOOL2,SCHOOL4
0006 C IL SCHOOL2,SCHOOL4
0007 D NY SCHOOL4
0008 D IL SCHOOL4
0009 E NY SCHOOL4

If I'm not mistaken, there is a rule table and a data table
Here is the SQL script that creates those tables and populates with sample data
create table REFERENCE_TABLE (
STATEMENTFIELD nvarchar(1000),
category varchar(20)
)
insert into REFERENCE_TABLE values ('Field1 in (''0001'',''0002'',''0003'')','Shool1'),('ZoneName in (''A'')','Shool1')
insert into REFERENCE_TABLE values ('Field1 in (''0004'',''0005'',''0006'')','Shool2'),('ZoneName in (''B'')','Shool3')
insert into REFERENCE_TABLE values ('ZoneName in (''C'')','Shool4')
create table REFERENCE_DATA(
Field1 varchar(10),
ZoneName varchar(10),
District varchar(10),
Category varchar(20),
)
insert into REFERENCE_DATA (Field1) select '0002'
insert into REFERENCE_DATA (Field1) select '0001'
insert into REFERENCE_DATA (District) select 'NY'
insert into REFERENCE_DATA (Field1) select '0004'
insert into REFERENCE_DATA (Field1) select '0003'
insert into REFERENCE_DATA (ZoneName) select 'A'
Now I build a dynamic SQL update statement as follows
declare #sql nvarchar(max) = '
update REFERENCE_DATA
set category =
case
'
select #sql = #sql + 'when ' + statementfield + ' then ''' + category + '''
'
from REFERENCE_TABLE
set #sql = #sql + '
end'
print #sql
The print command shows the SQL statement that will update all data according to the defined rules in one statement
If it is OK for you, you can execute it by using sp_executesql procedure.
Replace PRINT command line with following
execute sp_executesql #sql

Related

SQL Create new column(s) if row value exists

I have a large data set that looks like the below:
NAME Value
Dan 1
Dan 92
Dan A4
Steve 1
Steve B10
John 4
I'm trying to convert it into a table like:
Name Value1 Value2 Value3
Dan 1 92 B10
Steve 1 B10 Null
John 4 Null Null
So there is an unknown amount of rows and I'd like to create a new column for every value when it exists. Anyone have an idea of how to do this in SQL?
The example you provided would work perfectly using PIVOT, but you need to supply a category to the values to do the pivot.
e.g.
NAME Category Value
Dan Value1 1
Dan Value2 92
Dan Value3 A4
Steve Value1 1
Steve Value3 B10
John Value1 4
Then your results would be like this
Name Value1 Value2 Value3
Dan 1 92 A4
Steve 1 NULL B10
John 4 NULL NULL
Here's Microsoft's documentation:
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver15
To do this dynamically, please read this post. It handles the same situation well.
SQL Server dynamic PIVOT query?
To pivot data you need something to pivot it by.
In this case it can be generated using ROW_NUMBER.
For example:
--
-- sample data
--
create table yourlargetable (
id int identity(1,1) primary key,
name nvarchar(30),
value nvarchar(30)
);
insert into yourlargetable (name, value) values
('jane', 'val1'), ('jane', 'val2'), ('jane', 'val3'),
('john', 'val4'), ('john', 'val5');
--
-- declare a few variables
--
declare #DynSql nvarchar(max);
declare #Cols nvarchar(max);
declare #ColTotal int;
--
-- how many columns are needed
--
select top 1 #ColTotal = count(*)
from yourlargetable
group by name
order by count(*) desc;
--
-- generate a string with column names
--
with RCTE_NUMS as
(
select 1 as n
union all
select n+1
from RCTE_NUMS
where n < #ColTotal
)
select #Cols = concat(#Cols+', ', quotename(concat('Value', n)))
from RCTE_NUMS
order by n;
--
-- create the dynamic sql string
--
set #DynSql = 'select *'+ char(10) +
'from ('+
'select name, value '+ char(10) +
', concat(''Value'', row_number() over (partition by name order by value)) col '+ char(10) +
'from yourlargetable) s'+ char(10) +
'pivot (max(value) '+ char(10) +
'for col in ('+ #Cols +')) p'+ char(10) +
'order by name';
-- select #DynSql;
--
-- run the dynamic sql
--
exec sp_executesql #DynSql;
Returns:
name Value1 Value2 Value3
jane val1 val2 val3
john val4 val5 NULL

Dynamic SELECT statement, generate columns based on present and future values

Currently building a SELECT statement in SQL Server 2008 but would like to make this SELECT statement dynamic, so the columns can be defined based on values in a table. I heard about pivot table and cursors, but seems kind of hard to understand at my current level, here is the code;
DECLARE #date DATE = null
IF #date is null
set # date = GETDATE() as DATE
SELECT
Name,
value1,
value2,
value3,
value4
FROM ref_Table a
FULL OUTER JOIN (
SELECT
PK_ID ID,
sum(case when FK_ContainerType_ID = 1 then 1 else null) Box,
sum(case when FK_ContainerType_ID = 2 then 1 else null) Pallet,
sum(case when FK_ContainerType_ID = 3 then 1 else null) Bag,
sum(case when FK_ContainerType_ID = 4 then 1 else null) Drum
from
Packages
WHERE
#date between PackageStart AND PackageEnd
group by PK_ID ) b on a.Name = b.ID
where
Group = 0
The following works great for me , but PK_Type_ID and the name of the column(PackageNameX,..) are hard coded, I need to be dynamic and it can build itself based on present or futures values in the Package table.
Any help or guidance on the right direction would be greatly appreciated...,
As requested
ref_Table (PK_ID, Name)
1, John
2, Mary
3, Albert
4, Jane
Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
1 , 1, 4, 1JAN2014, 30JAN2014
2 , 2, 3, 1JAN2014, 30JAN2014
3 , 3, 2, 1JAN2014, 30JAN2014
4 , 4, 1, 1JAN2014, 30JAN2014
ContainerType (PK_ID, Type)
1, Box
2, Pallet
3, Bag
4, Drum
and the result should look like this;
Name Box Pallet Bag Drum
---------------------------------------
John 1
Mary 1
Albert 1
Jane 1
The following code like I said works great, the issue is the Container table is going to grow and I need to replicated the same report without hard coding the columns.
What you need to build is called a dynamic pivot. There are plenty of good references on Stack if you search out that term.
Here is a solution to your scenario:
IF OBJECT_ID('tempdb..##ref_Table') IS NOT NULL
DROP TABLE ##ref_Table
IF OBJECT_ID('tempdb..##Packages') IS NOT NULL
DROP TABLE ##Packages
IF OBJECT_ID('tempdb..##ContainerType') IS NOT NULL
DROP TABLE ##ContainerType
SET NOCOUNT ON
CREATE TABLE ##ref_Table (PK_ID INT, NAME NVARCHAR(50))
CREATE TABLE ##Packages (PK_ID INT, FK_ref_Table_ID INT, FK_ContainerType_ID INT, PackageStartDate DATE, PackageEndDate DATE)
CREATE TABLE ##ContainerType (PK_ID INT, [Type] NVARCHAR(50))
INSERT INTO ##ref_Table (PK_ID,NAME)
SELECT 1,'John' UNION
SELECT 2,'Mary' UNION
SELECT 3,'Albert' UNION
SELECT 4,'Jane'
INSERT INTO ##Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
SELECT 1,1,4,'2014-01-01','2014-01-30' UNION
SELECT 2,2,3,'2014-01-01','2014-01-30' UNION
SELECT 3,3,2,'2014-01-01','2014-01-30' UNION
SELECT 4,4,1,'2014-01-01','2014-01-30'
INSERT INTO ##ContainerType (PK_ID, [Type])
SELECT 1,'Box' UNION
SELECT 2,'Pallet' UNION
SELECT 3,'Bag' UNION
SELECT 4,'Drum'
DECLARE #DATE DATE, #PARAMDEF NVARCHAR(MAX), #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
SET #DATE = '2014-01-15'
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(T.[Type])
FROM ##ContainerType T
FOR XML PATH, TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #SQL = 'SELECT [Name], ' + #COLS + '
FROM (SELECT [Name], [Type], 1 AS Value
FROM ##ref_Table R
JOIN ##Packages P ON R.PK_ID = P.FK_ref_Table_ID
JOIN ##ContainerType T ON P.FK_ContainerType_ID = T.PK_ID
WHERE #DATE BETWEEN P.PackageStartDate AND P.PackageEndDate) X
PIVOT (COUNT(Value) FOR [Type] IN (' + #COLS + ')) P
'
PRINT #COLS
PRINT #SQL
SET #PARAMDEF = '#DATE DATE'
EXEC SP_EXECUTESQL #SQL, #PARAMDEF, #DATE=#DATE
Output:
Name Bag Box Drum Pallet
Albert 0 0 0 1
Jane 0 1 0 0
John 0 0 1 0
Mary 1 0 0 0
Static Query:
SELECT [Name],[Box],[Pallet],[Bag],[Drum] FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( [Box],[Pallet],[Bag],[Drum])
) AS PivotTable
) AS Main
ORDER BY RFID
Dynamic Query:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + [Type] + ']'
FROM ContanerType
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT [Name],' + #columnList + ' FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( ' + #columnList + ')
) AS PivotTable
) AS Main
ORDER BY RFID;'
EXEC sp_executesql #pivotsql
Following my tutorial below will help you to understand the PIVOT functionality:
We write sql queries in order to get different result sets like full, partial, calculated, grouped, sorted etc from the database tables. However sometimes we have requirements that we have to rotate our tables. Sounds confusing?
Let's keep it simple and consider the following two screen grabs.
SQL Table:
Expected Results:
Wow, that's look like a lot of work! That is a combination of tricky sql, temporary tables, loops, aggregation......, blah blah blah
Don't worry let's keep it simple, stupid(KISS).
MS SQL Server 2005 and above has a function called PIVOT. It s very simple to use and powerful. With the help of this function we will be able to rotate sql tables and result sets.
Simple steps to make it happen:
Identify all the columns those will be part of the desired result set.
Find the column on which we will apply aggregation(sum,ave,max,min etc)
Identify the column which values will be the column header.
Specify the column values mentioned in step3 with comma separated and surrounded by square brackets.
So, if we now follow above four steps and extract information from the above sales table, it will be as below:
Year, Month, SalesAmount
SalesAmount
Month
[Jan],[Feb] ,[Mar] .... etc
We are nearly there if all the above steps made sense to you so far.
Now we have all the information we need. All we have to do now is to fill the below template with required information.
Template:
Our SQL query should look like below:
SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( [Jan],[Feb] ,[Mar],
[Apr],[May],[Jun] ,[Jul],
[Aug],[Sep] ,[Oct],[Nov] ,[Dec])
) AS PivotTable;
In the above query we have hard coded the column names. Well it's not fun when you have to specify a number of columns.
However, there is a work arround as follows:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + SalesMonth + ']'
FROM Sales
GROUP BY SalesMonth
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( ' + #columnList +' )
) AS PivotTable;'
EXEC sp_executesql #pivotsql
Hopefully this tutorial will be a help to someone somewhere.
Enjoy coding.

Update data based on parameter with operator from another table

I have 2 tables with sample data below:
tblSale
PartCode PartGroup SaleQty
a FM 600
b MM 202
c SM 10
d NM 0
tblCondition
PartGroup Condition
FM >500
MM >=200
SM >=1
NM 0
in SQL Server stored procedure i want to update PartGroup in tblSale by PartGroup in tblCondition based on sum(SaleQty) and compare with Condition.
Any help please.
UPDATE:
Example:
PartCode 'A' is has PartGroup='FM' and SaleQty=500.
if SaleQty=400 then update PartGroup='MM' based on Condition in tblCondition.
UPDATE tblSale
SET tblSale.PartGroup=tblCondition.PartGroup
WHERE SUM(tblSale.Sale) ??? tblCondition.Condition
I don't think you will be able to do this without dynamic code.
For my solution you will need make some changes/notes:
change PartGroup NM condition 0 to =0
make sure that tblCondition table conditions are inserted from biggest (500) to lowest (0)
First what I do is create CASE for every row from tblCondition table.
Then I SUM data to temp table by PartCode (I split PartCode 'c' to 2 rows for testing)
And for the last, create dynamic code, which will update data
/*
CREATE TABLE #tblSale ( PartCode VARCHAR(10), PartGroup VARCHAR(10), SaleQty INT)
INSERT INTO #tblSale SELECT 'a', 'FM', 600
INSERT INTO #tblSale SELECT 'b', 'MM', 202
INSERT INTO #tblSale SELECT 'c', 'SM', 5
INSERT INTO #tblSale SELECT 'd', 'NM', 0
INSERT INTO #tblSale SELECT 'c', 'SM', 5
CREATE TABLE #tblCondition ( PartGroup VARCHAR(10), Condition VARCHAR(10))
INSERT INTO #tblCondition SELECT 'FM', '>500'
INSERT INTO #tblCondition SELECT 'MM', '>=200'
INSERT INTO #tblCondition SELECT 'SM', '>=1'
INSERT INTO #tblCondition SELECT 'NM', '=0'
*/
--CREATE CASES
DECLARE #CaseStr NVARCHAR(1000) = 'CASE '
SELECT #CaseStr = #CaseStr + '
WHEN SaleSUM ' + Condition + ' THEN '''+ PartGroup + ''' '
FROM #tblCondition
SET #CaseStr = #CaseStr + ' END'
-- SUM data by PartCode
SELECT PartCode, SUM(SaleQty) AS SaleSUM
INTO #tblSaleSUM
FROM #tblSale
GROUP BY PartCode
-- Create dynamic code for update
DECLARE #query NVARCHAR(MAX)
SET #query = N'
UPDATE S
SET S.PartGroup = SS.PartGroup
FROM #tblSale AS S
INNER JOIN
(
SELECT PartCode, ' + #CaseStr + ' AS PartGroup
FROM #tblSaleSUM
) AS SS
ON SS.PartCode = S.PartCode
'
EXEC sp_executesql #query
Use this
update ts set partGroup = something
from tblSale ts
inner join tblCondition tc
on tc.PartGroup=ts.PartGroup
inner join (Select PartGroup, sum(SaleQty) as SumSaleQty
from tblSale
group by PartGroup) as sums
on sums.PartGroup = tc.PartGroup
and sums.SumSaleQty >= tc.Condition

Not able to formulate query to combine different row values in single row using pivot table

Below is the actual table
In the table above:
1) FEID is the examination ID which remains same for one exam, like ist semester examination of particular class. So it will remain same for all rows in above table as it consists of data of single exam always.
2) To store result of single student, marks for each subject are entered in each row. So if there are 5 subjects in a class,For each student marks of 5 subjects will be stored in 5 separate rows with marks obtained in each subject
3) Result, Result_code, NCHMCTID remain same in each row of single student. Like in above table, their values remain same in 3 rows.
Due to some reasons I cant remove this redundancy
So my question is, I need to store result of one student in single row, but number of rows related to single student to store each subject marks is not pre determined(number of subjects can change and determined dynamically)
So , if I have 5 subjects marks in 5 rows, I need those in single row.
Below is exactly what I need to convert above table into:
Above there are only 3 subjects, but they can be more than 3 subjects also.
To get subjects list, I use below query for the same and get subjects like:
[vb],[c(p)],VB(p) stored in single variable which I was trying to use in pivot table.
DECLARE #values varchar(max);
SET #values = '';
SELECT #values = #values +'['+ CAST(SubjectName AS varchar(max))+ ']' + ','
FROM tbSubjects where SubID IN(Select SubID from tbFinalMarks Where FEID=2) ;
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
Full procedure is below :
ALTER PROCEDURE [dbo].[prFinalMarksLoadByFEID]
#FEID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #values varchar(max);
SET #values = '';
SELECT #values = #values +'['+ CAST(SubjectName AS varchar(max))+ ']' + ','
FROM tbSubjects where SubID IN(Select SubID from tbFinalMarks Where FEID=2) ;
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
SELECT #values As 'Values'
select Student_Name,#values,Result,NCHMCTID,Examination_Name from
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
(SELECT dbo.tbStudent.Name AS Student_Name, dbo.tbSubjects.SubjectName AS Subject_Name, dbo.tbFinalMarks.MarksObtained AS Marks_Obtained,
dbo.tbFinalMarks.Result, dbo.tbFinalMarks.ResultCode AS Result_Code, ISNULL(dbo.tbStudent.NCHMCTID, 'Not Available') AS NCHMCTID,
dbo.tbFinalExam.ExaminationName as Examination_Name
FROM dbo.tbFinalMarks INNER JOIN
dbo.tbSubjects ON dbo.tbFinalMarks.SubID = dbo.tbSubjects.SubID INNER JOIN
dbo.tbStudent ON dbo.tbFinalMarks.StdID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbFinalExam ON dbo.tbFinalMarks.FEID = dbo.tbFinalExam.FEID
Where FEID =#FEID
) ps
PIVOT
(
MAX(Marks_Obtained) For Subject_Name IN ([VB],[VB(P)],[C(P)])
) AS pvt
But I am not able to do it. Please help
Below part give me actual table which i need to manipulate for result table
(SELECT dbo.tbStudent.Name AS Student_Name, dbo.tbSubjects.SubjectName AS Subject_Name, dbo.tbFinalMarks.MarksObtained AS Marks_Obtained,
dbo.tbFinalMarks.Result, dbo.tbFinalMarks.ResultCode AS Result_Code, ISNULL(dbo.tbStudent.NCHMCTID, 'Not Available') AS NCHMCTID,
dbo.tbFinalExam.ExaminationName as Examination_Name
FROM dbo.tbFinalMarks INNER JOIN
dbo.tbSubjects ON dbo.tbFinalMarks.SubID = dbo.tbSubjects.SubID INNER JOIN
dbo.tbStudent ON dbo.tbFinalMarks.StdID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbFinalExam ON dbo.tbFinalMarks.FEID = dbo.tbFinalExam.FEID
Where FEID =#FEID
)
I used [vb],[vb(p)],[C(P)] instead of #values ( it contains subjects list) as using # values in below part gives me error:
PIVOT
(
MAX(Marks_Obtained) For Subject_Name IN ([VB],[VB(P)],[C(P)])
) AS pvt
Below is the data:
FEID Student_Name Subject_Name Marks_Obtained Result Result_Code NCID Exam_Name
2 roof VB 100 First 1234 ist semester
2 roof VB(P) 100 First 1234 ist semester
2 roof C(P) 100 First 1234 ist semester
2 Amir VB 100 First nbb 8 ist semester
2 Amir VB(P) 100 First nbb 8 ist semester
2 Amir C(P) 100 First nbb 8 ist semester
Here's your query:
create table #t (FEID int, Student_Name char(4), Subject_Name char(5), Marks_Obtained int,
Result char(5), Result_Code int, NCID char(5), Exam_Name char(12))
go
insert #t values
( 2, 'roof', 'VB ', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'roof', 'VB(P)', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'roof', 'C(P) ', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'Amir', 'VB ', 100, 'First', NULL, 'nbb 8', 'ist semester'),
( 2, 'Amir', 'VB(P)', 100, 'First', NULL, 'nbb 8', 'ist semester'),
( 2, 'Amir', 'C(P) ', 100, 'First', NULL, 'nbb 8', 'ist semester')
go
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(subject_name)
FROM #t -- your table here
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #collist
declare #q nvarchar(max)
set #q = '
select *
from (
select
Student_Name, subject_name, Marks_Obtained, Exam_Name, Result, NCID, Result_Code
from (
select *
from #t -- your table here
) as x
) as source
pivot (
max(Marks_Obtained)
for subject_name in (' + #collist + ')
) as pvt
'
exec (#q)

Pivoting rows into columns in SQL Server

I have a set of data that looks like this:
Before
FirstName LastName Field1 Field2 Field3 ... Field27
--------- -------- ------ ------ ------ -------
Mark Smith A B C D
John Baptist X T Y G
Tom Dumm R B B U
However, I'd like the data to look like this:
After
FirstName LastName Field Value
--------- -------- ----- -----
Mark Smith 1 A
Mark Smith 2 B
Mark Smith 3 C
Mark Smith 4 D
John Baptist 1 X
John Baptist 2 T
John Baptist 3 Y
John Baptist 4 G
Tom Dumm 1 R
Tom Dumm 2 B
Tom Dumm 3 B
Tom Dumm 4 U
I have looked at the PIVOT function. It may work. I am not too sure. I couldn't make sense of how to use it. But, I am not sure that the pivot could place a '4' in the 'Field' column. From my understanding, the PIVOT function would simply transpose the values of Field1...Field27 into the 'Value' column.
I have also considered iterating over the table with a Cursor and then looping over the field columns, and then INSERTing into another table the 'Field's and 'Value's. However, I know this will impact performance since it's a serial-based operation.
Any help would be greatly appreciated! As you can tell, I'm quite new to T-SQL (or SQL in general) and SQL Server.
You can perform with an UNPIVOT. There are two ways to do this:
1) In a Static Unpivot you would hard-code your Field columns in your query.
select firstname
, lastname
, replace(field, 'field', '') as field
, value
from test
unpivot
(
value
for field in (field1, field2, field3, field27)
) u
See a SQL Fiddle for a working demo.
2) Or you could use a Dynamic Unpivot which will get the list of items to PIVOT when you run the SQL. The Dynamic is great if you have a large amount of fields that you will be unpivoting.
create table mytest
(
firstname varchar(5),
lastname varchar(10),
field1 varchar(1),
field2 varchar(1),
field3 varchar(1),
field27 varchar(1)
)
insert into mytest values('Mark', 'Smith', 'A', 'B', 'C', 'D')
insert into mytest values('John', 'Baptist', 'X', 'T', 'Y', 'G')
insert into mytest values('Tom', 'Dumm', 'R', 'B', 'B', 'U')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('mytest') and
C.name like 'Field%'
for xml path('')), 1, 1, '')
set #query = 'SELECT firstname, lastname, replace(field, ''field'', '''') as field, value
from mytest
unpivot
(
value
for field in (' + #cols + ')
) p '
execute(#query)
drop table mytest
Both will produce the same results.
If you want to do it query than quick and dirty way will be to create Union
Select FirstName,LastName,1,Field1
from table
UNION ALL
Select FirstName,LastName,2,Field2
from table
.
.
And similar for all field cols
Rather than using pivot, use unpivot like this:
select firstname, lastname, substring(field,6,2) as field, value
from <yourtablename>
unpivot(value for field in (field1,field2,field3,field4,field5,field6,field7,field8,field9,field10,field11,field12,field13,field14,field15,field16,field17,field18,field19,field20,field21,field22,field23,field24,field25,field26,field27,field1,field2,field3,field4,field5,field6,field7,field8,field9,field10,field11,field12,field13,field14,field15,field16,field17,field18,field19,field20,field21,field22,field23,field24,field25,field26,field27)) as unpvt;