SQL Server: Select multiple records in one select statement - sql

In a query like this one:
SELECT *
FROM `Order`
WHERE `CustID` = '1'
My results are displayed like so:
| CustID| Order |
-----------------
| 1 | Order1|
| 1 | Order2|
| 1 | Order3|
-----------------
How do I write SQL statement, to get a result like this one?:
| CustID| Order |
---------------------------------
| 1 | Order1, Order2, Order3|
---------------------------------
In mySQL it's possible with Group_Concat, but in SQL Server it gives error like syntax error or some.

Use xml path (see fiddle)
SELECT distinct custid, STUFF((SELECT ',' +[order]
FROM table1 where custid = t.custid
FOR XML PATH('')), 1, 1, '')
FROM table1 t
where t.custid = 1
STUFF replaces the first , with an empty string, i.e. removes it. You need a distinct otherwise it'll have a match for all orders since the where is on custid.
FOR XML
PATH Mode
STUFF

You can use Stuff function and For xml clause like this:
SELECT DISTINCT CustId, STUFF((
SELECT ','+ [Order]
FROM [Order] T2
WHERE T2.CustId = T1.CustId
FOR XML PATH('')
), 1, 1, '')
FROM [Order] T1
fiddle here
Note: Using order as a table name or a column name is a very, very bad idea. There is a reason why they called reserved words reserved.
See this link for my favorite way to avoid such things.

try this.
Change table name and column names for what you need;
SELECT custID,
LISTAGG(Order, ', ') WITHIN GROUP (ORDER BY Order) text
FROM table_name
GROUP BY custID
edit for MSSQL . You should use group_concat function.
SELECT custID, GROUP_CONCAT(Order)
FROM table_name
WHERE CustID = 1
GROUP BY custID;

Related

Get the mentioned output by SQL query

I appeared for an interview lately. The interviewer asked me the problem.
I have 2 tables:
First table is Location like this:
ID | City
---+-----------
1 | Mumbai
2 | Delhi
3 | Bangalore
Second table is Item like this:
Item | Location_id
-----+-------------
A | 1,2
B | 2,3
C | 1,2,3
Now we want the output as below
Item | Location
-----+-------------------------
A | Mumbai,Delhi
B | Delhi,Bangalore
C | Mumbai,Delhi,Bangalore
Please help me write the query.
You can use below query . String split works on SQL server 2016 and later versions.
Select * into #temp
from #location l
join (
select item,value from #item
CROSS APPLY STRING_SPLIT(location_id, ',')
) A on l.id=a.value
select
item,
stuff((
select ',' + u.city
from #temp u
where u.item = A.item
for xml path('')
),1,1,'') as List
from #temp A
group
by item
Drop table #temp
You want to STUFF all matching results into one column:
SELECT i.item,
STUFF((SELECT ',' + l.city
FROM location l
WHERE CHARINDEX(cast(l.id AS VARCHAR(4)),i.location_id)>0
FOR XML PATH ('')), 1, 1, '')
AS Location
FROM item i;
CHARINDEX finds the first instance of a substring in a string, in this case it finds that you have the number 1 for Mumbai, 2 for Dehli etc in your string of location_id.
STUFF combines your results into a single result.
SQLFiddle
UPDATE -
As pointed out by Rajneesh, this only works because your IDs are single digit. String splitting is probably the best way to handle the possibility of such IDs. That can still be done within this one query, without the need for a temp table.
SELECT i.item,
STUFF((SELECT ',' + l.city
FROM location l
WHERE l.id IN (select value from STRING_SPLIT(i.location_id, ','))
FOR XML PATH ('')), 1, 1, '')
AS Location
FROM item i;
SQLFiddle

Combining Data From Multiple Rows

I have 3 tables I am writing a query for: Memos, Memos_Description, Policies. The database was not designed by myself and I cannot change it, this is simply a report.
I currently have a query that seems to be working, but is extremely inefficient before joining the extra tables that I need.
SELECT
Main.CLIENTSNAME,
Main.ENTRYDATE,
Main.AUTHOR,
Main.POLICYNUMBER,
Main.CLIENTS_ID,
Main.MEMOS_ID,
Left(Main.DESCRIPTION,Len(Main.DESCRIPTION)) AS REGARDING
FROM
(
SELECT distinct ST1.MEMOS_ID,
(
SELECT ST2.DESCRIPTION + ' ' AS [text()]
FROM dbo.MEMOS_DESCRIPTION ST2
WHERE ST1.MEMOS_ID = ST2.MEMOS_ID
ORDER BY ST1.MEMOS_ID
For XML PATH ('')
) [DESCRIPTION],
ST1.CLIENTSNAME,
ST1.ENTRYDATE,
ST1.AUTHOR,
ST1.POLICYNUMBER,
ST1.REGARDING,
ST1.CLIENTS_ID
FROM dbo.MEMOS ST1
) [Main]
The tables look like this:
tbl.MEMOS
MEMOS_ID
POLICIES_ID
CLIENTSNAME
tbl.MEMOS_DESCRIPTION
MEMOS_ID
DESCRIPTION
tbl.POLICIES
POLICIES_ID
POLICYNUMBER
The data looks like this:
tbl1.MEMOS_ID | tbl1.CLIENTSNAME
1 PERSON ONE
2 PERSON TWO
3 PERSON THREE
tbl2.MEMOS_ID | tbl2.DESCRIPTION
1 This is a sentence
1 that can run over more
1 than one description record.
2 Person two has
2 something different.
3 Client Created.
tbl3.POLICIES_ID | tbl3.POLICYNUMBER
123 ABCDE
456 FGHIJ
I would like the report to look like:
tbl1.MEMOS_ID | tbl1.CLIENTSNAME | tbl2.DESCRIPTION | tbl3.POLICIES_ID | tbl3.POLICYNUMBER
1 PERSON ONE This is a sentence that can run over more tan one description record. 123 ABCDE
I hope this makes sense and thank you.
Updated Query as per Gordon's suggested answer:
SELECT ST1.*,
STUFF(
(SELECT ' ' + ST2.DESCRIPTION AS [text()]
FROM dbo.MEMOS_DESCRIPTION ST2
WHERE ST1.MEMOS_ID = ST2.MEMOS_ID
ORDER BY ST1.MEMOS_ID
For XML PATH ('')
), 1, 1, '') [REGARDING]
FROM
(SELECT DISTINCT ST1.MEMOS_ID,
ST1.CLIENTSNAME,
ST1.ENTRYDATE,
ST1.AUTHOR,
ST1.POLICYNUMBER,
ST1.CLIENTS_ID,
ST1.POLICIES_ID
FROM dbo.MEMOS ST1
) ST1
LEFT JOIN POLICIES B
ON ST1.POLICIES_ID = B.POLICIES_ID
WHERE ST1.ENTRYDATE >= DATEADD(month, -2, GETDATE())
AND (B.PROD1 = ('123') OR B.PROD1 = ('456') OR B.PROD1 = ('789'))
How does the performance compare if you do the select distinct before doing the string aggregation?
SELECT ST1.*,
STUFF((SELECT ' ' + ST2.DESCRIPTION AS [text()]
FROM dbo.MEMOS_DESCRIPTION ST2
WHERE ST1.MEMOS_ID = ST2.MEMOS_ID
ORDER BY ST1.MEMOS_ID
For XML PATH ('')
), 1, 1, '') ) [DESCRIPTION]
FROM (SELECT DISTINCT T1.MEMOS_ID, ST1.CLIENTSNAME, ST1.ENTRYDATE,
ST1.AUTHOR, ST1.POLICYNUMBER, ST1.REGARDING, ST1.CLIENTS_ID
FROM dbo.MEMOS ST1
) ST1
I suspect that SQL Server might be doing the string aggregation for every row before running distinct -- and that is a lot of unnecessary work.

SQL Group column data from joins

I need some help with writing SQL query.
I've data in the following format coming from a query returned by joining multiple tables.
CustID Name AccNo Bank
------ ---- ----- -----
1 Varun 9848032919 CB
1 Varun 9998887771 COB
1 Varun 9988776655 CB
2 Lokesh 9876543210 COB
2 Lokesh 9282726252 CB
3 aaaa 9181716151 COB
I would like the data to be formatted as below so that it would be easy to load into Crystal reports.
CustID Name AccNo Bank
------ ---- ----- -----
1 Varun 9848032919,9998887771,9988776655 CB,COB,CB
2 Lokesh 9876543210,9282726252 COB,CB
3 aaaa 9181716151 COB
I've looked at other answers which suggest using STUFF, XML PATH() but they are applicable only if data comes from one table. In my scenario data is retrieved by joining multiple tables.
May I know how to group by CustID and concatenate data in other columns as I desire?
Thanks a lot for your time!!!
EDIT: I'm looking for a SQL Server 2005 version answer
You can place the query inside a CTE, then apply FOR XML PATH on this CTE like this:
;WITH CTE AS (
... your query here
)
SELECT C.CustID, MAX(Name),
STUFF((
SELECT ', ' + + CAST(AccNo AS VARCHAR(MAX))
FROM CTE
WHERE (CustID = C.CustID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS AccNo,
STUFF((
SELECT ', ' + + CAST(Bank AS VARCHAR(MAX))
FROM CTE
WHERE (CustID = C.CustID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS Bank
FROM CTE C
GROUP BY C.CustID
Wrapping the select makes it look like a simple table. You can apply the logic on the result.
Like
Select a.*
from (select t1.col1, t2.col2
from table1 t1
join table2 t2 on t1.f1=t2.c1) a

SQL: Lookup table rows into columns for reporting purposes

I have the following two table data structure for dealing with custom user fields:
[UserFieldID] [UserFieldName]
-------------------------------
1 Location
2 Color
[UserID] [UserFieldID] [UserFieldValue]
----------------------------------------
1 1 Home
1 2 Orange
2 1 Office
2 2 Red
This allows any number of fields to be defined (globally) and users to have values for those custom fields. I need to figure out how to display this information for reporting purposes as part of a pre-existing report, in the following format:
UserID ... Location Color
----------------------------------------------------
1 Home Orange
2 Office Red
I know this probably involves using either PIVOT or UNPIVOT, but try as I might, they just confuse me.
Thanks in advance
There are several different ways that you can get the result, you can use an aggregate function with a CASE expression or you can use the PIVOT function to get this. Based on your comment that any number of fields can be defined, it sounds like you will need to use dynamic SQL to get the final result. Before writing a dynamic SQL version, I would always start with a static or hard-coded version of the query, then convert it to dynamic SQL.
Besides using these methods, I would also recommend using the windowing function row_number() to generate a unique value for each combination of userid and fieldname. Since you are pivoting string values, then you have to use either the max/min aggregate function which will return only one value for each fieldname, by adding the row_number you will be able to return multiple combinations of Location, etc for each user.
If you were using an aggregate function with a CASE expression the query would be:
select
userid,
max(case when userfieldname = 'Location' then userfieldvalue end) location,
max(case when userfieldname = 'Color' then userfieldvalue end) Color
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) d
group by userid, seq
order by userid;
See SQL Fiddle with Demo
If you were using PIVOT, the hard-coded version of the query would be:
select userid, Location, Color
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) d
pivot
(
max(userfieldvalue)
for userfieldname in (Location, Color)
) p
order by userid;
See SQL Fiddle with Demo.
Once you have the correct logic you can convert the PIVOT to dynamic SQL to be executed:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(UserFieldName)
from UserFields
group by UserFieldName, userfieldId
order by userfieldid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT userid, ' + #cols + '
from
(
select v.userid,
f.userfieldname,
v.userfieldvalue,
row_number() over(partition by v.userid, v.userfieldid
order by v.userfieldid) seq
from userFields f
left join userValues v
on f.userfieldId = v.userFieldId
) x
pivot
(
max(userfieldvalue)
for userfieldname in (' + #cols + ')
) p
order by userid'
execute sp_executesql #query;
See SQL Fiddle with Demo. All versions will give a result:
| USERID | LOCATION | COLOR |
|--------|----------|--------|
| 1 | Home | Orange |
| 1 | Office | (null) |
| 2 | Office | Red |

SQL Query - Display Count & All ID's With Same Name

I'm trying to display the amount of table entries with the same name and the unique ID's associated with each of those entries.
So I have a table like so...
Table Names
------------------------------
ID Name
0 John
1 Mike
2 John
3 Mike
4 Adam
5 Mike
I would like the output to be something like:
Name | Count | IDs
---------------------
Mike 3 1,3,5
John 2 0,2
Adam 1 4
I have the following query which does this except display all the unique ID's:
select name, count(*) as ct from names group by name order by ct desc;
select name,
count(id) as ct,
group_concat(id) as IDs
from names
group by name
order by ct desc;
You can use GROUP_CONCAT for that
Depending on version of MSSQL you are using (2005+), you can use the FOR XML PATH option.
SELECT
Name,
COUNT(*) AS ct,
STUFF((SELECT ',' + CAST(ID AS varchar(MAX))
FROM names i
WHERE i.Name = n.Name FOR XML PATH(''))
, 1, 1, '') as IDs
FROM names n
GROUP BY Name
ORDER BY ct DESC
Closest thing to group_concat you'll get on MSSQL unless you use the SQLCLR option (which I have no experience doing). The STUFF function takes care of the leading comma. Also, you don't want to alias the inner SELECT as it will wrap the element you're selecting in an XML element (alias of TD causes each element to return as <TD>value</TD>).
Given the input above, here's the result I get:
Name ct IDs
Mike 3 1,3,5
John 2 0,2
Adam 1 4
EDIT: DISCLAIMER
This technique will not work as intended for string fields that could possibly contain special characters (like ampersands &, less than <, greater than >, and any number of other formatting characters). As such, this technique is most beneficial for simple integer values, although can still be used for text if you are ABSOLUTELY SURE there are no special characters that would need to be escaped. As such, read the solution posted HERE to ensure these characters get properly escaped.
Here is another SQL Server method, using recursive CTE:
Link to SQLFiddle
; with MyCTE(name,ids, name_id, seq)
as(
select name, CAST( '' AS VARCHAR(8000) ), -1, 0
from Data
group by name
union all
select d.name,
CAST( ids + CASE WHEN seq = 0 THEN '' ELSE ', ' END + cast(id as varchar) AS VARCHAR(8000) ),
CAST( id AS int),
seq + 1
from MyCTE cte
join Data d
on cte.name = d.name
where d.id > cte.name_id
)
SELECT name, ids
FROM ( SELECT name, ids,
RANK() OVER ( PARTITION BY name ORDER BY seq DESC )
FROM MyCTE ) D ( name, ids, rank )
WHERE rank = 1