Dynamically sum table columns in SQL Server - sql

I have the following data in a table with columns Col1,Col2,Col3,Col4, Col5......... Col153
The sample data in the first four columns in the table:
CREATE TABLE dbo.MyTable (LocaID VARCHAR(10),Col1 INT, Col2 INT,Col3 INT,Col4 INT)
INSERT INTO dbo.MyTable values
('LV',2,6,4,7),('CH',4,8,3,1),('LV',1,3,9,3),('MC',7,0,5,4),
('LV',4,5,2,4),('MC',7,1,4,9),('MC',5,1,8,1),('CH',7,3,4,0),
('MC',2,5,7,3);
There are 153 columns that I need to sum individually. I used the query below to get the list of columns:
-- This is to get the columns names
SELECT
COLUMN_NAME AS Col_Names
INTO #Column
From INFORMATION_SCHEMA.COLUMNS
Where TABLE_NAME Like 'MyTable'
I tried to use the query below to dynamically sum them:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(Col_Names)
FROM #Column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query= 'SELECT
SUM('+#cols+')
FROM dbo.MyTable GROUP BY [LocaID]'
PRINT #query
When I print the result to check if the query is correct I got this:
SELECT SUM([Col1],[Col2],[Col3],[Col4],[LocaID])
FROM dbo.MyTable GROUP BY [LocaID]
Which is clearly not correct. What I need is:
SELECT
[LocaID],
SUM([Col1]) AS [Col1],
SUM([Col2]) AS [Col2],
SUM([Col3]) AS [Col3],
SUM([Col4]) AS [Col4]
FROM dbo.MyTable
GROUP BY [LocaID]
For all the 153 columns. There is only one LocaID in the table.

If you want to SUM individual column, you need to change your STUFF function. You need to apply SUM function for all the columns, like below.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',SUM(' + QUOTENAME(Col_Names) + ')'
FROM #Column
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'')
SET #query= 'SELECT [LocaID], '+#cols+'
FROM dbo.MyTable GROUP BY [LocaID]'
PRINT #query

Thanks you so much #Vad Soft, I adjusted you query to give me what i want
DECLARE #cols AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SET #cols = STUFF((
----------------------------------------------------------------------------------
SELECT DISTINCT CONCAT(',SUM(' + QUOTENAME(a.Col_Names) + ') AS ',' ',b.RN)
FROM #Column a
LEFT JOIN
(
SELECT
Col_Names AS RN
from #Column
where Col_Names <> 'LocaID' ) b ON b.RN=a.Col_Names
-------------------------------------------------------------------------------------
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'')
SET #cols2 =REVERSE(RIGHT(REVERSE(#cols),LEN(REVERSE(#cols))- CHARINDEX(',',REVERSE(#cols))))
--PRINT #cols2
SET #query= 'SELECT [LocaID],
'+#cols2+'
FROM dbo.MyTable GROUP BY [LocaID]'
--PRINT #cols
EXEC (#query)
---------------------------
DROP TABLE #Column
Output
print #query
SELECT [LocaID],
SUM([Col1]) AS Col1,
SUM([Col2]) AS Col2,
SUM([Col3]) AS Col3,
SUM([Col4]) AS Col4
FROM dbo.MyTable GROUP BY [LocaID]

Related

Declare a Table Variable based on dynamic pivot columns statement

I want to declare a table variable and fill it from the pivot with dynamic column to perform join statement.
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX)
SELECT #cols =
STUFF((SELECT DISTINCT ',' + QUOTENAME(ColName)
FROM [sbs].[ProposalAmounts]
GROUP BY ColName, ProposalID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #query = N'SELECT ProposalID, ' + #cols + N' from
(select ProposalID, Amount, ColName from [sbs].[ProposalAmounts]) x
PIVOT
(MAX(Amount)for ColName in (' + #cols + N')) p '
EXEC sp_executesql #query;
This is what I've done so far and I'm confused as to how to declare a table variable that has dynamic columns in it.
This is the result of the query above:
And this is the result of the table I want to perform join statement:
Like Jeroen Mostert commented, you need to make everything dynamic.
I would suggest to put the join inside the dynamic query. Instead of a table variable, I use a common table expression.
DECLARE #cols AS NVARCHAR(MAX), #query AS NVARCHAR(MAX);
SELECT #cols =
STUFF((SELECT DISTINCT N', ' + QUOTENAME([ColName])
FROM [_tmp_].[ProposalAmounts]
GROUP BY [ColName], [ProposalID]
FOR XML PATH(N''), TYPE).value(N'.', N'NVARCHAR(MAX)'), 1, 2, N'')
SET #query = N'
WITH [CTE_Pivoted_ProposalAmounts] AS
(
SELECT [ProposalID], ' + #cols + N'
FROM
(SELECT [ProposalID], [Amount], [ColName] FROM [sbs].[ProposalAmounts]) x
PIVOT (MAX([Amount]) FOR [ColName] IN (' + #cols + N')) p
)
SELECT *
FROM
[sbs].[OtherTable] ot
INNER JOIN [CTE_Pivoted_ProposalAmounts] ppa ON ppa.[ProposalID] = ot.[ProposalID];
';
EXEC sp_executesql #query;
You need to replace [sbs].[OtherTable] with the actual name of the table you want to join with. And you might also tweak the join criteria and the fields in the SELECT clause. This code here is just a simple example. I assume you will manage to fix the query yourself to make it behave as you expect.

Stored procedure with string as parameters not working -SQL

In my database 'Student_name' is set as varchar. And the stored procedure is:
ALTER PROCEDURE [dbo].[SP_STUDENT]
#STUDENT_NAME NVARCHAR(MAX),
AS
DECLARE
#columns NVARCHAR(MAX) = '',
#columnsname NVARCHAR(MAX) = '',
#columnsnameA NVARCHAR(MAX) = '',
#columnsB NVARCHAR(MAX) = '',
#columnsnameB NVARCHAR(MAX) = '',
#sql NVARCHAR(MAX) = '';
-- select the question num
SET #columns = STUFF((SELECT distinct ',' + QUOTENAME(cast(Question_no as varchar))
FROM submission1_details WHERE Submission1_id=100
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #columnsname = STUFF((SELECT distinct ',' + QUOTENAME(cast(Question_no as varchar)) + ' sub1_Q'+ cast(Question_no as varchar)
FROM submission1_details WHERE Submission1_id=100
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #columnsnameA = STUFF((SELECT distinct ','+' sub1_Q'+ cast(Question_no as varchar)
FROM submission1_details WHERE Submission1_id=100
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #columnsB = STUFF((SELECT distinct ',' + QUOTENAME(cast(Question_no as varchar))
FROM submission2_details WHERE Submission2_id=500
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #columnsnameB = STUFF((SELECT distinct ',' + QUOTENAME(cast(Question_no as varchar)) + ' sub2_Q'+ cast(Question_no as varchar)
FROM submission2_details WHERE Submission2_id=500
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
print #columns
SET #sql =N'
select Submission_id,DRA_submission_id,SubmittedOn,Driver,VehicleType,VehicleNo,Interval, '+#columnsnameB +','+ #columnsnameA+ ' from (
select Submission_id,DRA_submission_id,SubmittedOn,Driver ,VehicleType,'+#columnsname+',Question_no, Answer,VehicleNo,Interval from (
select distinct t1.Submission_id,t1.DRA_submission_id,t1.Submitted_time AS SubmittedOn,t1.Driver_id as Driver, dvc_vehicle_types.vehicle_type_name AS VehicleType,dvc_vehicles.Vehicle_RegNo AS VehicleNo,
dvc_checklist_intervals.Interval_description AS Interval,t2.Question_no t,t2.Answer A,t3.Question_no,t3.Answer from dvc_submission_header t1
inner JOIN dvc_submission_details t2 ON t1.Submission_id= t2.Submission_id
inner JOIN dvc_DRAsubmission_details t3 ON t1.DRA_submission_id=
t3.Submission_id INNER JOIN dvc_vehicles ON
t1.Vehicle_id=dvc_vehicles.Vehicle_id INNER JOIN dvc_vehicle_types ON
dvc_vehicles.Vehicle_type_id=dvc_vehicle_types.Vehicle_type_id
INNER JOIN dvc_checklist_intervals ON t1.Interval_id =
dvc_checklist_intervals.Interval_id
WHERE t1.Student_name= '+ #STUDENT_NAME +' ';
SET #sql +=N'
) as a
PIVOT(
MAX(A)
for t IN ('+ #columns +')
) AS pivot_table
) as b
PIVOT(
MAX(Answer)
for Question_no IN ('+ #columnsB +')
) AS pivot_table1;';
print #sql
-- execute the dynamic SQL
EXECUTE sp_executesql #sql;
And I have executed the stored procedure as below:
EXEC [SP_STUDENT] #STUDENT_NAME='Yuvan'
But it shows an error:
Conversion failed when converting the nvarchar value 'Yuvan' to data type int.
Please help to correct.
Do not concatenate string to build a statement. Use sp_executesql correctly and use parameters (if you really want to use a dynamic statement):
ALTER PROCEDURE [dbo].[SP_STUDENT]
#STUDENT_id NVARCHAR(MAX),
#STUDENT_name NVARCHAR(MAX),
#EXAM_date NVARCHAR(MAX)
AS
BEGIN
DECLARE
#sql nvarchar(max),
#err int
SET #sql =
N'SELECT * FROM student_details ' +
N'WHERE student_id = #STUDENT_id AND student_name = #STUDENT_name AND exam_date = #EXAM_date';
PRINT #sql
EXECUTE #err = sp_executesql
#sql,
N'#STUDENT_id NVARCHAR(MAX), #STUDENT_name NVARCHAR(MAX), #EXAM_date NVARCHAR(MAX)',
#STUDENT_id, #STUDENT_name, #EXAM_date
RETURN (#err)
END
Notes:
Define parameters data types carefully. Using nvarchar(max) is not needed. Use the appropriate data type, based on the columns data types.
Pass datetime values using an unambiguous datetime format
If possibe, use simple SELECT statement:
ALTER PROCEDURE [dbo].[SP_STUDENT]
#STUDENT_id NVARCHAR(MAX),
#STUDENT_name NVARCHAR(MAX),
#EXAM_date NVARCHAR(MAX)
AS
BEGIN
SELECT *
FROM student_details
WHERE
student_id = #STUDENT_id AND
student_name = #STUDENT_name AND
exam_date = #EXAM_date
END

Summing multiple dynamically declared variables (fast)

I'm developing a program where I have multiple columns which I need to show the sum of to anEnd-user. These columns are assigned in the table ColumnTest in the column ColumnNames and their names can be changed by the user. Therefore I need to look up all the column names in ColumnTest\ColumnNames and afterwards sum all values regarding these columnnames from the outputtable.
I have previously used this script where I get all the columnnames in #cols like this [col1].[col2].[col3] and so on, but when I try to run the query I'm not able to sum these columns using '+ #cols + '. When I run this I get the following error: The SUM function requires 1 argument(s). Is there a viable option to do this procedure, without compromising the loading-time substantially?
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.ColumnNames)
FROM ColumnTest c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set #query =
'
SELECT
ID
,SUM('+ #cols + ')
FROM Output_table
GROUP BY
ID
'
execute(#query);
Try this,
DECLARE
#cols AS NVARCHAR(MAX)='',
#query AS NVARCHAR(MAX);
SELECT #cols += 'SUM('+ColumnNames+') as ['+ColumnNames+'],'
from
(
SELECT distinct ColumnNames FROM ColumnTest
)A
SELECT #cols=LEFT(#cols,LEN(#cols)-1)
set #query =
'
SELECT ID,'+ #cols + '
FROM Output_table
GROUP BY ID
'
execute(#query);
or if you want addition of all column values you can use below query
DECLARE #cols AS NVARCHAR(MAX)='',
#query AS NVARCHAR(MAX);
SELECT #cols += ''+ColumnNames+'+'
FROM
(
SELECT DISTINCT ColumnNames FROM ColumnTest
)A
SELECT #cols=LEFT(#cols,LEN(#cols)-1)
set #query =
'
SELECT ID,SUM('+ #cols + ')
FROM Output_table
GROUP BY ID
'
execute(#query);
You can not use comma separated like ','
You can use '+'
Try this
DECLARE
#cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct '+' + QUOTENAME(c.ColumnNames)
FROM ColumnTest c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') ,1,1,'')
set #query =
'
SELECT
ID
,SUM('+ #cols + ')
FROM Output_table
GROUP BY
ID
'
execute(#query);

Combine two pivot tables SQL Server

I am trying to cross join two pivot tables.
--First Pivot table query gets columns of drug_names and produces the row value
--of the drug_id.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME(drug)
from _app_drugs
group by drug, drug_id,order_number
order by order_number
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select drug_id, drug
from _app_drugs
) x
pivot
(
max(drug_id)
for drug in (' + #cols + N')
) p'
exec sp_executesql #query;
--The second pivot table simply gets the signature_labels and displays the label name in the column and displays the label_id as the row value.
DECLARE #cols AS NVARCHAR(MAX),#scols as NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME(signature_label)
from _app_signature_labels
WHERE _app_signature_labels.isactive=1
group by signature_label_id, signature_label,ordernumber
order by ordernumber
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' from
(
select signature_label_id,signature_label
from _app_signature_labels
) x
pivot
(
max(signature_label_id)
for signature_label in (' + #cols + N')
) p'
exec sp_executesql #query;
Now I just need to know how to combine these two pivot tables as one table... they don't have a common field and don't need one.
Can anybody help me with this one?
Thank you
You can execute two separate dynamic SQL statements into two global temp tables and then combine the result, like this:
IF object_id('tempdb..##tmpResult1') IS NOT NULL
BEGIN
DROP TABLE ##tmpResult1
END
IF object_id('tempdb..##tmpResult2') IS NOT NULL
BEGIN
DROP TABLE ##tmpResult2
END
DECLARE #cols AS NVARCHAR(MAX),
#cols2 AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#query2 AS NVARCHAR(MAX)
Select #cols = STUFF((SELECT ',' + QUOTENAME(drug)
from _app_drugs
group by drug, drug_id,order_number
order by order_number
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT ' + #cols + N' INTO ##tmpResult1 from
(
select drug_id, drug
from _app_drugs
) x
pivot
(
max(drug_id)
for drug in (' + #cols + N')
) p'
exec sp_executesql #query;
--The second pivot table simply gets the signature_labels and displays the label name in the column and displays the label_id as the row value.
Select #cols2 = STUFF((SELECT ',' + QUOTENAME(signature_label)
from _app_signature_labels
WHERE _app_signature_labels.isactive=1
group by signature_label_id, signature_label,ordernumber
order by ordernumber
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query2 = N'SELECT ' + #cols2 + N' INTO ##tmpResult2 from
(
select signature_label_id,signature_label
from _app_signature_labels
) x
pivot
(
max(signature_label_id)
for signature_label in (' + #cols2 + N')
) p'
exec sp_executesql #query2;
SELECT * FROM ##tmpResult1 t1
INNER JOIN ##tmpResult2 t2 ON t1.ordernumber = t2.ordernumber
This is also a way to address this error when doing a large PIVOT query: Internal error: An expression services limit has been reached. Please look for potentially complex expressions in your query, and try to simplify them.

Inserting dynamic pivot result into an existing table without knowing table definition

I have a dynamic pivot table that produces an unknown number of columns until runtime. I am trying to clear the data out of an existing table and insert the results of my query into it. The problem that I am having is that I do not know what the table definition will be ahead of time because it is dynamic.
Here is the error I am getting:
Msg 213, Level 16, State 7, Line 1
Column name or number of supplied values does not match table definition.
The pivoted column is a date column which could have 1 date or 1000+ dates.
How can I create a table definition that will match the data that I am trying to insert?
Here is my query:
DECLARE #colsPivot AS NVARCHAR(MAX),
#colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ',' + QUOTENAME(rce.EcoDate)
from PhdRpt.RptCaseEco_542 AS rce
group by rce.EcoDate
order by rce.EcoDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #colsUnpivot = stuff((SELECT ','+quotename(C.name)
FROM sys.columns AS C
WHERE C.object_id = object_id('PhdRpt.RptCaseEco_542') AND
C.name in ('NDCash', 'DCash')--LIKE '%Cash' or C.Name like 'NetGas'
FOR xml path('')), 1, 1, '')
set #query
= 'select *
--into ##hello
from
(
SELECT
ReportRunCaseId,
col,
EcoDate,
val
FROM PhdRpt.RptCaseEco_542
unpivot
(
val
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
max(val)
for EcoDate in ('+ #colspivot +')
) p'
truncate table dbo.Table1
insert into dbo.Table1
exec(#query)
I would DROP original table and just create new one on the fly using SELECT INTO clause.
by using SELECT INTO you do not need to create table before hand or worry what type and how many columns it will have. SQL Server will just create it for you.
Note: this only works when table does not exists.
Full documentation on INTO Clause http://technet.microsoft.com/en-us/library/ms188029.aspx
as another alternative you can always SELECT INTO #temp table that you can drop or use it to model your other table but I think this is extra work that you do not want to do.
You can do something like this:
DROP TABLE dbo.Table1
DECLARE #colsPivot AS NVARCHAR(MAX),
#colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ',' + QUOTENAME(rce.EcoDate)
from PhdRpt.RptCaseEco_542 AS rce
group by rce.EcoDate
order by rce.EcoDate
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SELECT #colsUnpivot = stuff((SELECT ','+quotename(C.name)
FROM sys.columns AS C
WHERE C.object_id = object_id('PhdRpt.RptCaseEco_542') AND
C.name in ('NDCash', 'DCash')--LIKE '%Cash' or C.Name like 'NetGas'
FOR xml path('')), 1, 1, '')
set #query
= 'select *
into dbo.Table1
from
(
SELECT
ReportRunCaseId,
col,
EcoDate,
val
FROM PhdRpt.RptCaseEco_542
unpivot
(
val
for col in ('+ #colsunpivot +')
) u
) x1
pivot
(
max(val)
for EcoDate in ('+ #colspivot +')
) p'
exec(#query)