SQL Query & unpivot Syntax - sql

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

Related

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

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;

SQL Select COLUMNS in a single row with inner join

Hi I am having problem with selecting my desired output in my program.
Here's the scenario:
I have 2 tables
_Users
_Mobiles
Lets say I have these fields and data in each tables:
_Users
**UserID** **Name**
1 John
2 Mark
_Mobiles
**UserID** **Mobile**
1 44897065
1 44897066
1 44897067
2 45789071
What I know is that I can use
Select a.UserID, b.Mobile
from _Users a INNER JOIN
_Mobiles b ON a.UserID = b.UserID
where UserID = 1
which will retrieve data in this format:
UserID Mobile
1 44897065
1 44897066
1 44897067
but what I want is to arrange the data into:
UserID Mobile1 Mobile2 Mobile3
1 44897066 44897065 44897065
and if another mobile for the same user is encoded, it will output as Mobile4 and so on..
I know this is strange but I want to do it for some reason :D
Is this possible and can anybody help me how to do it. Thank you so much everyone.
try this Sql query..
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX);
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(a.MobileCategory)
from
(
Select a.UserID as UserID, b.Mobile as Mobile,'Mobile'+ CAST(ROW_NUMBER() OVER( ORDER BY b.Mobile) AS VARCHAR) AS MobileCategory
FROM _Users a INNER JOIN
_Mobiles b ON a.UserID = b.UserID
WHERE a.UserID = 1
) a
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT UserID,' + #cols + ' from
(
Select a.UserID as UserID,b.Mobile as Mobile,''Mobile''+ CAST(ROW_NUMBER() OVER( ORDER BY b.Mobile) AS VARCHAR) AS MobileCategory
FROM _Users a INNER JOIN
_Mobiles b ON a.UserID = b.UserID
WHERE a.UserID = 1
) x
pivot
(
sum(Mobile)
for MobileCategory in (' + #cols + ')
) p '
execute(#query)
This could use help, but something like this
with (
select u.userid, m1.mobid, m2.mobid, .. mN.mobid
from users u
left join mobiles m1
on u.userid=m1.userid
left join mobiles m2
on u.userid=m2.userid
...
left join mobiles mN
on u.userid=mN.userid
) as mobuser
select * from mobuser
where ( m2.mobid>m1.mobid or m2.mobid is null)
and ( m3.mobid>m2.mobid or m3.mobid is null)
...
and (mN.mobid>mNMinus1.mobid or mN.mobid is null)

How to create query in sql to pivot data? [duplicate]

This question already has answers here:
Get ROWS as COLUMNS (SQL Server dynamic PIVOT query)
(2 answers)
Closed 9 years ago.
I have two tables named PRODUCT and DETAIL
TABLE: PRODUCT
slno product
1 x
2 y
3 z
TABLE: DETAIL
product detail
x good
y bad
z worse
x bad
I need to get output as
TABLE
X Y Z
good bad worse
bad
This data transformation is known as a PIVOT and starting in SQL Server 2005 there is a function to convert the data from rows to columns.
There are several ways that this can be done depending on whether or not you have a static number of values to transpose into columns. All of them involve adding a row_number() to the data so you can return the multiple rows of any of the products.
You can use an aggregate function with a CASE expression:
select
max(case when product = 'x' then detail end) x,
max(case when product = 'y' then detail end) y,
max(case when product = 'z' then detail end) z
from
(
select p.product, d.detail,
row_number() over(partition by p.product order by p.slno) rn
from product p
inner join detail d
on p.product = d.product
) src
group by rn
See SQL Fiddle with Demo
You can use the PIVOT function:
select x, y, z
from
(
select p.product, d.detail,
row_number() over(partition by p.product order by p.slno) rn
from product p
inner join detail d
on p.product = d.product
) src
pivot
(
max(detail)
for product in (x, y, z)
) piv
See SQL Fiddle with Demo.
If you have an unknown number of values (products in this case) to turn into columns, then you will want to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(product)
from product
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' from
(
select p.product, d.detail,
row_number() over(partition by p.product order by p.slno) rn
from product p
inner join detail d
on p.product = d.product
) x
pivot
(
max(detail)
for product in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result of all of the queries is:
| X | Y | Z |
--------------------------
| good | bad | worse |
| bad | (null) | (null) |
This is your query.
select p.product, d.detail from product p
inner join detail d on p.product = d.product
Look into the SQL Join statements. This blogpost explains it nicely.
select d.product, d.detail from detail d
join product p on d.product = p.product

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