Dynamic Pivot table error - sql

I am currently having trouble getting the following to work. I have a table, 'tbl_View', that exists in my database that has the column names of another table held in its rows (users are able to choose specific columns that they wish to display and their list of their choices is kept here).
I then want to create a view that will bring only each users specific column choices from the target table 'AREG' and would like to do so dynamically if possible.
Table Samples - AREG Column names:
ID | Text_1 | Text_2 | Text_3 | Text_4 etc...
tbl_View Data:
ViewID | lblValue | fldName | showYN | sOrder
10 | EANPR | YesNo8 | True | 80
10 | INSP_DATE| Date_1 | True | 81
10 | KG_DATE | Date_2 | True | 82
11 | Life | Num_13 | True | 35
11 | Area | Dimension_1 | True | 69
11 | Length | Dimension_2 | True | 70
11 | EANPR | YesNo8 | True | 80
12 | TRAVEL | Text_1 | True | 1
12 | SPILLLVE | Text_2 | True | 2
12 | SLOPE_PCT| Text_3 | True | 3
14 |Project_Name | Text_1 | True | 1
14 | Project Description | Text_2 | 2
code is:
DECLARE #DynamicPivotQuery AS NVARCHAR(MAX)
DECLARE #ColumnName AS NVARCHAR(MAX)
--Get distinct values of the PIVOT Column
SELECT #ColumnName= ISNULL(#ColumnName + ',','') + QUOTENAME(fldName)
FROM (SELECT DISTINCT fldName FROM qry_ADet WHERE (Viewid=14)) AS fname
--Prepare the PIVOT query using the dynamic
SET #DynamicPivotQuery = N'SELECT ' + #ColumnName + ' FROM #AREG PIVOT ' + #ColumnName + ' AS PVTTable'
--Execute the Dynamic Pivot Query
EXEC sp_executesql #DynamicPivotQuery
I keep getting the error:
Msg 102, Level 15, State 1, Line 1
Incorrect syntax near 'Text_1'.
I have hard coded the view id into the query for this example but will use a parameter in my final query as it will be referenced by an asp.net datasource. But the error points to a different column name if you change the Viewid clause.
Please help.
Thanks

to debug a dynamic sql query, do a PRINT statement before executing it.
You will probably see that the error is from #DynamicPivotQuery
- you should be selecting from tbl_View instead of #AREG right ?
- your pivot statement is wrong. it should be something like
FROM sometable
PIVOT
(
sum( somecol)
for anothercol in ( [value1], [value2] , [value3] )
) p

too long for comment, I think the issue may stem from the column concatenation and it needs something like this:
SET #ColumnName = STUFF((SELECT distinct ',' + QUOTENAME(fldName)
FROM qry_ADet
WHERE (Viewid=14)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')

Related

SQL Sort Numeric Strings After Split

I currently have char values in a table column which are in the format "IS-" and then 1 to 5 numbers, a possible period with either 2 numbers or a letter following the period.
Examples are, IS-1, IS-12, IS-123, IS-123.11, IS-123.a.
I need to split the string so that I grab only the number part, sort the strings ASC, and the bring the strings back together the way they were.
Explanation. I have the following set of values, IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a. As you can see, because IS-1 comes before IS-8 they are sorting out of numerical order.
Any idea where to begin? I was thinking of using CASE, but I'm not really sure how to proceed.
Thanks.
Do string functions in your ORDER BY to remove only the number. Something like this should work:
SELECT col
FROM table
ORDER BY CAST(CASE WHEN ISNUMERIC(SUBSTRING(col,4,20)) = 1
THEN SUBSTRING(col,4,20)
ELSE LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1)
END AS NUMERIC)
This will first remove the IS- and check if the rest of the string is a number. If it is, it will leave the decimal digits, otherwise it will remove the . and the following alpha characters.
This is assuming your intended ordering in the case of numeric decimal places would be:
IS-123.A
IS-123.1
IS-123.2
If you don't care about what's after the decimal/period, then simply:
ORDER BY CAST(LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1) AS NUMERIC)
If I understand you correctly, this might help you:
DECLARE #mockup TABLE(ID INT IDENTITY,YourExample VARCHAR(100));
INSERT INTO #mockup VALUES
('IS-1, IS-12, IS-123, IS-123.11, IS-123.a.')
,('IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a');
WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE(m.YourExample,',','</x><x>') + '</x>' AS XML) AS SplitAtComma
FROM #mockup AS m
)
,NumberExtracted AS
(
SELECT s.ID
,part.value('text()[1]','nvarchar(max)') AS OnePart
,CAST('<y>' + REPLACE(REPLACE(part.value('text()[1]','nvarchar(max)'),'.','-'),'-','</y><y>') + '</y>' AS XML).value('/y[2]/text()[1]','int') AS TheNumber
FROM Splitted AS s
CROSS APPLY s.SplitAtComma.nodes('/x') AS A(part)
)
SELECT *
FROM NumberExtracted
ORDER BY ID,TheNumber;
The first CTE uses a string-split via XML to get all values within the original string (btw: never store comma separated values!).
The second CTE will use the same approach to extract the number, typesafe as INT.
You can use this in an ORDER BY finally.
The result:
+----+-----------+-----------+
| ID | OnePart | TheNumber |
+----+-----------+-----------+
| 1 | IS-1 | 1 |
+----+-----------+-----------+
| 1 | IS-12 | 12 |
+----+-----------+-----------+
| 1 | IS-123 | 123 |
+----+-----------+-----------+
| 1 | IS-123.11 | 123 |
+----+-----------+-----------+
| 1 | IS-123.a. | 123 |
+----+-----------+-----------+
| 2 | IS-870.a | 870 |
+----+-----------+-----------+
| 2 | IS-871.a | 871 |
+----+-----------+-----------+
| 2 | IS-872.a | 872 |
+----+-----------+-----------+
| 2 | IS-1170 | 1170 |
+----+-----------+-----------+
| 2 | IS-1171 | 1171 |
+----+-----------+-----------+
| 2 | IS-1172 | 1172 |
+----+-----------+-----------+
| 2 | IS-1173 | 1173 |
+----+-----------+-----------+
| 2 | IS-1174 | 1174 |
+----+-----------+-----------+
IF OBJECT_ID(N'tempdb..##table1', N'U') IS NOT NULL
DROP TABLE ##table1;
create table ##table1(col1 varchar(20))
declare #query as nvarchar(max)
declare #var1 as varchar(max)='IS-1, IS-12, IS-123, IS-123.11, IS-123.a.,IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a.'
set #var1=replace(#var1,',','''),(''')
set #var1='('''+#var1+''')'
set #var1=replace(#var1,' ','')
set #query='insert into ##table1 values'+#var1
EXEC sp_executesql #query
IF OBJECT_ID(N'tempdb..##table2', N'U') IS NOT NULL
DROP TABLE ##table2;
select * into ##table2 from ##table1 order by cast(replace(replace(replace(col1,'IS-',''),'.a.',''),'.a','') as float)
declare #results varchar(max)
select #results = coalesce(#results + ', ', '') + convert(varchar(12),col1) from ##table2
select #results
DROP TABLE ##table1
DROP TABLE ##table2

Row values as column headings

How can I make the following sql result:
Be rendered in a result set like this
| ID | Chain Size | Length | Hook Type | Shortening Grab |
|-----|-----------------|---------|------------|-----------------|
| 163 | 7mm (1.5 tonne) | 1 metre | C Hook | Yes |
| 226 | 7mm (1.5 tonne) | 1 metre | C Hook | No |
| 247 | 7mm (1.5 tonne) | 1 metre | Latch Hook | No |
I know that the values in columns 2,4,6 and 8 (which I want to be headers) will be the same across all rows (but different depending on the initial query).
I believe the approach for what I want is through the use of PIVOT but really struggling to get the desired result.
Thanks
Assuming your source data actually looks like this:
Static Pivot
Select *
From YourTable
Pivot (max(attributeValue) For [attributeName] in ([Chain Size],[Length],[Hook Type],[Shortening Grab]) ) p
Returns
Dynamic Approach
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName([attributeName]) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select *
From YourTable
Pivot (max(attributeValue) For [attributeName] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Notice, without an item for column sequence, you'll see that they are alphabetical.

SQL Server: UPDATE after pivot (unpivot needed?)

Two days ago I asked how to use pivot in SQL to transpose a table.
With your help I got it right, here the solution: SQL server: Transpose Rows to Columns (n:m relationship)
I'm running SQL Server 2012, with an OBDC-connection, programming in C#.
So now I have:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|---------------|------------|------------|------------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | **30.5.2016** | | 05.06.2015 | 02.11.2015 |
| 2 | Robert | Downey | 4.4.1965 | 2 | 27.7.2014 | 01.01.2016 | 20.01.2015 | |
I get this with following SQL statement:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = ISNULL(#cols + ',', '') + QUOTENAME(Descr)
FROM Faehigkeiten ORDER BY faeID
SET #sql = N'
SELECT mitID, Name, FamName, DOB, abtIDref, ' + #cols + '
FROM(
SELECT mitID, Name, FamName, DOB, abtIDref, [when], descr
FROM Mitarbeiter m
JOIN[Link - List] l ON m.mitID = l.mitIDref
JOIN Faehigkeiten f ON f.faeID = l.feaIDref
) a
PIVOT(MAX([when]) FOR descr IN(' + #cols + ')) p'
EXEC sp_executesql #sql
But when I enter a new date (shown in asterisk above), or change a name of a member, how do I save the data?
I thought about unpivot, but I have no idea how to use it in my case.
My solution would be to loop through all the rows and compare it to existing entries ... but that could take a long time with lots of members.
Can you point me in some direction, wether it is possible to UPDATE this an easy way? I only need UPDATE no INSERT since new rows are forbidden.
Right now I have no class for my members. I load my DataGridView in C# with the following code:
BindingSource bsMember = new BindingSource();
bsMember.DataSource = DBMember.Get();
dgv.DataSource = bsMember;
The DBMember.Get() returns a DataTable made with the above shown SQL code.
Sorry, I have no approach except my loop through each row :/
EDIT: The provided duplicate is hard for me to follow because of the JOIN in my SQL-code. Can i JOIN while updating?
Please consider helping me directly.

SQL Server 2005 Pivot on Unknown Number of Columns

I am working with a set of data that looks something like the following.
StudentName | AssignmentName | Grade
---------------------------------------
StudentA | Assignment 1 | 100
StudentA | Assignment 2 | 80
StudentA | Total | 180
StudentB | Assignment 1 | 100
StudentB | Assignment 2 | 80
StudentB | Assignment 3 | 100
StudentB | Total | 280
The name and number of assignments are dynamic, I need to get results simlilar to the following.
Student | Assignment 1 | Assignment 2 | Assignment 3 | Total
--------------------------------------------------------------------
Student A | 100 | 80 | null | 180
Student B | 100 | 80 | 100 | 280
Now ideally I would like to sort the column based on a "due date" that could be included/associated with each assignment. The total should be at the end if possible (It can be calculated and removed from the query if possible.)
I know how to do it for the 3 assignments using pivot with simply naming the columns, it is trying to do it in a dynamic fashion that I haven't found a GOOD solution for yet. I am trying to do this on SQL Server 2005
EDIT
Ideally I would like to implement this WITHOUT using Dynamic SQL, as that is against the policy. If it isn't possible...then a working example with Dynamic SQL will work.
I know you said no dynamic SQL, but I don't see any way to do it in straight SQL.
If you check out my answers to similar problems at Pivot Table and Concatenate Columns and PIVOT in sql 2005
The dynamic SQL there is not vulnerable to injection, and there is no good reason to prohibit it. Another possibility (if the data is changing very infrequently) is to do code-generation - instead of dynamic SQL, the SQL is generated to a stored procedure on a regular basis.
To PIVOT this data using dynamic sql you can use the following code in SQL Server 2005+:
Create Table:
CREATE TABLE yourtable
([StudentName] varchar(8), [AssignmentName] varchar(12), [Grade] int)
;
INSERT INTO yourtable
([StudentName], [AssignmentName], [Grade])
VALUES
('StudentA', 'Assignment 1', 100),
('StudentA', 'Assignment 2', 80),
('StudentA', 'Total', 180),
('StudentB', 'Assignment 1', 100),
('StudentB', 'Assignment 2', 80),
('StudentB', 'Assignment 3', 100),
('StudentB', 'Total', 280)
;
Dynamic PIVOT:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(AssignmentName)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT StudentName, ' + #cols + ' from
(
select StudentName, AssignmentName, grade
from yourtable
) x
pivot
(
min(grade)
for assignmentname in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result is:
| STUDENTNAME | ASSIGNMENT 1 | ASSIGNMENT 2 | ASSIGNMENT 3 | TOTAL |
--------------------------------------------------------------------
| StudentA | 100 | 80 | (null) | 180 |
| StudentB | 100 | 80 | 100 | 280 |
The only way I've found to do this is to use dynamic SQL and put the column labels into a variable.
you could query information_schema to get the column names and types, then use the result as a subquery when you build your result set. Note you'll likely have to change the login's access a bit.
This is the same as PIVOT in sql 2005
If this data is for consumption in a report you could use a SSRS matrix. It will generate columns dynamically from result set. I've used it many times - it works quite well for dynamic crosstab reports.
Here's a good example w/ dynamic sql.
http://www.simple-talk.com/community/blogs/andras/archive/2007/09/14/37265.aspx
SELECT TrnType
INTO #Temp1
FROM
(
SELECT '[' + CAST(TransactionType AS VARCHAR(4)) + ']' AS TrnType FROM tblPaymentTransactionTypes
) AS tbl1
SELECT * FROM #Temp1
SELECT * FROM
(
SELECT FirstName + ' ' + LastName AS Patient, TransactionType, ISNULL(PostedAmount, 0) AS PostedAmount
FROM tblPaymentTransactions
INNER JOIN emr_PatientDetails ON tblPaymentTransactions.PracticeID = emr_PatientDetails.PracticeId
INNER JOIN tblPaymentTransactionDetails ON emr_PatientDetails.PatientId = tblPaymentTransactionDetails.PatientID
AND tblPaymentTransactions.TransactionID = tblPaymentTransactionDetails.TransactionID
WHERE emr_PatientDetails.PracticeID = 152
) tbl
PIVOT (SUM(PostedAmount) FOR [TransactionType] IN (SELECT * FROM #Temp1)
) AS tbl4
select studentname,[Assign1],[Assign2],[Assign3],[Total]
from
(
select studentname, assignname, grade from student
)s
pivot(sum(Grade) for assignname IN([Assign1],[Assign2],[Assign3],[Total])) as pvt

How to merge multiple rows into one row with filtering rules in SQL Server

I have a table like this:
+---------------+---------------+----------------+---------------------+
| MedicalCardId | DiagnosisType | DiagnosisOrder | Symptom |
+---------------+---------------+----------------+---------------------+
| 1 | Main | 1 | Lung Cancer |
| 1 | Secondary | 1 | High Blood Pressure |
| 1 | Secondary | 2 | Heart Attack |
| 1 | Secondary | 3 | Gastritis |
| 2 | Main | 1 | Diabetes |
| 2 | Secondary | 1 | Kidney Malfunction |
| 3 | Main | 1 | Flu |
+---------------+---------------+----------------+---------------------+
The DiagnosisOrder for each 'Main' DiagnosisType is 1, and for 'Secondary' DiagnosisType of the same MedicalCardId, it restarts to increase from 1.
I would like to merge multiple rows of the same MedicalCardId into a single row, and each Symptom becomes a new column depending on its DiagnosisType and DiagnosisOrder
The query result is expected to be like:
+---------------+-------------+---------------------+-------------------+-------------------+
| MedicalCardId | MainSymptom | SecondarySymptom1 | SecondarySymptom2 | SecondarySymptom3 |
+---------------+-------------+---------------------+-------------------+-------------------+
| 1 | Lung Cancer | High Blood Pressure | Heart Attack | Gastritis |
| 2 | Diabetes | Kidney Malfunction | | |
| 3 | Flu | | | |
+---------------+-------------+---------------------+-------------------+-------------------+
I've tried using PIVOT, but I'm unable to apply it to my practice.
You can try with conditional aggregation -
select MedicalCardId,
max(case when DiagnosisType='Main' then Symptom end) as MainSymptom,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=1 then Symptom end) as SecondarySymptom1,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=2 then Symptom end) as SecondarySymptom2,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=3 then Symptom end) as SecondarySymptom3
from tablename
group by MedicalCardId
I believe you need to create a dynamic pivot table. The reason why you can’t use a normal pivot table query is because you don’t know how many Secondary Symptoms there are and therefore you don’t know how many columns to create. Below is a stored procedure that works. The first step is creating a VARCHAR (#Columns) variable that will be used to store the dynamic column names these will be [Main], [Secondary1], [Secondary2], [Secondary3] so on and so forth (I used a case statement to create the column names per your expected query result). The second step is creating another VARCHAR (#SQL) variable that will contain the pivot table SQL query. In this step you will use string concatenation to put this variable together.
Kris Wenzel has a great tutorial on dynamic pivot tables at essentialsql.com here is the link https://www.essentialsql.com/create-dynamic-pivot-table-sql-server/
Here is the stored procedure.
USE [TestDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[GenerateData]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
--GATHER PIVOT COLUMNS DYNAMICALLY
DECLARE #Columns as VARCHAR(MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','') + QUOTENAME([Diagnosis])
FROM
(SELECT DISTINCT case when [DiagnosisOrder] = 1 and [DiagnosisType] = 'Main' then 'MainSymptom' else 'SecondarySymptom' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis] FROM [TestDB].[dbo].[test] ) AS B
ORDER BY B.[Diagnosis]
--CREATE SQL QUERY FOR PIVOT TABLE
DECLARE #SQL as VARCHAR(MAX)
SET #SQL = 'SELECT MedicalCardId, ' + #Columns + '
FROM
(
select [MedicalCardId]
,[Diagnosis]
,[Sympton]
from
(
SELECT [MedicalCardId]
,case when [DiagnosisOrder] = 1 and [DiagnosisType] = ''Main'' then ''MainSymptom'' else ''SecondarySymptom'' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis]
,[Sympton]
FROM [TestDB].[dbo].[test]
) A
) t
PIVOT(
MAX([Sympton])
FOR [Diagnosis] IN (' + #Columns + ')
) AS pivot_table order by [MedicalCardId]'
--EXECUTE SQL
EXEC(#SQL)
END
GO