concat column names with string inside a string - sql

I am trying to create a pretty column to display but I cannot get a space or hyphen to work. I want the column to display like
Jun-2014 or Jun 2014 but I get errors when I try to insert it the normal way due to the query already being inside a string.
Here is the part I am having trouble with:
(LEFT(DATENAME(month, leadtime),3) + " " + CONVERT(varchar, DATEPART(year, leadtime))) as leadmonth
And here is the rest of the query for context:
set #query = 'SELECT name AS "Lead Source",' + #cols + '
from
(
SELECT d.name, COUNT(u.lead_source_id) AS totalLeads, + "["+ (LEFT(DATENAME(month, leadtime),3) + " " + CONVERT(varchar, DATEPART(year, leadtime)))+"] as leadmonth" +
FROM DI_TrackingDB.dbo.userleads u
INNER JOIN GSPremiumServices.dbo.supplier_product_lead_source_def d ON d.lead_source_id = u.lead_source_id
WHERE vend_id = 355135
AND u.lead_source_id IS NOT NULL
GROUP BY u.lead_source_id, d.name, DATENAME(month, leadtime), DATEPART(year, leadtime), Convert(varchar(7), leadtime, 126)
) x
pivot
(
max(totalLeads)
for leadmonth in (' + #cols + ')
) p '
Please keep in mind I am setting #query so I am already inside of a string.
Any suggestions are appreciated.

The answer was quite simple. Not sure if people didn't quite understand my initial question but the answer was to use two ' on either side of the inner string. Below is the final version of the code that works.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(monthyear)
FROM (SELECT DISTINCT (LEFT(DATENAME(MONTH, month_start),3)+ '-' + CONVERT(VARCHAR, DATEPART(YEAR, month_start))) AS monthyear, month_start
FROM dbo.mo_vendor_month WITH(NOLOCK)
WHERE vend_id = #arguments.prospectId#
AND month_start >= '#startDate#'
)alertsMonths
ORDER BY month_start ASC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
SET #query = 'SELECT ' + #cols + '
FROM
(
SELECT total_unique_leads,(LEFT(DATENAME(MONTH, month_start),3)+ ''-'' + CONVERT(VARCHAR, DATEPART(YEAR, month_start))) AS monthyear
FROM dbo.mo_vendor_month
WHERE vend_id = #arguments.prospectId#
AND month_start >= ''#startDate#''
GROUP BY month_start, total_unique_leads
) x
pivot
(
max(total_unique_leads)
FOR monthyear IN (' + #cols + ')
) p '
EXECUTE(#query)

Just put square brackets around the field name you're constructing, like this:
"[" + (LEFT(DATENAME(month, leadtime),3) + " " + CONVERT(varchar, DATEPART(year, leadtime))) + "] as leadmonth"
I would also be ready to handle any single quotes or double quotes that might occur (by escaping them as needed when building your string). Other than that, enclosing a field name in square brackets allows you to use characters that normally would not be allowed in an identifier's name.

Related

SQL Pivot - take a changing date from column and make it a row header

I have a basic query that looks like this.
SELECT Database_Name,
FilingDate,
SUM(ISNULL([column1], 0) + ISNULL(column2], 0) +
ISNULL([column3], 0) + ISNULL([column4], 0)) AS Total
FROM SomeTable(NOLOCK)
GROUP BY Database_Name,
FilingDate
ORDER BY Database_Name,
FilingDate DESC
This query outputs results that look like this.
I would like to take the dates returned in the FilingDate column and use them as new column headers with the totals for each database and date being used as the row content. The end result should look like this:
My research suggests that a pivot is the best option but I'm struggling to find the right way to execute it as my dates change each day Any assistance would be appreciated.
If this is MS SQL, you can use a dynamic pivot table. Here is a solution using your query (should work, but I don't have the base data to test it).
SELECT Database_Name,
FilingDate,
SUM( ISNULL(column1 ,0) +
ISNULL(column2],0) +
ISNULL([column3],0) +
ISNULL([column4],0)
) AS Total
INTO #T1
FROM SomeTable(NOLOCK)
GROUP BY Database_Name,
FilingDate
DECLARE #PivotColumnHeaders varchar(MAX)
SELECT #PivotColumnHeaders =
COALESCE(
#PivotColumnHeaders + ',[' + CAST(UC.FilingDate AS NVARCHAR(10)) + ']',
'[' + CAST(UC.FilingDate AS NVARCHAR(10)) + ']'
)
FROM (SELECT FilingDate FROM #T1 GROUP BY FilingDate) UC
DECLARE #PQuery varchar(MAX) = '
SELECT * FROM (SELECT Database_Name, FilingDate, Total FROM #T1 T0) T1
PIVOT (SUM([Total]) FOR FilingDate IN (' + #PivotColumnHeaders + ') ) AS P'
EXECUTE (#PQuery)
DROP TABLE #T1

Query Filter based on condition

I have this query (SQL Server 2019) and I actually wanted to add a filter to it to show only items where IssueStatusID = 1 (This column is based on int Data Type)
Can anyone help with this or point me in the right direction?
SELECT ID,
STRING_AGG(TRY_CONVERT(varchar, Issue#, 101) + ': ' + Notes + CHAR(13) + CHAR(10) + CHAR(13), CHAR(10)) WITHIN GROUP (ORDER BY Issue# DESC) AS IssueNotes
FROM (SELECT DISTINCT
ac4.ID,
nd.Notes,
nd.Issue#,
nd.IssueStatusID
FROM dbo.IssueTracking AS nd
INNER JOIN dbo.StatusUpdates AS ac4 ON ac4.ID = nd.ReleaseTrackerID) AS vNotes
GROUP BY ID;
Add the WHERE clause as shown below. The WHERE clause limits the data being returned.
SELECT ID,
STRING_AGG(TRY_CONVERT(varchar, Issue#, 101) + ': ' + Notes + CHAR(13) + CHAR(10) + CHAR(13), CHAR(10)) WITHIN GROUP (ORDER BY Issue# DESC) AS IssueNotes
FROM (SELECT DISTINCT
ac4.ID,
nd.Notes,
nd.Issue#,
nd.IssueStatusID
FROM dbo.IssueTracking AS nd
INNER JOIN dbo.StatusUpdates AS ac4 ON ac4.ID = nd.ReleaseTrackerID
WHERE nd.IssueStatusID = 1
) AS vNotes
GROUP BY ID;

Invalid column name after PIVOT

I would like something like this:
42 | 41 | 31 | 32 | Name
----------------------------
O 42
X 41
P 32
Y 41
Z 41
The column headers that are pivoted are also the value in the name column. The various columns can have different statuses. This is what I have but I keep getting an error saying the ValveGroupName column is invalid.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = stuff((select ',' + quotename(ValveGroupName)
from dbo.adm_ValveGroup vgroup,
dbo.adm_Station station
where station.StationID = vgroup.StationID
and station.StationName in ('CBRN')
order by vgroup.ValveGroupName desc
for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '')
set #query = 'select ValveGroupName,' + #cols + ' from
(
select vlog.status,
vgroup.ValveGroupName
from dbo.adm_Station station,
dbo.adm_ValveGroup vgroup,
dbo.valvegroup_log vlog
where station.StationID = vgroup.StationID
and vgroup.ValveGroupID = vlog.ValveGroupID
and station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
execute (#query)
What's wrong with this query?
Since your ValveGroupName is becoming the new column names from the PIVOT, you normally won't include that in the final select list. Since you are basically "removing" the ValveGroupName to become the new columns, SQL Server doesn't have that column any longer so it throws an error.
The code would normally be:
set #query = 'select ' + #cols + '
from
(
select vlog.status,
vgroup.ValveGroupName
from dbo.adm_Station station
inner join dbo.adm_ValveGroup vgroup
on vgroup.ValveGroupID = vlog.ValveGroupID
inner join dbo.valvegroup_log vlog
on station.StationID = vgroup.StationID
where station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
However, since you are aggregating a string that will return a single row for each ValveGroupName, if you want to return multiple rows, then you'll need to include a row_number:
set #query = 'select ' + #cols + '
from
(
select vlog.status,
vgroup.ValveGroupName,
seq = row_number() over(partition by vgroup.ValveGroupName
order by vlog.status)
from dbo.adm_Station station,
inner join dbo.adm_ValveGroup vgroup
on vgroup.ValveGroupID = vlog.ValveGroupID
inner join dbo.valvegroup_log vlog
on station.StationID = vgroup.StationID
where station.StationName in (''CBRN'')
and vlog.logdate between ''2012-10-01'' and ''2012-10-30''
) x
pivot
(
max(status)
for ValveGroupName in (' + #cols + ')
) p '
This change will allow you to return multiple rows in each column. Note: I also change the code to use INNER JOIN syntax instead of the comma joins, with the conditions in the WHERE clause.

change the column name while selecting from dyanamic table

Hi I have attendence query which will generate the attendence report with using PIVOT function
Here's the procedure :
declare #in_date DATETIME
/*Select all the stagign entries related to promotion id and investment type id */
/* also only those staging daat related interface status tracking*/
-- Getting all distinct dates into a temporary table #Dates
SELECT a.date as full_date_of_attendence INTO #Dates
FROM dbo.getFullmonth(#in_date) a
ORDER BY a.date
-- The number of days will be dynamic. So building
-- a comma seperated value string from the dates in #Dates
SELECT #cols = COALESCE(#cols + ',[' + CONVERT(varchar, full_date_of_attendence, 106)
+ ']','[' + CONVERT(varchar, full_date_of_attendence, 106) + ']')
FROM #Dates
ORDER BY full_date_of_attendence
--select #cols
---- Building the query with dynamic dates
SET #qry =
'SELECT * FROM
(SELECT admission_id, attendence_status , date_of_attendence
FROM dbo.tblattendence)emp
PIVOT (MAX(attendence_status) FOR date_of_attendence IN (' + #cols + ')) AS stat'
-- Executing the query
EXEC(#qry)
-- Dropping temporary tables
DROP TABLE #Dates
here is the output of the above query::
admission_id 01 May 2013 02 May 2013 03 May 2013
2 NULL 1 0
3 NULL 1 1
4 NULL 0 0
5 NULL 0 1
Here I want to change the names of the columns as 01,02,03......
and I want the values 1 as 'P' and 0 as 'A'
can anyone would help me to achieve this ??
I would suggest the following changes to your code. If you want a list of the days (1, 2, 3, etc), then you can use the DAY function.
Typically when I get a list of columns dynamically, my preference is using STUFF and FOR XML PATH, I would alter that code to the following:
select #colsPiv = STUFF((SELECT ',' + QUOTENAME(cast(day(full_date_of_attendence) as varchar(2)))
from #Dates
GROUP BY full_date_of_attendence
ORDER BY full_date_of_attendence
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Then if you want to replace the 0 with an A and a 1 with a P, you will want to create a query to get a list of columns to replace the values:
select #colsSel = STUFF((SELECT ', case when ' + QUOTENAME(cast(day(full_date_of_attendence) as varchar(2)))+'= 1 then ''P'' else ''A'' end as '+QUOTENAME(cast(day(full_date_of_attendence) as varchar(2)))
from #Dates
GROUP BY full_date_of_attendence
ORDER BY full_date_of_attendence
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
Basically, this is creating a select list similar to this:
select case when [1] = 1 then 'P' else 'A' end as [1], ...
Then your final query will be:
SET #qry =
'SELECT admission_id, '+#colsSel +'
FROM
(
SELECT admission_id,
attendence_status ,
day(date_of_attendence) date_of_attendence
FROM dbo.tblattendence
)emp
PIVOT
(
MAX(attendence_status)
FOR date_of_attendence IN (' + #colsPiv + ')
) AS stat'
See SQL Fiddle with Demo
Let's change just the two things you wanted to, i.e.
CONVERT(CHAR(2), full_date_of_attendence, 106) -- use CHAR(2) instead of varchar
CASE attendence_status when 1 then 'P' else 'A' END in the SELECT...
The code with minimal changes. Hope this helps you see how you can make similar changes in future to other code.
declare #in_date DATETIME
/*Select all the stagign entries related to promotion id and investment type id */
/* also only those staging daat related interface status tracking*/
-- Getting all distinct dates into a temporary table #Dates
SELECT a.date as full_date_of_attendence INTO #Dates
FROM dbo.getFullmonth(#in_date) a
ORDER BY a.date
-- The number of days will be dynamic. So building
-- a comma seperated value string from the dates in #Dates
SELECT #cols = COALESCE(#cols + ',', '') + [' +
CONVERT(CHAR(2), full_date_of_attendence, 106) + ']'
FROM #Dates
ORDER BY full_date_of_attendence
--select #cols
---- Building the query with dynamic dates
SET #qry =
'SELECT * FROM
(SELECT admission_id, CASE attendence_status when 1 then 'P' else 'A' END, date_of_attendence
FROM dbo.tblattendence)emp
PIVOT (MAX(attendence_status) FOR date_of_attendence IN (' + #cols + ')) AS stat'
-- Executing the query
EXEC(#qry)
-- Dropping temporary tables
DROP TABLE #Dates

Concatenation of strings by for xml path

Hi!
Today I learned for xml path technique to concatenate strings in mssql. Since I've never worked with xml in mssql and google hasn't helped, I need to ask you.
Let's imagine the default case. We need to concatenate some strings from a table:
declare #xmlRepNames xml = (
select
', [' + report_name + ']'
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('.', 'nvarchar(max)')), 1, 1, '')
So I get smth like this:
[str1], [str2], [strn]
Ok. It works fine. But I have two very similar concatenate blocks. The difference is just in the way the result string looks like:
[str1], [str2], [strn]
and
isnull([str1], 0) as [str1], isnull([str2], 0) as [str2], isnull([strn], 0) as [strn]
So I can write 2 very similar code blocks (already done, btw) with different string constructors or to try extend previous code to has xml variable containing 2 kind of constructors and then concatenate by xml node type. Doing 2nd way I met some problems - I wrote this:
declare #xmlRepNames xml = (
select
', [' + report_name + ']' as name,
', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (
select distinct
report_order,
report_name
from #report
) x
order by
report_order
for xml path(''), type)
select
stuff((select #xmlRepNames.value('/name', 'nvarchar(max)')), 1, 1, ''),
stuff((select #xmlRepNames.value('/res', 'nvarchar(max)')), 1, 1, '')
but it raise error "XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'".
To replace, e.g., '/name' to '/name[1]' or any other '/name[x]', will return just x-th 'name' record but not all 'name' records concatenated.
[question]: is it possible to solve problem 2nd way like I want and if it's possible then how?
[disclaimer]: the problem isn't really serious for me now (1st way just a little bit uglier but also fine), but it seems very interesting how to come over :)
Thanks!
Your subquery cannot return two values. If you just want to concatenate strings, you do not need the xml data type at all. You can do the stuff() and subquery in a single statement:
declare #Rep1Names nvarchar(max) = (
stuff((select ', [' + report_name + ']' as name
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
declare #Rep2Names nvarchar(max) = (
stuff(select ', isnull([' + report_name + '], 0) as [' + report_name + ']' as res
from (select distinct report_order, report_name
from #report
) x
order by report_order
for xml path('')
)
), 1, 1, '');
Ok, so I haven't been completely satisfied the way Gordon Linoff suggested and since I've found this kind of problem actual for me I'm adding here another solution without using for xml path:
declare
#pivot_sequence nvarchar(max),
#columns_sequence nvarchar(max)
select
#pivot_sequence = coalesce(#pivot_sequence + ', [', '[')
+ col_name + ']',
#columns_sequence = coalesce(#columns_sequence + ', ', '')
+ 'isnull([' + col_name + '], 0) as [' + col_name + ']'
from some_table
order by
/* some_columns if needed to order concatenation */
Obviously, it'll work much slower but in cases when you haven't many rows it won't drastically affect on performance. In my case I have dynamic pivot query and these strings are built for it - I won't have many columns in pivot so it's fine for me.