Stuff with group by on DATENAME function - sql

Situation:
Let there be a table ALPHA which contains a column ALPHA.ID_BETA (Foreign Key on Table BETA) and a column ALPHA.CREATIONDATE (DATETIME NOT NULL).
Now assume the following records in Table ALPHA:
ID
CREATIONDATE (YYYY-MM-DD)
ID_BETA
1
2022-05-26 00:00:00.000
1
2
2022-02-02 00:00:00.000
1
3
2022-01-28 00:00:00.000
1
4
2022-01-02 00:00:00.000
1
Now imagine Table BETA to look like this (i left out other columns for simplicity:
ID
1
Desired Output:
Is a value that concatenates all values (Format: DATENAME + YYYY) of CREATIONDATE for a single ID_BETA Ordered by date ascending. In this example, the output should be January 2022, February 2022, May 2022 (obviously depending on Language settings)
What I have tried:
SELECT STUFF(
(SELECT ', '
+ DATENAME(MONTH,(ALPHA.CREATIONDATE)) + DATENAME(YEAR, ALPHA.CREATIONDATE)
FROM ALPHA
WHERE ALPHA.ID_BETA = 1
GROUP BY ALPHA.CREATIONDATE
ORDER BY ALPHA.CREATIONDATE ASC
FOR XML PATH('')),1, 1, '')
This however will not give me distinct values. Trying out the obvious DISTINCT statement gives me the following error:
Server: Msg 145, Level 15, State 1, Line 1 ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
Note that I cannot solve this problem with the "new" STRING_AGG function since it's only supported from SQL-Server2017 upwards.

You need to group and sort by EOMONTH(CREATIONDATE) in order to group by a single date per month, rather than grouping just by CREATIONDATE.
Note also that you need .value to unescape the XML, and the third parameter of STUFF should be the same as the length of the separator
SELECT STUFF(
(SELECT
', ' + DATENAME(MONTH, EOMONTH(a.CREATIONDATE)) + DATENAME(YEAR, EOMONTH(a.CREATIONDATE))
FROM ALPHA a
WHERE a.ID_BETA = 1
GROUP BY
EOMONTH(a.CREATIONDATE)
ORDER BY
EOMONTH(a.CREATIONDATE)
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)'), 1, LEN(', '), '')
If you want to do this per row of BETA you can use CROSS APPLY or a subquery:
SELECT STUFF(
(SELECT
', ' + DATENAME(MONTH, EOMONTH(a.CREATIONDATE)) + DATENAME(YEAR, EOMONTH(a.CREATIONDATE))
FROM ALPHA a
WHERE a.ID_BETA = b.ID
GROUP BY
EOMONTH(a.CREATIONDATE)
ORDER BY
EOMONTH(a.CREATIONDATE)
FOR XML PATH(''), TYPE
).value('text()[1]','nvarchar(max)'), 1, LEN(', '), '')
FROM BETA b;

As an error mesage says, order by exactly the expression in the select clause
SELECT STUFF(
(SELECT distinct ', '
+ DATENAME(MONTH,(ALPHA.CREATIONDATE)) + DATENAME(YEAR, ALPHA.CREATIONDATE)
FROM ALPHA
WHERE ALPHA.ID_BETA = 1
GROUP BY ALPHA.CREATIONDATE
ORDER BY ', '
+ DATENAME(MONTH,(ALPHA.CREATIONDATE)) + DATENAME(YEAR, ALPHA.CREATIONDATE) ASC
FOR XML PATH('')),1, 1, '')

Related

Find out Equal row set

I have a number of row base on plan and plan detail, I want to find out the same row set with other detail like plan 1 has 3 rows of data in detail table so need to find out the same rows for another plan. I have some sample data with this post may be more helpful to understand my problem. below is the Sample data Image, iwan to group by the record but not on a single row base on the full row set base on
PlanId, MinCount, MaxCount and CurrencyId
my expected data is below
I had tried to do with Some lengthy process like append all data in a single row and compare with other data, but it seems very lengthy process and takes to much time for 100 records I have an approx 20000 records in an actual database so not a good solution, please suggest me some thought
This would be trivial in MS Sql Server 2017 by using STRING_AGG.
But in 2012 one of the methods is to use the FOR XML trick.
SELECT PlanId, StairCount, MinCount, MaxCount, CurrencyId,
STUFF((
SELECT CONCAT(',', t1.AccountId)
FROM YourTable t1
WHERE t1.PlanId = t.PlanId
AND t1.StairCount = t.StairCount
AND t1.MinCount = t.MinCount
AND t1.MaxCount = t.MaxCount
AND t1.CurrencyId = t.CurrencyId
ORDER BY t1.AccountId
FOR XML PATH('')), 1, 1, '') AS AccountIdList
FROM YourTable t
GROUP BY PlanId, StairCount, MinCount, MaxCount, CurrencyId
Test here
Your desired result is rather hard to produce in SQL Server. The simplest method requires two levels of string concatenation:
with t as (
select a.account_id,
stuff( (select '[' +
convert(varchar(255), staircount) + ',' +
convert(varchar(255), mincount) + ',' +
convert(varchar(255), maxcount) + ',' +
convert(varchar(255), currencyid) +
']'
from t
where t.account_id = a.account_id
order by staircount
for xml path ('')
), 1, 1, '') as details
from (select distinct account_id from t) a
)
select d.details,
stuff( (select cast(varchar(255), account_id) + ','
from t
where t.details = d.details
for xml path ('')
), 1, 1, '') as accounts
from (select distinct details from t) d;
This isn't exactly your output, but it might be good enough for your problem.

MS SQL Server - concatenating in a particular way

I've got a particular table that I need to concatenate using something like substring, but in a particular way. There are going to be lots of nulls but we still need to pay attention to them.
Basically, I've got something like...
PID Date Flag1 Flag2 Code
11 01/01/2014 1 0 16
11 25/12/2014 1 1 48
11 16/07/2016 0 1 9
12 07/01/2014 0 16
12 08/01/2014 1
12 09/01/2014 16
13 01/10/2014 1 4
13 01/11/2014 1 0 16
13 01/12/2014 0 48
Would result in (very long)...
PID Date Flag1 Flag2 Code
11 01/01/2014,25/12/2014,16/07/2014, 1,1,0, 0,1,1, 16,48,9,
12 07/01/2014,08/01/2014,09/01/2014, ,1,, 0,,, 16,,16,
13 01/10/2014,01/11/2014,01/12/2014, 1,1,, ,0,0, 4,16,48,
This way, in some code I would use later, I would be able to tell which date each flag belongs to.
Any ideas? So far I've just been using the regular substring commands which do put things into the correct fields, but I can't tell what belongs with what.
SELECT DISTINCT PS2.PID, substring
((SELECT ',' + CAST(CONVERT(VARCHAR(10), PS1.Date, 111) AS NVARCHAR) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Date], substring
((SELECT ',' + LEFT(CAST(LUC.Code AS NVARCHAR), 2) AS [text()]
FROM dbo.PS PS1 INNER JOIN
dbo.MyCodes LUC ON PS1.Code = LUC.Id
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Code], substring
((SELECT ',' + LEFT(CAST(PS1.Flag1 AS NVARCHAR), 1) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Flag1], substring
((SELECT ',' + LEFT(CAST(PS1.Flag2 AS NVARCHAR), 1) AS [text()]
FROM dbo.PS PS1
WHERE PS1.PID = PS2.PID
ORDER BY PS1.PID, PS1.Date FOR XML PATH('')), 2, 9999) + ',' [Flag2]
FROM dbo.PS PS2
Should also note, we will always have a Date. That will not be null. Same with the PID (as that's what they're grouped on).
Please try this, I have used Date column as Edate so please replace that column and table name with your original one:
SELECT t1.PID,
STUFF(
(SELECT ',' + cast(EDate AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Edate,
STUFF(
(SELECT ',' + cast(Flag1 AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Flag1,
STUFF(
(SELECT ',' + cast(Flag2 AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Flag2,
STUFF(
(SELECT ',' + cast(Code AS varchar)
FROM #tmpone t WHERE t.PID = t1.PID
FOR XML PATH(''))
, 1, 1, '') Code
FROM #tmpone t1
GROUP BY t1.PID

Concat columns in different rows with same ID SQL Server 2005

I am looking to concat a column in each row that that holds text input....
I am using SQL Server 2005
The rows look like this
Number Date Update Time description
------ ----- ----------- -------------
0123 01/01/2015 01/07/2015 Hello, I want to
0123 01/01/2015 01/01/2015 Concat these columns
Hopefully this is easy and I am just being a simpleton
Because you are dealing free form text, I think it is better to explicitly convert the XML back to a character data type:
SELECT t.Number,
STUFF((SELECT ' ' + cast(t2.description AS nvarchar(max) )
FROM <tablen> t2
WHERE t2.Number = t.Number
FOR XML PATH(''), TYPE
).VALUE('.', 'nvarchar(max)'
), 1, 1, '' ) AS Description
FROM <table> t
GROUP BY t.Number;
This prevents problems with characters such as &, <, and >.
You could use this approach:
SELECT A.Number, MAX(A.[Date]) AS [Date], MAX(A.[Update Time]) AS [Update Time]
, STUFF((SELECT ' ' + B.description AS [text()]
FROM table1 B
WHERE A.Number = B.Number
FOR XML PATH('')), 1, 1, '' ) AS Description
FROM table1 A
GROUP BY A.Number

Selecting data for columns based on a range of dates

I have a table that has a week_id and net_sales for that week (as well as a lot of other columns).
style_number, week_id, net_sales
ABCD, 1, 100.00
ABCD, 2, 125.00
EFGH, 1, 50.00
EFGH, 2, 75.00
I am trying to write a statement that will list the
style_number, net_sales
for the
MAX(week_id), net_sales for the MAX(week_id)-1 .... , MAX(week_id) - n
So that the results look like:
ABCD, 125.00, 100.00
EFGH, 75.00, 50.00
What is the best way to approach this, especially when n can be rather large (i.e. looking back 52 weeks)?
I hope this makes sense! I am using SQL Server 2008 R2. Thanks a lot in advance!
You can use PIVOT and dynamic SQL to deal with your large number of weeks
DECLARE #cols NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(week_id)
FROM sales
ORDER BY 1 DESC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT style_number, ' + #cols +
' FROM
(
SELECT style_number, week_id, net_sales
FROM sales
) x
PIVOT
(
MAX(net_sales) FOR week_id IN (' + #cols + ')
) p
ORDER BY style_number'
EXECUTE(#sql)
Here is SQLFiddle demo.
If you know the number of weeks, since you are using SQL Server 2008, you can use the PIVOT command, or you can use MAX with CASE:
Here's an example using MAX with CASE:
select
style_number,
max(case when week_id = 2 then net_sales end) week2sales,
max(case when week_id = 1 then net_sales end) week1sales
from yourtable
group by style_number
SQL Fiddle Demo
If you do not know the number of weeks, you'll need to look into using dynamic SQL. Just do a search, lots of posts on SO on it.
You might consider using the PIVOT command: http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
OR
If you are okay with the result being a comma separated list, you could use the STUFF and FOR XML commands like so.
SELECT DISTINCT
style_name,
STUFF(
(SELECT ',' + CAST(net_sales AS VARCHAR(20))
FROM MyTable AS SubTable
WHERE SubTableUser.style = MyTable.style_name
ORDER BY week_id DESC --DESC will get your max ID to be first
FOR XML PATH('')), 1, 1, '') AS net_sales_list
FROM MyTable
ORDER BY style_name
This will provide you with:
style_name | net_sales_list
---------------------------
ABCD | 100.00,125.00
EFGH | 75.00,50.00

Stored procedure redundant data and sorting challenge

I've got a stored procedure with a pivot where data fields become columns. Data that is presently returned has some redundancies and would like to see if there is a way to correct this. The stored procedure is actually feeding a .rdlc report and current output looks like this:
Period | No Of Interim | Excellent | Very Good | Good | Satisfactory | Unsatisfactory
-------------------------------------------------------------------------------------
1 1
12 1
18 1
18 1
18 1
19 1
19 1
2 1
2 1
This is what it needs to look like:
Period | No Of Interim | Excellent | Very Good | Good | Satisfactory | Unsatisfactory
-------------------------------------------------------------------------------------
1 1
2 1 1
12 1
18 1 1 1
19 1 1
Period column needs to sorted in ascending fashion and repeating instances need to be added to the same line.
The stored procedure and corresponding view responsible for output is as follows:
BEGIN
DECLARE #cols NVARCHAR(MAX)
DECLARE #query NVARCHAR(MAX)
SET NOCOUNT ON;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT #cols = STUFF((SELECT '],[' + [Description]
FROM vQualScoringGrade
GROUP BY [Description]
ORDER BY MAX([orderby]), [Description]
FOR XML PATH('')), 1,2,'') + ']'
SET #query = N'SELECT Period, ' + #cols + ' FROM
(SELECT Period, Description, Value, OrderBy FROM
vQualScoringGrade) p
PIVOT (SUM([Value]) for [Description] IN ( ' + #cols + ' )) AS pvt ORDER BY Period'
execute(#query)
end
SELECT TOP (100) PERCENT Period, Description, GradeCount AS Value, OrderBy
FROM (SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'NoOfInterim' AS Description, COUNT(Number) AS
GradeCount, 1 AS OrderBy
FROM vQualScoringExcellent AS vQualScoringExcellent
GROUP BY Number, Description
UNION
SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'Excellent' AS Description, COUNT(Description) AS
GradeCount, 2 AS OrderBy
FROM vQualScoringExcellent AS vQualScoringExcellent_1
GROUP BY Number, Description
UNION
SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'VeryGood' AS Description, COUNT(Description) AS
GradeCount, 3 AS OrderBy
FROM vQualScoringVeryGood AS vQualScoringVeryGood
GROUP BY Number, Description
UNION
SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'Good' AS Description, COUNT(Description) AS
GradeCount, 4 AS OrderBy
FROM vQualScoringGood AS vQualScoringGood
GROUP BY Number, Description
UNION
SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'Satisfactory' AS Description, COUNT(Description) AS
GradeCount, 5 AS OrderBy
FROM vQualScoringSatisfactory AS vQualScoringSatisfactory
GROUP BY Number, Description
UNION
SELECT CASE CHARINDEX('.', Number) WHEN 0 THEN Number ELSE
REPLACE(LEFT(Number, CHARINDEX('.', Number)), '.', '')
END AS Period, 'Unsatisfactory' AS Description, COUNT(Description) AS
GradeCount, 6 AS OrderBy
FROM vQualScoringUnsatisfactory AS vQualScoringUnsatisfactory
GROUP BY Number, Description) AS QualScoringGrade
ORDER BY OrderBy
I am guessing that the problem is due to the OrderBy column in your PIVOT query. This line is the cause of your problem:
SELECT Period, Description, Value, OrderBy
I would alter the query to be:
SELECT #cols = STUFF((SELECT '],[' + [Description]
FROM vQualScoringGrade
GROUP BY [Description]
ORDER BY MAX([orderby]), [Description]
FOR XML PATH('')), 1,2,'') + ']'
SET #query = N'SELECT Period, ' + #cols + '
FROM
(
SELECT Period, Description, Value
FROM vQualScoringGrade
) p
PIVOT
(
SUM([Value])
for [Description] IN ( ' + #cols + ' )
) AS pvt
ORDER BY Period'
execute(#query);
The OrderBy column is not being used in the outer select or the PIVOT but you include it in the subquery. This column is being used in the grouping that takes place during a PIVOT. If the values are distinct, then you will get multiple rows.
You can easily test this by including the OrderBy on the final select list.