SQL Server mulitple results to one row by ID (Version 2012) - sql

Consider this query and result set:
Select udbA.userId, d.dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
Order By udbA.userId
1 az
1 nc_west
1 bsc_mo
1 NS_002
What I am looking for is a way to flatten this into one record. I know I can do it with a temp table and select into, but I was curious to see if a query could do it directly. A user could have up to 15 databases available to them.
Looking for results like below ( 2 columns -- userid and the database names ):
userid dbname
1 az nc_west bsc_mo NS_002
SQL Server Version: Microsoft SQL Server 2012 (SP3) (KB3072779) - 11.0.6020.0 (X64) Oct 20 2015 15:36:27 Copyright (c) Microsoft Corporation Enterprise Edition (64-bit) on Windows NT 6.3 (Build 9600: ) (Hypervisor)

Assuming you want a space-delimited list of database names:
DECLARE #Access table ( userId int, dbName varchar(50) );
INSERT INTO #Access VALUES
( 1, 'az' ), ( 1, 'nc_west' ), ( 1, 'bsc_mo' ), ( 1, 'NS_002' );
SELECT DISTINCT
ax.userId, db.list
FROM #Access AS ax
OUTER APPLY (
SELECT LTRIM ( (
SELECT ' ' + dbName AS "text()" FROM #Access AS x WHERE x.userId = ax.userId
FOR XML PATH ( '' )
) ) AS list
) AS db;
Returns
+--------+--------------------------+
| userId | list |
+--------+--------------------------+
| 1 | az nc_west bsc_mo NS_002 |
+--------+--------------------------+
For a comma-delimited list:
SELECT DISTINCT
ax.userId, db.list
FROM #Access AS ax
OUTER APPLY (
SELECT STUFF ( (
SELECT ',' + dbName AS "text()" FROM #Access AS x WHERE x.userId = ax.userId
FOR XML PATH ( '' )
), 1, 1, '' ) AS list
) AS db;
Returns
+--------+--------------------------+
| userId | list |
+--------+--------------------------+
| 1 | az,nc_west,bsc_mo,NS_002 |
+--------+--------------------------+

You need GROUP BY and row_number as follows:
select userId,
max(case when rn = 1 then dbName end) as val1,
max(case when rn = 2 then dbName end) as val2,
max(case when rn = 3 then dbName end) as val3,
max(case when rn = 4 then dbName end) as val4
from
(Select udbA.userId, d.dbName,
row_number() over (partition by udbA.userId order by d.dbName) as rn
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid ) t
group by userId
-- update
You just need two columns then use the STRING_AGG as follows:
Select udbA.userId,
string_agg(d.dbName, ' ') within group (order by d.dbName) as dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
group by udbA,userId
-- For SQL server 2012
Select distinct udbA.userId,
STUFF((SELECT distinct '' + d.dbName
from dbList d
where d.dbid = udbA.dbid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,0,'') AS dbid
FROM user_db_access udbA
WHERE EXISTS (SELECT 1 FROM dbList d
where d.dbid = udbA.dbid)
Or use CTE as follows:
with CTE as
(Select udbA.userId, d.dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid)
Select distinct c.userId,
STUFF((SELECT distinct '' + cc.dbName
from cte cc
where c.dbid = cc.dbid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,0,'') AS dbid
FROM cte c

Depending on your SQL Server version you can use STRING_AGG
Like this:
Select udbA.userId, string_agg(d.dbName, ', ') as name
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
group by udbA.userId
If you are using an older version you already have answers here: How to make a query with group_concat in sql server
Like this:
select
udbA.userId,
stuff((
select cast(',' as varchar(max)) + d.dbName
from dbList d
where d.dbid = udbA.dbid
order by d.dbName
for xml path('')), 1, 1, '') as dbList
from
user_db_access udbA
inner join dbList dl on dl.dbid = udbA.dbid
order by
udbA.userId;

Related

(Group By) & (FOR XML PATH) & JOIN

Is there any other approch rather than saving the result of joining (the Table #VT & TableA ) in one Table variable and then from there make the Grouping
select ID ,
STUFF (( select distinct ' / ' + TA.Reason
from #VT
where ( ID = VT.ID )
for xml path(''),TYPE).value('(./text())[1]','VARCHAR(MAX)') ,1,2,'' ) as XXX
from #VT as VT
join TableA as TA on ( TA.ID = VT.ID)
group by ID
I get this Error
Column 'TableA.Reason' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I would go with DISTINCT instead of GROUP BY :
SELECT DISTINCT ID,
STUFF (( SELECT DISTINCT ' / ' + ta.Reason
FROM TableA ta
WHERE vt.ID = ta.ID
FOR XML PATH (''),TYPE).VALUE('(./text())[1]','VARCHAR(MAX)'
), 1, 2, ''
) AS XXX
FROM #VT vt;
I think you have the tables in the wrong place. Does this work?
select ID ,
stuff(( select distinct ' / ' + TA.Reason
from TableA TA
where vt.ID = ta.ID
for xml path(''), TYPE
).value('(./text()) [1]', 'VARCHAR(MAX)') , 1, 2, ''
) as XXX
from #VT vt ;

Convert a column of Rows to only one column column in SQL Server query

I have this query in SQL Server 2012 :
select
tblplantitle.id, tblplantoproductpayment.productid
from
tblplantitle
inner join
tblplan on tblplan.plantitleid = tblplantitle.id
inner join
tblplantoproductpayment on tblplantoproductpayment.planid = tblplan.id
group by
tblplantitle.id, tblplantoproductpayment.productid
The result is like this :
id productid
1 1
1 2
1 3
1 10
2 5
2 1
3 4
3 11
but I want this result :
1 1,2,3,10
2 5,1
3 4,11
How can I get that result ?
Try this:
WITH cte as
(
select
tblplantitle.id, tblplantoproductpayment.productid
from
tblplantitle
inner join
tblplan on tblplan.plantitleid = tblplantitle.id
inner join
tblplantoproductpayment on tblplantoproductpayment.planid = tblplan.id
group by
tblplantitle.id, tblplantoproductpayment.productid
)
SELECT id, productid =
STUFF((SELECT ', ' + productid
FROM cte b
WHERE b.id= a.id
FOR XML PATH('')), 1, 2, '')
FROM cte a
GROUP BY id
Use the below query for the desired output.
SELECT tblplantitle.id
, STUFF((SELECT ', ' + CAST(tblplantoproductpayment.productid AS VARCHAR(10)) [text()]
FROM tblplantoproductpayment
WHERE tblplantoproductpayment.planid = tblplan.id
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,' ') List_ProductIds
FROM tblplantitle
INNER JOIN tblplan on tblplan.plantitleid = tblplantitle.id
GROUP BY tblplantitle.id

SQL Query & unpivot Syntax

I have a permission system that defines users access to certain items.
I need a report page to show members and their access to the items.
The table needs to be like this:
+---------+--------+--------+------+--------+
| Members | Item 1 | Item 2 | .... | Item N |
+---------+--------+--------+------+--------+
| Member1 | x | + | .... | + |
+---------+--------+--------+------+--------+
| Member2 | + | x | .... | + |
+---------+--------+--------+------+--------+
I have a 3 table joined query for listing the members and the items they can access but I could not convert it into pivot syntax.
Select members.id memberID, members.name memberName, items.name as item
From members
Left Join member_permission On memberID = members.id
Left Join items On items.id = member_permission.itemID
This is the not working query I have:
Select memberName, itemName
From (
select members.id, members.name, item.name as itemName
from members
Left Join member_permission On memberID = members.id
Left Join item On item.id = member_permission.itemID
) p
Unpivot
(itemName For name IN (item.name)) as unp
and the error I receive:
The column name "name" specified in the UNPIVOT operator conflicts with the existing column name in the UNPIVOT argument.
I created an example to play with Sqlfiddle
You don't need to use UNPIVOT for this query. UNPIVOT is used to convert multiple columns into multiple rows. You only need to apply the PIVOT function to turn your items into columns.
I would first suggest using a windowing function like row_number() to create your new column headers, then apply the PIVOT function:
select id, name, Item1, Item2, Item3
from
(
select members.id, members.name, items.name as item,
'item'+
cast(row_number() over(partition by members.id
order by members.id) as varchar(10)) col
from members
Left Join member_permission
On memberID = members.id
Left Join items
On items.id = member_permission.itemID
) d
pivot
(
max(item)
for col in (Item1, Item2, Item3)
) piv;
See SQL Fiddle with Demo. Then if you have an unknown number of values your query would need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME('item'+cast(seq as varchar(10)))
from
(
select row_number() over(partition by memberid
order by memberid) seq
from member_permission
) d
group by seq
order by seq
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = N'SELECT id, name, ' + #cols + N'
from
(
select members.id, members.name, items.name as item,
''item''+
cast(row_number() over(partition by members.id
order by members.id) as varchar(10)) col
from members
Left Join member_permission
On memberID = members.id
Left Join items
On items.id = member_permission.itemID
) x
pivot
(
max(item)
for col in (' + #cols + N')
) p '
execute sp_executesql #query;
See SQL Fiddle with Demo

sql comma delimited list of a column in analytical function

I am using SQL server 2008, and I need to make a common delimeted list of a column. I know how to do it, but I need this time while I use analytical function, I mean I don't want to use group by clause.
Since I will also select the records in outer query "where row_num=1"
Here is the query:
SELECT UserId
,ProductList
,Value
FROM
(
SELECT p.UserId
,p.Value
, ROW_NUMBER()OVER (PARTITION BY p.UserId ORDER BY p.RecordCreateDate asc) AS 'row_num'
--here I need a solution OVER (PARTITION BY p.UserId) AS 'ProductList'
FROM Products p
INNER JOIN
Users u
ON p.UserId = u.Id
) result
WHERE result.row_num = 1
Users data:
Id Name ....
1 John
2 Anton
3 Craig
Products data:
Id UserId Name RecordCreateDate Value
1 1 a 21.12.2012 10
2 1 b 11.12.2012 20
3 1 c 01.12.2012 30
4 2 e 05.12.2012 40
5 2 f 17.12.2012 50
6 3 d 21.12.2012 60
7 3 i 31.12.2012 70
I need a result such as:
UserId ProductList Value
1 a,b,c 30
2 e,f 40
3 d,i 60
Thanks for your help
Since you are going to filter on row_num = 1 you need to put your query in a CTE or the likes where you include the extra columns from Products. Then you can build your comma separated string in the outer query using the for XML path trick without using group by.
;WITH C as
(
SELECT p.UserId
, ROW_NUMBER()OVER (PARTITION BY p.UserId ORDER BY p.RecordCreateDate asc) AS 'row_num'
--, Some other fields from Products
FROM Products p
INNER JOIN
Users u
ON p.UserId = u.Id
)
SELECT UserId,
--, Some other fields from Products
--, Build the concatenated list here using for xml path()
FROM C
WHERE C.row_num = 1
Just for completeness. Remove the # symbols for your actual solution.
SET NOCOUNT ON;
CREATE TABLE #users
(
Id INT,
Name VARCHAR(32)
);
INSERT #users VALUES
(1,'John'),
(2,'Anton'),
(3,'Craig');
CREATE TABLE #products
(
Id INT,
UserId INT,
Name VARCHAR(32),
RecordCreateDate DATE,
Value INT
);
INSERT #products VALUES
(1,1,'a','2012-12-21',10),
(2,1,'b','2012-12-11',20),
(3,1,'c','2012-12-01',30),
(4,2,'e','2012-12-05',40),
(5,2,'f','2012-12-17',50),
(6,3,'d','2012-12-21',60),
(7,3,'i','2012-12-31',70);
The query:
;WITH x AS
(
SELECT UserId, Value,
row_num = ROW_NUMBER() OVER
(
PARTITION BY UserId
ORDER BY RecordCreateDate
)
FROM #products
)
SELECT
x.UserId,
u.Name,
ProductList = STUFF((
SELECT ',' + Name
FROM #Products AS p
WHERE p.UserId = x.UserId
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'varchar(max)'),1,1,''),
x.Value
FROM x
INNER JOIN #users AS u
ON x.UserId = u.Id
WHERE x.row_num = 1;
Then clean up:
DROP TABLE #users, #products;
Results:
UserId Name ProductList Value
1 John a,b,c 30
2 Anton e,f 40
3 Craig d,i 60
I'm not sure what you're asking in the beginning, but this will give you the requested output
SELECT UserId,
STUFF((SELECT ',' + ProductName from Products p WHERE p.UserID = u.UserID FOR XML PATH('')), 1, 1, '') as ProductList
FROM Users u

How to get table-like query result on SQL Server 2005/8?

I have 3 tables:
users (id, name)
currency (id, name)
accounts (id, user_id, currency_id, amount)
And I want to read the data from accounts and present it in table-like view:
owner currency1 currency2 currency3
1 0 0 0
2 10 20 30
3 0 5 10
Where owner is ID of accounts.owner, currency1,2,3 - (SELECT id FROM currency WHERE name = '1',etc)
I can get such result only for one specific ID:
SELECT
SELECT amount FROM accounts WHERE currency = (SELECT id FROM currency WHERE name = 'currency1') AND owner = #user) AS [currency1],
SELECT amount FROM accounts WHERE currency = (SELECT id FROM currency WHERE name = 'currency2') AND owner = #user) AS [currency2],
SELECT amount FROM accounts WHERE currency = (SELECT id FROM currency WHERE name = 'currency2') AND owner = #user) AS [currency2]
Is it possible to get the same result for every object in users table? Without using Reporing Service, etc.
Use a pivot table and dynamic SQL to retrieve the columns
DECLARE #columns VARCHAR(2000)
SELECT #columns = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + c.name
FROM currency AS c
ORDER BY '],[' + c.name
FOR XML PATH('')
), 1, 2, '') + ']'
DECLARE #query NVARCHAR(4000)
SET #query = N'SELECT UserName, ' + #columns +
'FROM
(SELECT u.Name AS UserName, c.name AS CurrencyName, a.Amount
FROM Accounts AS a WITH(NOLOCK)
JOIN Users u WITH(NOLOCK) ON a.user_id = u.user_id
JOIN Currency c WITH(NOLOCK) ON a.currency_id = c.currency_id
) p
PIVOT
(
SUM (p.Amount)
FOR p.CurrencyName IN
( '+ #columns +')
) AS pvt
ORDER BY UserName'
EXECUTE(#query)
This was tested in SQL Server 2005
Sounds like you want a Pivot table. It will be difficult to do if you have a varying number of rows in currency, but could still be done by using dynamiclly written sql.
Here's a resource from MSDN that explains how to use the pivot table: http://msdn.microsoft.com/en-us/library/ms177410.aspx
SELECT u.name, [1] AS Currency1, [2] AS Currency2, [3] AS Currency3
FROM
(SELECT u.Name AS UserName, c.Currency_ID, a.Amount
FROM Accounts AS a WITH(NOLOCK)
JOIN Users u WITH(NOLOCK) ON a.user_id = u.user_id
) p
PIVOT
(
SUM (p.Amount)
FOR p.Currency_id IN
( [1], [2], [3] )
) AS pvt
ORDER BY pvt.UserName