Concatenate multiple rows to form one single row in SQL Server? - sql

Overview
I need to build a description field that describes an entity. The data I am working with has the property description split for each individual key in my table. Below is an example of what the data looks like:
+------------+--------------------+----------+
| Key | Desc | Order_Id |
+------------+--------------------+----------+
| 5962417474 | Big Yellow Door | 14775 |
| 5962417474 | Orange Windows | 14776 |
| 5962417474 | Blue Triangle Roof | 14777 |
+------------+--------------------+----------+
Originally, I wrote a query using an aggregate function like so:
SELECT
[P].[KEY],
CONCAT (MIN([P].[Desc]), + ' ' + MAX([P].[Desc])) [PROPERTY_DESCRIPTION]
FROM [dbo].[PROP_DESC] [P]
WHERE [P].[KEY] = '5962417474'
GROUP BY [P].[KEY];
This worked great for two row entries but then I realized what if I have multiple records for a property description? So I wrote the following query to check if I had multiple property descriptions:
SELECT
[P].[KEY], COUNT([P].[KEY])
FROM [dbo].[PROP_DESC] [P]
GROUP BY [P].[KEY]
HAVING COUNT(*) > 2; -- Returns one record which is the above table result.
This gave me back a record with three descriptions so my original query will not work. How can I tackle this problem down when there are multiple fields?
Desired Output
+------------+---------------------------------------------------+----------+
| Key | Desc | Order_Id |
+------------+---------------------------------------------------+----------+
| 5962417474 | Big Yellow Door Orange Windows Blue Triangle Roof | 14775 |
+------------+---------------------------------------------------+----------+

It depends on what SQL language you're using, but you'll want to use some kind of group concat / array agg function. Eg:
SELECT
Key,
STRING_AGG(desc, ', ')
FROM TABLE
GROUP BY Key;

I have solved my problem with the following query for those that have the same problem and do not have access to STRING_AGG which is introduced in SQL Server 2017:
SELECT
[P].[KEY],
[PROPERTY_DESCRIPTION] = STUFF((
SELECT ' ' + [P2].[DESC]
FROM [dbo].[PROP_DESC] [P2]
WHERE [P].[KEY] = [P2].[KEY]
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '')
FROM [dbo].[PROP_DESC] [P]
WHERE [P].[KEY] = '5962417474'
GROUP BY [P].[KEY]

There are many ways to do it in SQL server:
Below is one way:
SELECT key
,STUFF((SELECT '| ' + CAST(prop_desc AS VARCHAR(MAX)) [text()]
FROM PROP_DESC
WHERE key = t.key
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') prop_desc
FROM PROP_DESC t
GROUP BY key

Related

Getting Distinct values from query

I am on sql server.
My goal is to parse the name field based on the first space in the name field
then get a distinct list of names
I have the parsed the name out with the below code
SELECT substring(name, 1, CHARINDEX(' ' , name))
From mytable
I am having trouble getting the distinct list of names from the above query result.
Would someone tell me what correct syntax is to do this?
As example
If mytable has the following data
I would want the final query output of distinct to look like this
Mike
Edward
Do you want distinct?
select distinct substring(name, 1, charindex(' ' , name) - 1) name from mytable
Note: unless you want to capture also the trailing space after the name, you need to go back one character before the index of the space in substring().
Demo on DB Fiddle:
| name |
| :----- |
| Edward |
| Mike |

SQL Show all column fields on one row

I have a query that looks like this:
select Item_Number,Size_Description from Style_Size
where Company_Code='01' and Division_Code='008'
and Item_Number='18SP6726B024B' and Color_Code='00605'
This is the table that it returns
+-----------------+------------------+
| Item_Number | Size_Description |
+-----------------+------------------+
| 18SP6726B024B | SMALL |
| 18SP6726B024B | MEDIUM |
| 18SP6726B024B | LARGE |
| 18SP6726B024B | X-LARGE |
+-----------------+------------------+
How do I make it so it shows all Size Descriptions on one field so it would look like
18SP6726B024B SMALL /MEDIUM /LARGE/X-LARGE
Is this possible or would it have to be four different fields through a pivot? And if so, how would I make sure it will work for all item numbers?
You really need to consider upgrading from 2008. It has been out of support for several months now. You can use the STUFF FOR XML technique for this. It is just a lot more code and effort than the much simpler STRING_AGG we have in the current versions.
This should work for you given the sample data you provided. But you probably want a different order for the sizes. There was nothing in your sample data to indicate a proper sort order there so I just used the Size_Description. You will either need a better sorting column or deal with the sizes in alphabetical order.
select Item_Number
, STUFF((Select '/' + Size_Description
from Style_Size ss
where ss.Company_Code = s.Company_Code
and ss.Division_Code = s.Division_Code
and ss.Item_Number = s.Item_Number
and ss.Color_Code = s.Color_Code
order by ss.Size_Description --You might want/need a different column to order by instead of the size description
FOR XML PATH('')), 1,1 ,'')
from Style_Size s
where Company_Code = '01'
and Division_Code = '008'
and Item_Number = '18SP6726B024B'
and Color_Code = '00605'
group by s.Item_Number
, s.Company_Code
, s.Division_Code
, s.Color_Code
I think you are looking for STRING_AGG and a GROUP BY
So something like
select Item_Number, STRING_AGG(Size_Description, ' /')
from Style_Size
where Company_Code='01' and Division_Code='008'
and Item_Number='18SP6726B024B' and Color_Code='00605'
GROUP BY Item_Number

SQL to concatenate all Query rows into one row

I have seen the following questions but can't get them to work for what I'm looking for.
How can multiple rows be concatenated into one in Oracle without creating a stored procedure?
SQL Query to concatenate column values from multiple rows in Oracle
I am using Oracle and would like the solution not to have pl-Sql.
I'm trying to get the query to look as so with a possible new line per each row and delimited with a | or comma :
Data Set:
question_id element_message date
--------------------------------------------
1 hello 10/10/19
2 goodbye 11/11/19
Desired one row query result:
1 | hello | 10/10/19 --new line added in query(if possible)
2 | goodbye| 11/11/19
And if new line in query isn't possible:
Desired result:
1 | hello | 10/10/19 | 2 | goodbye| 11/11/19 |
*EDIT
To to clarify why I am using this. I am using a tool called Geneos with has a row by row limitation in terms of triggering an email. So this is a hack to trigger one email for all the rows per that day.
Why do you want a one row query if you want each row to be printed on a different line?
If you just wan to run a query that returns your data with no header or page separator use this:
set pagesize 0
select question_id || '|' || element_message || '|' || date from your_table;
SQL Server solution :You can solve it using self join like below:
select top 1 CONCAT(d1.question_id, ' | ', d1.element_message, ' | ', d1.date,
'|' d2.question_id, ' | ', d2.element_message, ' | ', d2.date)
from data d1 join data d2 on (d1.question_id < d2.question_id and)
Is this what you're looking for?
SELECT CONCAT(question_id, ' | ', element_message, ' | ', date) FROM Data

Counting occurrences in a table

Lets say I want to count the total number of occurrences of a name contained within a string in a column and display that total next to all occurrences of that name in a new column beside it. For example, if I have:
Name | Home Address | Special ID
==================================
Frank | 152414 | aTRF342
Jane | 4342342 | rRFC432
Mary | 423432 | xTRF353
James | 32111111 | tLZQ399
May | 4302443 | 3TRF322
How would I count the occurrences of special tags like 'TRF', 'RFC', or 'LZQ' so the table looks like this:
Name | Home Address | Special ID | Occurrences
================================================
Frank | 152414 | aTRF342 | 3
Jane | 4342342 | rRFC432 | 1
Mary | 423432 | xTRF353 | 3
James | 32111111 | tLZQ399 | 1
May | 4302443 | 3TRF322 | 3
Currently using Access 2007. Is this even possible using a SQL query?
Using Access 2007, I stored your sample data in a table named tblUser1384831. The query below returns this result set.
Name Home Address Special ID special_tag Occurrences
---- ------------ ---------- ----------- -----------
Frank 152414 aTRF342 TRF 3
Jane 4342342 rRFC432 RFC 1
Mary 423432 xTRF353 TRF 3
James 32111111 tLZQ399 LZQ 1
May 4302443 3TRF322 TRF 3
Although your question has a vba tag, you don't need to use a VBA procedure for this. You can do it with SQL and the Mid() function.
SELECT
base.[Name],
base.[Home Address],
base.[Special ID],
base.special_tag,
tag_count.Occurrences
FROM
(
SELECT
[Name],
[Home Address],
[Special ID],
Mid([Special ID],2,3) AS special_tag
FROM tblUser1384831
) AS base
INNER JOIN
(
SELECT
Mid([Special ID],2,3) AS special_tag,
Count(*) AS Occurrences
FROM tblUser1384831
GROUP BY Mid([Special ID],2,3)
) AS tag_count
ON base.special_tag = tag_count.special_tag;
You would have to GROUP BY the substring of Special ID. In MS Access, you can read about how to compute substrings here.
The problem in your case is that your data in Special ID column does not follow a standard pattern, one which easy to extract via the substring function. You might need to use regular expressions to extract such values, and later apply the GROUP BY to them.
With MSSQL, Oracle, PostgreSQL you would be able to declare a stored procedure (example CLR function in MS SQL Server) that would do this for you. Not sure with MS Access.
you can do something like this:
select Name, [Home Address], [Special ID],
(select count(*) from [your table] where [Special ID] = RemoveNonAlphaCharacters([Special ID]) ) as Occurrences
from [your table]
auxiliar function (got from this link):
Create Function [dbo].[RemoveNonAlphaCharacters](#Temp VarChar(1000))
Returns VarChar(1000)
AS
Begin
While PatIndex('%[^a-z]%', #Temp) > 0
Set #Temp = Stuff(#Temp, PatIndex('%[^a-z]%', #Temp), 1, '')
Return #Temp
End
lets say your first table is called 'table_with_string'
the following code will show the occurance based on the first 3 charecters of string in Special ID column. since it is not clear how exactly you are passing the string to match
select tws.Name,tws.HomeAddress,tws.SpecialID,str_count.Occurrences from
table_with_string tws
left join
(select SpecialID,count(*) from table_with_string where specialID like(substring
(specialid,0,3))
group by specialId) as str_count(id,Occurrences)
on str_count.id=tws.SpecialID
I would suggest doing this explicitly as a join, so you are clear on how it works:
select tws.Name, tws.HomeAddress, tws.SpecialID, str_count.Occurrences
from table_with_string tws
join
(
select substring(spcecialid, 2, 3) as code, count(*) as Occurrences
from table_with_string tws
group by substring(spcecialid, 2, 3)
) s
on s.code = substring(tws.spcecialid, 2, 3)

How do you concat multiple rows into one column in SQL Server?

I've searched high and low for the answer to this, but I can't figure it out. I'm relatively new to SQL Server and don't quite have the syntax down yet. I have this datastructure (simplified):
Table "Users" | Table "Tags":
UserID UserName | TagID UserID PhotoID
1 Bob | 1 1 1
2 Bill | 2 2 1
3 Jane | 3 3 1
4 Sam | 4 2 2
-----------------------------------------------------
Table "Photos": | Table "Albums":
PhotoID UserID AlbumID | AlbumID UserID
1 1 1 | 1 1
2 1 1 | 2 3
3 1 1 | 3 2
4 3 2 |
5 3 2 |
I'm looking for a way to get the all the photo info (easy) plus all the tags for that photo concatenated like CONCAT(username, ', ') AS Tags of course with the last comma removed. I'm having a bear of a time trying to do this. I've tried the method in this article but I get an error when I try to run the query saying that I can't use DECLARE statements... do you guys have any idea how this can be done? I'm using VS08 and whatever DB is installed in it (I normally use MySQL so I don't know what flavor of DB this really is... it's an .mdf file?)
Ok, I feel like I need to jump in to comment about How do you concat multiple rows into one column in SQL Server? and provide a more preferred answer.
I'm really sorry, but using scalar-valued functions like this will kill performance. Just open SQL Profiler and have a look at what's going on when you use a scalar-function that calls a table.
Also, the "update a variable" technique for concatenation is not encouraged, as that functionality might not continue in future versions.
The preferred way of doing string concatenation to use FOR XML PATH instead.
select
stuff((select ', ' + t.tag from tags t where t.photoid = p.photoid order by tag for xml path('')),1,2,'') as taglist
,*
from photos
order by photoid;
For examples of how FOR XML PATH works, consider the following, imagining that you have a table with two fields called 'id' and 'name'
SELECT id, name
FROM table
order by name
FOR XML PATH('item'),root('itemlist')
;
Gives:
<itemlist><item><id>2</id><name>Aardvark</a></item><item><id>1</id><name>Zebra</name></item></itemlist>
But if you leave out the ROOT, you get something slightly different:
SELECT id, name
FROM table
order by name
FOR XML PATH('item')
;
<item><id>2</id><name>Aardvark</a></item><item><id>1</id><name>Zebra</name></item>
And if you put an empty PATH string, you get even closer to ordinary string concatenation:
SELECT id, name
FROM table
order by name
FOR XML PATH('')
;
<id>2</id><name>Aardvark</a><id>1</id><name>Zebra</name>
Now comes the really tricky bit... If you name a column starting with an # sign, it becomes an attribute, and if a column doesn't have a name (or you call it [*]), then it leaves out that tag too:
SELECT ',' + name
FROM table
order by name
FOR XML PATH('')
;
,Aardvark,Zebra
Now finally, to strip the leading comma, the STUFF command comes in. STUFF(s,x,n,s2) pulls out n characters of s, starting at position x. In their place, it puts s2. So:
SELECT STUFF('abcde',2,3,'123456');
gives:
a123456e
So now have a look at my query above for your taglist.
select
stuff((select ', ' + t.tag from tags t where t.photoid = p.photoid order by tag for xml path('')),1,2,'') as taglist
,*
from photos
order by photoid;
For each photo, I have a subquery which grabs the tags and concatenates them (in order) with a commma and a space. Then I surround that subquery in a stuff command to strip the leading comma and space.
I apologise for any typos - I haven't actually created the tables on my own machine to test this.
Rob
I'd create a UDF:
create function GetTags(PhotoID int) returns #tags varchar(max)
as
begin
declare #mytags varchar(max)
set #mytags = ''
select #mytags = #mytags + ', ' + tag from tags where photoid = #photoid
return substring(#mytags, 3, 8000)
end
Then, all you have to do is:
select GetTags(photoID) as tagList from photos
Street_Name ; Street_Code
west | 14
east | 7
west+east | 714
If want to show two different row concat itself , how can do it?
(I mean last row i want to show from select result. My table had first and secord record)