SQL Server 2008 concatenate rows to column - sql

I am using SQL Server 2008, I have a dataset that look like:
FormKey Value Category
------- ----- ------
123456 Gloves PPE
123456 Hat PPE
123456 Scalf PPE
123456 Boots PPE
987654 Glasses PPE
987654 Harness PPE
987654 Overalls PPE
I am trying to concatenate the Values and group by FormKey, so that I would end up with:
Formkey Value Category
------- ----- -------
123456 Gloves, Hat, Scalf, Boots PPE
987654 Glasses, Harness, Overalls PPE
However, I am getting a concat of ALL of the Values for each of the Formkeys.
The code I have been using is:
SELECT frd.formresultkey AS frk
,STUFF((
SELECT ', ' + fra.value
FROM [FormResultAnswers] FRA
INNER JOIN [FormResultDetails] FRD ON FRA.[DetailKey] = FRD.[DetailKey]
INNER JOIN [FormResults] FR ON FRD.[FormResultKey] = FR.[FormResultKey]
WHERE FR.FormReference = 'PPE'
AND frd.FormElementReference = 'PPE_List'
FOR XML path('')
), 1, 1, '') AS Concatted
FROM [FormResultAnswers] FRA
INNER JOIN [FormResultDetails] FRD ON FRA.[DetailKey] = FRD.[DetailKey]
INNER JOIN [FormResults] FR ON FRD.[FormResultKey] = FR.[FormResultKey]
After this I need to update a table with the concatenated value where the Formkeys match. Can anyone help please?

Some modification to your query will fetch the result. Try this.
SELECT FormKey,
Stuff((SELECT ',' + Value
FROM Result b
WHERE a.FormKey = b.FormKey
AND a.Category = b.Category
FOR xml path('')), 1, 1, '') value,
Category
FROM Result a
GROUP BY FormKey,
Category

You can create two function to concatenate the values, like this:
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'GroupValue'))
DROP FUNCTION GroupValue;
GO
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'GroupCategory'))
DROP FUNCTION GroupCategory;
GO
CREATE FUNCTION dbo.GroupValue (#FormKey INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #VAL VARCHAR(MAX) = '';
SELECT #VAL = #VAL + Value + ', '
FROM (SELECT DISTINCT Value
FROM YourTable
WHERE FormKey = #FormKey) AS TT
IF (LEN(#VAL) > 0)
SET #VAL = LEFT(#VAL, LEN(#VAL) - 1)
RETURN #VAL
END
GO
CREATE FUNCTION dbo.GroupCategory (#Formkey INT)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #VAL VARCHAR(MAX) = '';
SELECT #VAL = #VAL + Category + ', '
FROM (SELECT DISTINCT Category
FROM YourTable
WHERE FormKey = #FormKey) AS TT
IF (LEN(#VAL) > 0)
SET #VAL = LEFT(#VAL, LEN(#VAL) - 1)
RETURN #VAL
END
GO
And here's the query:
SELECT FormKey
,dbo.GroupValue(FormKey) AS Value
,dbo.GroupCategory(FormKey) AS Category
FROM YourTable
GROUP BY FormKey;

Related

Mask values in a SQL Query string for SQL Server

I am a SQL Server DBA. I would like to write a procedure which I can provide to rest of my team where they can view the text for currently running queries on the server (Similar to how we view in sp_who2) but with all the values masked.
Examples:
Query text
Query text after Masking
Select * from sometable where rating = '4'
Select * from sometable where rating = '****'
Select name, id from sometable where id = '3233'
Select name, id from sometable where id = '****'
UPDATE Customers SET ContactName = 'Alfred Schmidt' WHERE CustomerID = 1;
UPDATE Customers SET ContactName = '****' WHERE CustomerID = ****;
INSERT INTO Customers (CustomerName, ContactName) VALUES ('Cardinal', 'Tom B. Erichsen');
INSERT INTO Customers (CustomerName, ContactName) VALUES ('*****', '****');
If I understand correctly your issue.
You can use this query:
select
r.session_id,
r.status,
r.command,
r.cpu_time,
r.total_elapsed_time,
t.text
from sys.dm_exec_requests as r
cross apply sys.dm_exec_sql_text(r.sql_handle) as t
e.g.
I run it on my SQL server right now:
(#P1 nvarchar(5),#P2 bigint,#P3 int,#P4 numeric(28, 12),#P5 nvarchar(5),#P6 datetime,#P7 datetime)
SELECT SUM(A.SETTLEAMOUNTCUR) FROM CUSTSETTLEMENT A,CUSTTRANS B WHERE ((A.DATAAREAID=#P1) AND (((A.TRANSRECID=#P2) AND (A.CANBEREVERSED=#P3)) AND (A.SETTLEAMOUNTCUR<>#P4))) AND ((B.DATAAREAID=#P5) AND (((B.RECID=A.OFFSETRECID) AND (B.TRANSDATE>=#P6)) AND (B.TRANSDATE<=#P7)))
All variables are hidden.
You could try some XML-trickery to handle the strings.
First replace all single quotes with an empty tag <X/> to get a XML that looks like this.
INSERT INTO Customers (CustomerName, ContactName)
VALUES (<X />Cardinal<X />, <X />Tom B. Erichsen<X />);
Then you shred the xml to get the text nodes and the node numbers where mod 2 is 0 is the ones you want to mask.
After that you can rebuild your query string using the mask values.
I have not found a way to deal with numbers other then removing all numbers from the query using Translate or nested replace and that will of course also remove numbers from table names and column names as well.
You could try something like this.
declare #S nvarchar(max);
declare #X xml;
set #S = N'UPDATE Customers SET ContactName = ''Alfred Schmidt'' WHERE CustomerID = 1;';
set #X = replace(#S, '''', '<X/>');
with C as
(
select T.X.value('.', 'nvarchar(max)') as V,
row_number() over(order by T.X) as RN
from #X.nodes('text()') as T(X)
)
select #S = (
select case when C.RN % 2 = 0 then '''*****''' else C.V end
from C
order by C.RN
for xml path(''), type
).value('text()[1]', 'nvarchar(max)');
set #S = translate(#S, '0123456789', '**********')
print #S;
Result:
UPDATE Customers SET ContactName = '*****' WHERE CustomerID = *;
Note: Just realized that this solution does not handle the cases where the string values contains single quotes but I think this is something that possibly can inspire more robust solution so I will leave it here.
sys.sp_get_query_template
fiddle
declare #t nvarchar(max), #p nvarchar(max);
declare #q nvarchar(max) = 'UPDATE Customers SET ContactName = N''Alfred Schmidt'' WHERE CustomerID = 1 AND Rate = 0.75 AND Rver = 0x0102 AND DateCreated = dateadd(day, -10, ''202z0818'')';
exec sys.sp_get_query_template #querytext = #q, #templatetext = #t OUTPUT, #parameters = #p OUTPUT;
select p,
case when tp like '%int' then cast('****' as nvarchar(40))
when tp like 'decimal(%' or tp like 'numeric(%' then '**.**'
when tp like '%binary(%' then '0x****'
when tp like 'n%char%' then 'N''****'''
else '''****'''
end as rv
into #t
from
(
select *, '#'+left(s.value, charindex(' ', s.value+' ')-1) as p, stuff(s.value, 1, charindex(' ', s.value), '') as tp
from string_split(replace(#p, ',#', '#'), '#') as s
where s.value <> ''
) as ss;
update #t
set #t = replace(#t, p, rv);
select #q union all select #t;

How to return multiple values as one field in SELECT statement

I have an query like so:
SET #tableHTML =
N'<table border="1">' +
N'<tr><th>Data i godzina</th><th>Pozycja</th><th>Lokalizacja</th>' +
N'<th>Jednostka</th><th>Ilość</th><th>Zamawiający</th>' +
N'<th>Dostępne na</th>' +
CAST ( ( SELECT
td = CONVERT(smalldatetime, ars.scan_date), '',
td = ars.item, '',
td = ars.loc, '',
td = ars.order_unit, '',
td = ars.qty, '',
td = ars.requesting_user, '',
--here is the problem
td = COALESCE((SELECT DISTINCT itl.loc FROM itemloc JOIN ANP_ResupplyStock AS ars ON itl.item = ars.item),','), ''
--end problem
FROM
dbo.ANP_ResupplyStock as ars
inner join itemloc as itl on ars.item = itl.item
WHERE
requesting_user = dbo.ANP_SplitParameters(#params, ',', 1)
AND device_id = dbo.ANP_SplitParameters(#params, ',', 2)
--AND sent_logistics = 0
AND sent_purchasing = 0
AND itl.qty_on_hand <= 0
ORDER BY scan_date
FOR XML PATH('tr'), TYPE
) AS NVARCHAR(MAX) ) +
N'</table>' ;
The result set looks like so:
Data i godzina Pozycja Lokalizacja Jednostka Ilość Zamawiający Dostępne na
2018-08-31T16:25:00 G353K1120XA0000 ll Pudelko 9 1613 QCS
2018-08-31T16:25:00 G353K1120XA0000 ll Pudelko 9 1613 REK
2018-08-31T16:25:00 G353K1120XA0000 ll Pudelko 9 1613 WGVS1
As you can see, only the last column differs. I would like to have this query return one row with all the items from last column listed as one field. I tried that with coalesce, without it, but I can't really get it to work. What else can I try?
The way to do this in SQL Server is to use the STUFF function. To demonstrate how this is used, I give a simplified example below. This uses a self join, but it is easy to change.
declare #test1 table
(
fkfield int,
datafield varchar(50)
)
insert into #test1 VALUES
(1,'ABC'),
(1,'DEF'),
(2,'xyz'),
(2,'mno'),
(2,'pqr')
SELECT DISTINCT
fkfield,
Stuff((SELECT ', ' + datafield
FROM #test1 t2
WHERE t2.fkfield = t1.fkfield
FOR XML PATH('')), 1, 2, '') ConCatData
FROM #test1 t1
The results look like this:
1 ABC, DEF
2 xyz, mno, pqr
HTH

How to sum values of multiple columns in SQL Server

SELECT
name
FROM
sys.all.column
WHERE object_id = (SELECT object_id
FROM sys.all_objects
WHERE name ='name of my table' and type = 'TT')
AND name NOT IN (list of columns that I don't need)
How do I sum all the values of the returned columns from the preceding SQL query?
Another option which does not require dynamic SQL, but only a CROSS APPLY or two
Just for fun, I add Min, Max, and Avg just to illustrate... Also added a PctOfTotal or Common-Size
Declare #YourTable table (ID int,CustName varchar(50),Sales_Jan int,Sales_Feb int,Sales_Mar int)
Insert into #YourTable values
(1,'John Smith',25,25,50),
(2,'Jane Doe' ,35,20,null)
Select A.*
,C.*
,PctOfTotal = Format(C.Total*1.0/Sum(C.Total) over (),'0.00%')
From #YourTable A
Cross Apply (Select XMLData=cast((Select A.* For XML RAW) as xml)) B
Cross Apply (
Select Total = Sum(Value)
,Min = Min(Value)
,Max = Max(Value)
,Avg = Avg(Value)
From (
Select Value = attr.value('.','int')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') Like 'Sales_%'
--Or you can Exclude Specific Columns
--Where attr.value('local-name(.)','varchar(100)') not in ('ID','CustName')
) S
) C
Returns
If I understand correctly, you want to find out some columns from meta tables that you want to sum, and then sum those columns on the given table. You can use dynamic SQL to achieve this:
create table t(a integer, b integer, c integer);
insert into t values(1,2,3);
declare #tab varchar(100);
declare #sql varchar(max);
set #sql = '';
set #tab = 't';
select #sql = #sql + '+' + a.name from sys.all_columns a
inner join
sys.all_objects b
on a.object_id = b.object_id
where b.name = #tab
and a.name not in ('c');
set #sql = 'select ' + stuff(#sql, 1, 1, '') + ' from ' + #tab;
exec(#sql);
Produces:
3
select col1,col2,col3,col4,NVL(col1,0)+NVL(col2,0)+NVL(col3,0)+NVL(col4,0)
from
(select *
from sys.all.column
where object_id =(select object_id from sys.all.object where name ='name of my table')
and name not in (list of columns that I dont need).)
A | B | Total(col1+col2)
------+------+-------
1 | 2 | 3
---------------------
1 | | 1
Whatever columns you get, sum it and put them as seperate column in the result table.

Stored procedure that returns a table from 2 combined

I am trying to write a stored procedure which returns a result combining 2 table variables which looks something like this.
Name | LastName | course | course | course | course <- Columns
Name | LastName | DVA123 | DVA222 | nothing | nothing <- Row1
Pete Steven 200 <- Row2
Steve Lastname 50 <- Row3
From these 3 tables
Table Staff:
Name | LastName | SSN |
Steve Lastname 234
Pete Steven 132
Table Course Instance:
Course | Year | Period |
DVA123 2013 1
DVA222 2014 2
Table Attended by:
Course | SSN | Year | Period | Hours |
DVA123 234 2013 1 200
DVA222 132 2014 2 50
I am taking #year as a parameter that will decide what year in the course will be displayed in the result.
ALTER proc [dbo].[test4]
#year int
as
begin
-- I then declare the 2 tables which I will then store the values from the tables
DECLARE #Table1 TABLE(
Firstname varchar(30) NOT NULL,
Lastname varchar(30) NOT NULL
);
DECLARE #Table2 TABLE(
Course varchar(30) NULL
);
Declare #variable varchar(max) -- variable for saving the cursor value and then set the course1 to 4
I want at highest 4 results/course instances which I later order by the period of the year
declare myCursor1 CURSOR
for SELECT top 4 period from Course instance
where year = #year
open myCursor1
fetch next from myCursor1 into #variable
--print #variable
while ##fetch_status = 0
Begin
UPDATE #Table2
SET InstanceCourse1 = #variable
where current of myCursor1
fetch next from myCursor1 into #variable
print #variable
End
Close myCursor1
deallocate myCursor1
insert into #table1
select 'Firstname', 'Lastname'
insert into #table1
select Firstname, Lastname from staff order by Lastname
END
select * from #Table1 -- for testing purposes
select * from #Table2 -- for testing purposes
--Then i want to combine these tables into the output at the top
This is how far I've gotten, I don't know how to get the courses into the columns and then get the amount of hours for each staff member.
If anyone can help guide me in the right direction I would be very grateful. My idea about the cursor was to get the top (0-4) values from the top4 course periods during that year and then add them to the #table2.
Ok. This is not pretty. It is a really ugly dynamic sql, but in my testing it seems to be working. I have created an extra subquery to get the courses values as the first row and then Union with the rest of the result. The top four courses are gathered by using ROW_Number() and order by Year and period. I had to make different versions of the courses string I am creating in order to use them for both column names, and in my pivot. Give it a try. Hopefully it will work on your data as well.
DECLARE #Year INT
SET #Year = 2014
DECLARE #Query NVARCHAR(2000)
DECLARE #CoursesColumns NVARCHAR(2000)
SET #CoursesColumns = (SELECT '''' + Course + ''' as c' + CAST(ROW_NUMBER() OVER(ORDER BY Year, Period) AS nvarchar(50)) + ',' AS 'data()'
FROM AttendedBy where [Year] = #Year
for xml path(''))
SET #CoursesColumns = LEFT(#CoursesColumns, LEN(#CoursesColumns) -1)
SET #CoursesColumns =
CASE
WHEN CHARINDEX('c1', #CoursesColumns) = 0 THEN #CoursesColumns + 'NULL as c1, NULL as c2, NULL as c3, NULL as c4'
WHEN CHARINDEX('c2', #CoursesColumns) = 0 THEN #CoursesColumns + ',NULL as c2, NULL as c3, NULL as c4'
WHEN CHARINDEX('c3', #CoursesColumns) = 0 THEN #CoursesColumns + ', NULL as c3, NULL as c4'
WHEN CHARINDEX('c4', #CoursesColumns) = 0 THEN #CoursesColumns + ', NULL as c4'
ELSE #CoursesColumns
END
DECLARE #Courses NVARCHAR(2000)
SET #Courses = (SELECT Course + ' as c' + CAST(ROW_NUMBER() OVER(ORDER BY Year, Period) AS nvarchar(50)) + ',' AS 'data()'
FROM AttendedBy where [Year] = #Year
for xml path(''))
SET #Courses = LEFT(#Courses, LEN(#Courses) -1)
SET #Courses =
CASE
WHEN CHARINDEX('c1', #Courses) = 0 THEN #Courses + 'NULL as c1, NULL as c2, NULL as c3, NULL as c4'
WHEN CHARINDEX('c2', #Courses) = 0 THEN #Courses + ',NULL as c2, NULL as c3, NULL as c4'
WHEN CHARINDEX('c3', #Courses) = 0 THEN #Courses + ', NULL as c3, NULL as c4'
WHEN CHARINDEX('c4', #Courses) = 0 THEN #Courses + ', NULL as c4'
ELSE #Courses
END
DECLARE #CoursePivot NVARCHAR(2000)
SET #CoursePivot = (SELECT Course + ',' AS 'data()'
FROM AttendedBy where [Year] = #Year
for xml path(''))
SET #CoursePivot = LEFT(#CoursePivot, LEN(#CoursePivot) -1)
SET #Query = 'SELECT Name, LastName, c1, c2, c3, c4
FROM (
SELECT ''Name'' as name, ''LastName'' as lastname, ' + #CoursesColumns +
' UNION
SELECT Name, LastName,' + #Courses +
' FROM(
SELECT
s.Name
,s.LastName
,ci.Course
,ci.Year
,ci.Period
,CAST(ab.Hours AS NVARCHAR(100)) AS Hours
FROM Staff s
LEFT JOIN AttendedBy ab
ON
s.SSN = ab.SSN
LEFT JOIN CourseInstance ci
ON
ab.Course = ci.Course
WHERE ci.Year=' + CAST(#Year AS nvarchar(4)) +
' ) q
PIVOT(
MAX(Hours)
FOR
Course
IN (' + #CoursePivot + ')
)q2
)q3'
SELECT #Query
execute(#Query)
Edit: Added some where clauses so only courses from given year is shown. Added Screenshot of my results.
try this
DECLARE #CourseNameString varchar(max),
#query AS NVARCHAR(MAX);
SET #CourseNameString=''
select #CourseNameString = STUFF((SELECT distinct ',' + QUOTENAME(Course)
FROM Attended where [Year]= 2013
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = '
select Name,LastName,'+#CourseNameString+' from Staff as e inner join (
SELECT * FROM
(SELECT [Hours],a.SSN,a.Course as c FROM Attended as a inner JOIN Staff as s
ON s.SSN = s.SSN) p
PIVOT(max([Hours])FOR c IN ('+#CourseNameString+')) pvt)p
ON e.SSN = p.SSN'
execute(#query)
Use subquery like this one :
SELECT Firstname, Lastname, (select instanceCourse1 from table2) as InstanceCourse1 from Table1

How to count in SQL all fields with null values in one record?

Is there any way to count all fields with null values for specific record excluding PrimaryKey column?
Example:
ID Name Age City Zip
1 Alex 32 Miami NULL
2 NULL 24 NULL NULL
As output I need to get 1 and 3. Without explicitly specifying column names.
declare #T table
(
ID int,
Name varchar(10),
Age int,
City varchar(10),
Zip varchar(10)
)
insert into #T values
(1, 'Alex', 32, 'Miami', NULL),
(2, NULL, 24, NULL, NULL)
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select *
from #T as T2
where T1.ID = T2.ID
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Result:
ID NullCount
----------- -----------
1 1
2 3
Update:
Here is a better version. Thanks to Martin Smith.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(/row/*[#ns:nil = "true"])', 'int') as NullCount
from #T as T1
Update:
And with a bit faster XQuery expression.
;with xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' as ns)
select ID,
(
select T1.*
for xml path('row'), elements xsinil, type
).value('count(//*/#ns:nil)', 'int') as NullCount
from #T as T1
SELECT id,
CASE WHEN Name IS NULL THEN 1 ELSE 0 END +
CASE WHEN City IS NULL THEN 1 ELSE 0 END +
CASE WHEN Zip IS NULL THEN 1 ELSE 0 END
FROM YourTable
If you do not want explicit column names in query, welcome to dynamic querying
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql + N' CASE WHEN '+QUOTENAME(c.name)+N' IS NULL THEN 1 ELSE 0 END +'
FROM sys.tables t
JOIN sys.columns c
ON t.object_id = c.object_id
WHERE
c.is_nullable = 1
AND t.object_id = OBJECT_ID('YourTableName')
SET #sql = N'SELECT id, '+#sql +N'+0 AS Cnt FROM [YourTableName]'
EXEC(#sql)
This should solve your problem:
select count (id)
where ( isnull(Name,"") = "" or isnull(City,"") = "" or isnull(Zip,"") = "" )
Not a smart solution, but it should do the work.
DECLARE #tempSQL nvarchar(max)
SET #tempSQL = N'SELECT '
SELECT #tempSQL = #tempSQL + 'sum(case when ' + cols.name + ' is null then 1 else 0 end) "Null Values for ' + cols.name + '",
sum(case when ' + cols.name + ' is null then 0 else 1 end) "Non-Null Values for ' + cols.name + '",' FROM sys.columns cols WHERE cols.object_id = object_id('TABLE1');
SET #tempSQL = SUBSTRING(#tempSQL, 1, LEN(#tempSQL) - 1) + ' FROM TABLE1;'
EXEC sp_executesql #tempSQL