2

If the output of my query is England, America, India.

Is it possible to display the query result like England, America and India

Here is my query:

Select stuff( ( Select Distinct ',' + Country as[text()] 
from tbl_Country 
where OrderNo in (Select Max(OrderNo) from Tbl_Literature where ID='AB123' 
                  Group By OrderNo) for XML path('') ),1,1,'')as Result 
from dbo.Tbl_Master as TP 
where 
  TP.OrderNo IN (select MAX(order No) from dbo.Tbl_Master where TP.ID = 'AB123') 
StuartLC
  • 104,537
  • 17
  • 209
  • 285
Himani
  • 159
  • 5
  • 22
  • Will your query return "England, America, India" in one column? – AK47 Dec 09 '14 at 11:44
  • Yes it returns, I want to append "and" string before the last item "India". – Himani Dec 09 '14 at 11:46
  • How are you getting the concatenated string? – GarethD Dec 09 '14 at 11:47
  • Here is my query: Select stuff( ( Select Distinct ',' + Country as[text()] from tbl_Country where OrderNo in (Select Max(OrderNo) from Tbl_Literature where ID='AB123' Group By OrderNo) for XML path('') ),1,1,'')as Result from dbo.Tbl_Master as TP where TP.OrderNo IN (select MAX(order No) from dbo.Tbl_Master where TP.ID = 'AB123') – Himani Dec 09 '14 at 11:55
  • My query is can I append "and" before the India – Himani Dec 09 '14 at 11:56

4 Answers4

2

Assuming you are referring to the string aggregation hack done to compensate for SqlServer's lack of GROUP_CONCAT, you can use this ingenious hack here:

WITH myStuffedCte AS
(SELECT 
    STUFF((
          SELECT ',' + Name
          FROM Country
          FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS SomeCol
)
SELECT ISNULL(reverse(replace(STUFF(reverse(SomeCol),
                      charindex(',',reverse(SomeCol)),0,'#'),'#,',' dna ')),
              SomeCol)
FROM myStuffedCte;

SqlFiddle here

Updated example of Stuff with a GROUPING criteria

In order to use STUFF in a GROUP BY fashion, you can manually filter the inner STUFF subquery based on an outer query's rows with a WHERE clause.

WITH myStuffedCte AS
(
    SELECT p.FirstName, p.LastName,
        STUFF((
              SELECT ',' + c.Name
              FROM Country c
              INNER JOIN PersonCountryVisits pcv
              ON pcv.CountryId = c.CountryId
              WHERE pcv.PersonId = p.PersonID -- Manual Filter step
              FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS SomeCol
    FROM Person p
)
SELECT FirstName, LastName,
   ISNULL(reverse(replace(STUFF(reverse(SomeCol),
          charindex(',',reverse(SomeCol)),0,'#'),'#,',' dna ')), 
          SomeCol) AS CountriesVisited
FROM myStuffedCte;

Updated Sql Fiddle example here

SqlClr Alternative to Stuff, IFF you can use SqlClr on your SqlServer:
If you have access to a Sql-Clr enabled SqlServer, you can ditch both of these hacks and instead, replace STUFF / FOR XML with a GROUP_CONCAT simulation of SqlUserDefinedAggregate like this one, and at the same time do the last comma replacement with a SqlFunction UDF, like this:

[SqlFunction]
public static SqlString ReplaceLast(SqlString source, SqlString find, SqlString replace, 
                                    int countFromRight)
{
    var splits = source.Value
        .Split(new[] {find.Value}, StringSplitOptions.None);

    var replacePoint = splits.Length - countFromRight;

    return (replacePoint > 0)
        ? new SqlString(
            string.Join(replace.Value,
                new[] { string.Join(find.Value, splits.Take(replacePoint)) }
                .Union(splits.Skip(replacePoint))))
        : source;
}

In which case the sample query becomes the entirely more palatable:

SELECT FirstName, LastName, 
    dbo.ReplaceLast(dbo.GROUP_CONCAT(c.Name, ','), ',', ' and ', 1)
FROM  Person p 
    INNER JOIN PersonCountryVisits pcv
    ON pcv.PersonId = p.PersonID
    INNER JOIN Country c
    ON pcv.CountryId = c.CountryId
GROUP BY FirstName, LastName;

I've put up a Gist on GitHub which provides all the salient bits to create the GROUP_CONCAT and ReplaceLast SqlClr functions. (SqlFiddle not available - No SQLClr ...)

Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • StaurtLC: I am able to get the output as expected but the query eliminates other columns. With the country column I have other Columns like FirstName, LastName and other user details to be displayed. In this only country column need to be concatenated with respect to id, rest are singular – Himani Dec 09 '14 at 12:20
  • So the trick here is then to return all the necessary columns from the CTE, and in addition, to manually filter each inner loop of the `STUFF` hack back to the group table. I've taken a stab at guessing your data model - [Updated Sql Fiddle is here](http://sqlfiddle.com/#!6/d1a40/1). – StuartLC Dec 09 '14 at 12:38
  • 1
    Many thanks StaurtLC.. I am able to get the output as expected :) – Himani Dec 09 '14 at 13:06
  • @StaurtLC i need your help for the above query. If the query result has only country like "New Zealand" then the result is displayed as null.. I tried using Case statement, but its in vain – Himani Dec 10 '14 at 10:08
2

As you have mentioned the stuff command, I assume you are using FOR XML PATH('') to concatenate your items, and STUFF to remove the first delimiter, e.g.

SELECT  STUFF(( SELECT  ', ' + Country
                FROM    InnerTable AS i
                WHERE   i.ID = o.ID
                ORDER BY Country
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
FROM    OuterTable AS o;

If this is the case then you can use ROW_NUMBER() to identify the last row (by using the inverse ordering to the subquery), and put AND in there:

SELECT  STUFF(( SELECT  ', ' + CASE WHEN ROW_NUMBER() OVER(ORDER BY Country DESC) = 1 
                                    THEN 'and ' 
                                    ELSE '' 
                                END + Country
                FROM    InnerTable AS i
                WHERE   i.ID = o.ID
                ORDER BY Country ASC
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
FROM    OuterTable AS o;

n.b. The above will result in a string serial comma (e.g. "England, America, and India"), but it won't require much manipulation to change this.

SIMPLE EXAMPLE

SELECT  STUFF(( SELECT  ', ' + CASE WHEN ROW_NUMBER() OVER(ORDER BY Country DESC) = 1 
                                    THEN 'and ' 
                                    ELSE '' 
                                END + Country
                FROM    (VALUES ('America'), ('England'), ('India')) AS i (Country)
                ORDER BY Country ASC
                FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)'), 1, 2, '');
GarethD
  • 68,045
  • 10
  • 83
  • 123
  • GarethD: I tried with the above query. It is appending the "and" string @first. i.e. ,andEngland, America, India. – Himani Dec 09 '14 at 12:16
  • Then you are using the same order by criteria in the `ROW_NUMBER()` function as you are in the subquery, the must be the opposite, note in the answer I have `ORDER BY Country DESC` inside the `ROW_NUMBER()` function, but `ORDER BY Country` in the subquery. (Although I did have the `ORDER BY` in the wrong place, I have corrected this and also added `ASC` to make it clear that the ordering is inverted) – GarethD Dec 09 '14 at 12:21
  • when it results in a single County it still puts "and " at the beginning. How do we avoid this? i.e. "and America" – Darrow7456 Jul 08 '21 at 15:08
1

Try this:

    Declare @String varchar(100)

    set @String = 'England, America, India'

    SELECT reverse(replace(STUFF(reverse(@String),
           charindex(',',reverse(@String)),0,'#'),'#,',' dna '))
Veera
  • 3,412
  • 2
  • 14
  • 27
  • When writing code like this any assumptions/conditions should be explicitly stated. For example, this ***requires*** each item in the list to ***not*** end with `#`. Also, as it is post-processing the aggregation, the last item in the list should not contain a `,`. – MatBailie Dec 09 '14 at 12:57
  • Also, what is its behaviour with a list of 1 item? – MatBailie Dec 09 '14 at 12:59
0

Use SUBSTRING(). Refer this for more String functions

 DECLARE @s AS VARCHAR(100)

 SET @s = 'England, America, India'

 SELECT SUBSTRING(@s, 0, LEN(@s) -  CHARINDEX(',', REVERSE(@s)) + 1 ) 
        + ' and' 
        + SUBSTRING(@s, LEN(@s) -  CHARINDEX(',', REVERSE(@s)) + 2, len(@s))
Saravana Kumar
  • 3,669
  • 5
  • 15
  • 35