How can join table with IN() in ON couse? - sql

I have two table
User
id | name | category
1 | test | [2,4]
Category
id | name
1 | first
2 | second
3 | third
4 | fourth
now i need to join this both table and get data like:
name | category
test | second, fourth
i tried like:
select u.name as name, c.name as category
from user
INNER JOIN category on(c.id in (u.category))
but it's not working.

As others have suggested, if you have any control whatsoever over the design of this database, don't store multiple values in user.category, but instead have a bridging table between the two which maps one or more category values to each user record.
However, if you are not in a position to be able to redesign the database, here's a way to get the result you're looking for. First, let's create some test data:
create table [user]
(
id int,
[name] varchar(50),
category varchar(50) -- I'm assuming this is a string type
)
create table category
(
id int,
[name] varchar(50)
)
insert into [user] values
(1,'test','[2,4]'),
(2,'another test','[1,2,4]'),
(3,'more test','[1,3,2,4]')
insert into category values
(1,'first'),
(2,'second'),
(3,'third'),
(4,'fourth');
Then you can use a CTE with split_string to pull apart the individual category values, join them to their names, then recombine them into a single comma-separated value with for xml:
with r as
(
select
u.[name] as username,
cat.id,
cat.[name] as categoryname
from [user] u
outer apply
(
select value from string_split(substring(u.category,2,len(u.category)-2),',')
) c
left join category cat on c.value = cat.id
)
select
r.username,
stuff(
(select ',' + categoryname
from r r2
where r.username = r2.username
order by r2.id
for xml path ('')), 1, 1, '') as categories
from r
group by r.username
which gives the desired output:
/-----------------------------------------\
| username | categories |
|-------------|---------------------------|
|another test | first,second,fourth |
|more test | first,second,third,fourth |
|test | second,fourth |
\-----------------------------------------/
I'm making a couple of assumptions here:
You're using MS SQL Server
The category values always begin with [, end with ] and contain nothing but a comma-delimited string containing value category ids

Related

SQL get top level object from joins

Working on a query right now where we want to understand which business is referring the most downstream orders for us. I've put together a very basic table for demonstration purposes here with 4 businesses listed. Bar and Donut were both ultimately referred by Foo and I want to be able to show Foo as a business has generated X number of orders. Obviously getting the the single referral for Foo (from Bar) and Bar (from Donut) are simple joins. But how do you go from Bar to get back to Foo?
I'll add that I've done some more googling this AM and found a few very similar questions about the top level parent and most of the responses suggest recursive CTE. It's been awhile since I've dug deep into SQL stuff, but 8 years ago I know these were not overly popular. Is there another way around this? Perhaps better to just store that parent ID on the order table at the time of order?
+----+--------+--------------------+
| Id | Name | ReferralBusinessId |
+----+--------+--------------------+
| 1 | Foo | |
| 2 | Bar | 1 |
| 3 | Donut | 2 |
| 4 | Coffee | |
+----+--------+--------------------+
WITH RECURSIVE entity_hierarchy AS (
SELECT id, name, parent FROM entities WHERE name = 'Donut'
UNION
SELECT e.id, e.name, e.parent FROM entities e INNER JOIN entity_hierarchy eh on e.id = eh.parent
)
SELECT id, name, parent FROM entity_hierarchy;
SQL Fiddle Example
Assuming you're using SQL Server, you could use a query like the one below to generate a hierarchical Id path for a particular business.
declare #tbl as table (Id int, Name varchar(30), ReferralBusinessId int)
insert into #tbl (id, Name, ReferralBusinessId) values
(1, 'Foo', null),
(2, 'Bar', 1),
(3, 'Donut', 2),
(4, 'Coffee', null);
;WITH business AS (
SELECT Id, Name, ReferralBusinessId
, 0 AS Level
, CAST(Id AS VARCHAR(255)) AS Path
FROM #tbl
UNION ALL
SELECT R.Id, R.Name, R.ReferralBusinessId
, Level + 1
, CAST(Path + '.' + CAST(R.Id AS VARCHAR(255)) AS VARCHAR(255))
FROM #tbl R
INNER JOIN business b ON b.Id = R.ReferralBusinessId
)
SELECT * FROM business ORDER BY Path

Recursively get nested URLs from database

I have a Database table structured with nested URLs, using ParentID and ID to tell which piece of an URL belongs where.
Table structure looks like this:
+-----+----------+------------+-------------+
| ID | ParentID | Name | Url |
+-----+----------+------------+-------------+
| 1 | 0 | Categories | categories |
| 34 | 1 | Movies | movies |
| 281 | 34 | Star Wars | star-wars |
| 33 | 1 | Books | a-good-book |
+-----+----------+------------+-------------+
What I want to do is that I want to be able to recursively go through all of the fields, and according to the ParentID, save all the possible url combinations.
So, from the table above, I'd like to get the following output:
mysite.com/categories
mysite.com/categories/movies
mysite.com/categories/movies/star-wars
mysite.com/categories/books
mysite.com/categories/books/a-good-book
I've started writing a CTE, looking like this:
WITH CategoriesCTE AS
(
SELECT
Name,
Url,
ParentID,
ID
FROM myDB
WHERE ParentID = 1
UNION ALL
SELECT
a.Name,
a.Url,
a.ParentID,
a.ID
FROM myDB.a
INNER JOIN CategoriesCTE s on a.ParentID = s.ID
)
SELECT * FROM CategoriesCTE
Thing is, this database call saves everything flat. What I would have to do, is that for EACH step, save all urls, and then for each ID, save the url according to what the ParentID is. Right now it of course isn't formatted but my output is flatly something like:
mysite.com/categories
mysite.com/movies
mysite.com/star-wars
mysite.com/a-good-book
Which creates a lot of broken links.
Is there some way to do an action/select for each recursive step? How should I be approaching this problem?
Add a few of new fields to your recursive CTE to track:
Depth of recursion (so you can find the record with the greatest depth
The path which will be built through each iteration by concatenating the latest value to it.
The starting point of the recursion so you know what record you started with
WITH CategoriesCTE AS
(
SELECT Name, Url, ParentID, ID, 1 as depth, CAST(url as VARCHAR(500)) as path, url as startingpoint
FROM myDB
WHERE ParentID = 1
UNION ALL
SELECT a.Name, a.Url, a.ParentID, a.ID, s.depth + 1, a.url + s.path, s.url
FROM myDB.a
INNER JOIN CategoriesCTE s on a.ParentID = s.ID
)
SELECT * FROM CategoriesCTE
See what you think of this...
IF OBJECT_ID('tempdb..#SomeTable', 'U') IS NOT NULL
DROP TABLE #SomeTable;
CREATE TABLE #SomeTable (
ID INT NOT NULL,
ParentID INT NOT NULL,
FolderName VARCHAR(20) NOT NULL,
UrlPath VARCHAR(8000) NULL
);
INSERT #SomeTable (ID, ParentID, FolderName) VALUES
(1 , 0 , 'categories'),
(34 , 1 , 'movies'),
(281, 34, 'star-wars'),
(33 , 1 , 'a-good-book');
-- SELECT * FROM #SomeTable st;
WITH
cte_Categories AS (
SELECT
SitePath = CAST(CONCAT('mysite.com/', st.FolderName) AS VARCHAR(8000)),
st.ID,
NodeLevel = 1
FROM
#SomeTable st
WHERE
st.ParentID = 0
UNION ALL
SELECT
SitePath = CAST(CONCAT(c.SitePath, '/', st.FolderName) AS VARCHAR(8000)),
st.ID,
nodeLevel = c.NodeLevel + 1
FROM
cte_Categories c
JOIN #SomeTable st
ON c.ID = st.ParentID
)
SELECT
c.SitePath,
c.ID,
c.NodeLevel
FROM
cte_Categories c;

SQL SELECT with JOINS and Multiple Rows for one Column

The issue at hand is that I have a basic query (from a document software database), taking from multiple tables with some LEFT JOINS, but I have one column in question that needs to take from a table where there are multiple results per unique document id (DocGUID).
CURRENT QUERY
SELECT doc.[Doc #]
, '' AS 'Authors'
, ud.[Lead Author]
, doc.[Title]
, ud.[Publication]
, ud.[Citation]
, ud.[Year]
, ud.[Month]
, ud.[Comments]
, notes.[Note]
FROM [tblDocuments] doc
LEFT JOIN [tblNotes] notes ON notes.[DocGUID] = doc.[DocGUID]
LEFT JOIN [tblUserData] ud ON ud.[MasterGUID] = doc.[DocGUID]
WHERE doc.[DocGUID] = '12345678'
As you can see, I have simply queried '' for "Authors". Here's where my issue comes in. I have a table named tblMultiValues where there are two or more authors listed per DocGUID.
Table Example: (for tblMultiValues)
|------|-------------|-------------|-------------------|
| Id | DocGUID | FieldName | Value |
|------|-------------|-------------|-------------------|
| 123 | 12345678 | Authors | Collins, Nick |
| 456 | 12345678 | Authors | Williams, Robert |
| 321 | 87654321 | Authors | Smith, Kate |
| 654 | 87654321 | Authors | Hanks, Tom |
|------|-------------|-------------|-------------------|
So, what I want to show for the 2nd column of 'Authors', is the result of:
Collins, Nick; Williams, Robert
Specifically for DocGUID of '12345678'
How might one go about doing this, mixed in with the query that is already built?
(I hope this was enough info... if more is needed, please advise).
-Nick
:::EDIT:::
I was able to get things running with the following code... (very well guided from the answer given by #mohan111
SELECT DISTINCT
STUFF((
SELECT '; ' + mv2.Value
FROM [dbo].[tblMultiValues] mv2
WHERE mv1.DocGUID = mv2.DocGUID
FOR XML PATH ('')),1,2,'') AS 'Authors', mv1.FieldName, mv1.DocGUID
INTO #TempMultival
FROM [dbo].[tblMultiValues] mv1
SELECT doc.[Doc #]
, tmv.[Authors]
, ud.[Lead Author]
, doc.[Title]
, ud.[Publication]
, ud.[Citation]
, ud.[Year]
, ud.[Month]
, ud.[Comments]
, notes.[Note]
FROM [tblDocuments] doc
LEFT JOIN [tblNotes] notes ON notes.[DocGUID] = doc.[DocGUID]
LEFT JOIN [tblUserData] ud ON ud.[MasterGUID] = doc.[DocGUID]
LEFT JOIN #TempMultiVal tmv ON tmv.DoCGUID = doc.[DocGUID]
DROP TABLE #TempMultiVal
Declare #table TABLE
(
Id INT,
DocGUID int,
FieldName VARCHAR(25),
Value VARCHAR(200)
);
INSERT INTO #table
( Id,
DocGUID,
FieldName,
Value
)
VALUES
(123,12345678,'Authors','Collins, Nick'),
(456,12345678,'Authors','Williams, Robert'),
(321,87654321,'Authors','Smith, Kate'),
(654,87654321,'Authors','Hanks, Tom');
Select distinct DocGUID,
(SELECT
Substring((SELECT ', ' + CAST(i.id AS VARCHAR(1024))
FROM
#table i
WHERE i.DocGUID = tt.DocGUID
ORDER BY i.id
FOR XML PATH('')), 3, 10000000) AS list) AS ID,
FieldName,
STUFF((Select distinct t.Value + ','
from #table t
where t.DocGUID = tt.DocGUID
FOR XML PATH(''),TYPE).value('.', 'NVARCHAR(MAX)')
, 1, 0, ' ') from #table tt

Select query to get all data from junction table to one field

I have 2 tables and 1 junction table:
table 1 (Log): | Id | Title | Date | ...
table 2 (Category): | Id | Title | ...
junction table between table 1 and 2:
LogCategory: | Id | LogId | CategoryId
now, I want a sql query to get all logs with all categories title in one field,
something like this:
LogId, LogTitle, ..., Categories(that contains all category title assigned to this log id)
can any one help me solve this? thanks
Try this code:
DECLARE #results TABLE
(
idLog int,
LogTitle varchar(20),
idCategory int,
CategoryTitle varchar(20)
)
INSERT INTO #results
SELECT l.idLog, l.LogTitle, c.idCategory, c.CategoryTitle
FROM
LogCategory lc
INNER JOIN Log l
ON lc.IdLog = l.IdLog
INNER JOIN Category c
ON lc.IdCategory = c.IdCategory
SELECT DISTINCT
idLog,
LogTitle,
STUFF (
(SELECT ', ' + r1.CategoryTitle
FROM #results r1
WHERE r1.idLog = r2.idLog
ORDER BY r1.idLog
FOR XML PATH ('')
), 1, 2, '')
FROM
#results r2
Here you have a simple SQL Fiddle example
I'm sure this query can be written using only one select, but this way it is readable and I can explain what the code does.
The first select takes all Log - Category matches into a table variable.
The second part uses FOR XML to select the category names and return the result in an XML instead of in a table. by using FOR XML PATH ('') and placing a ', ' in the select, all the XML tags are removed from the result.
And finally, the STUFF instruction replaces the initial ', ' characters of every row and writes an empty string instead, this way the string formatting is correct.

SQL query assistance with bridge table

I'm working with a existing database and trying to write a sql query to get out all the account information including permission levels. This is for a security audit. We want to dump all of this information out in a readible fashion to make it easy to compare. My problem is that there is a bridge/link table for the permissions so there are multiple records per user. I want to get back results with all the permission for one user on one line. Here is an example:
Table_User:
UserId UserName
1 John
2 Joe
3 James
Table_UserPermissions:
UserId PermissionId Rights
1 10 1
1 11 2
1 12 3
2 11 2
2 12 3
3 10 2
PermissionID links to a table with the name of the Permission and what it does. Right is like 1 = view, 2 = modify, and etc.
What I get back from a basic query for User 1 is:
UserId UserName PermissionId Rights
1 John 10 1
1 John 11 2
1 John 12 3
What I would like something like this:
UserId UserName Permission1 Rights1 Permission2 Right2 Permission3 Right3
1 John 10 1 11 2 12 3
Ideally I would like this for all users.
The closest thing I've found is the Pivot function in SQL Server 2005.
Link
The problem with this from what I can tell is that I need to name each column for each user and I'm not sure how to get the rights level. With real data I have about 130 users and 40 different permissions.
Is there another way with just sql that I can do this?
You could do something like this:
select userid, username
, max(case when permissionid=10 then rights end) as permission10_rights
, max(case when permissionid=11 then rights end) as permission11_rights
, max(case when permissionid=12 then rights end) as permission12_rights
from userpermissions
group by userid, username;
You have to explicitly add a similar max(...) column for each permissionid.
If you where using MySQL I would suggest you use group_concat() like below.
select UserId, UserName,
group_concat(PermissionId) as PermIdList,
group_concat(Rights SEPARATOR ',') as RightsList
from Table_user join Table_UserPermissions on
Table_User.UserId = Table_UserPermissions.UserId=
GROUP BY Table_User.UserId
This would return
UserId UserName PermIdList RightsList
1 John 10,11,12 1,2,3
A quick google search for 'mssql group_concat' revealed a couple different stored procedures (I), (II) for MSSQL that can achieve the same behavior.
Short answer:
No.
You can't dynamically add columns in to your query.
Remember, SQL is a set based language. You query sets and join sets together.
What you're digging out is a recursive list and requiring that the list be strung together horizontally rather then vertically.
You can, sorta, fake it, with a set of self joins, but in order to do that, you have to know all possible permissions before you write the query...which is what the other suggestions have proposed.
You can also pull the recordset back into a different language and then iterate through that to generate the proper columns.
Something like:
SELECT Table_User.userID, userName, permissionid, rights
FROM Table_User
LEFT JOIN Table_UserPermissions ON Table_User.userID =Table_UserPermissions.userID
ORDER BY userName
And then display all the permissions for each user using something like (Python):
userID = recordset[0][0]
userName = recordset[0][1]
for row in recordset:
if userID != row[0]:
printUserPermissions(username, user_permissions)
user_permissions = []
username = row[1]
userID = row[0]
user_permissions.append((row[2], row[3]))
printUserPermissions(username, user_permissions)
You could create a temporary table_flatuserpermissions of:
UserID
PermissionID1
Rights1
PermissionID2
Rights2
...etc to as many permission/right combinations as you need
Insert records to this table from Table_user with all permission & rights fields null.
Update records on this table from table_userpermissions - first record insert and set PermissionID1 & Rights1, Second record for a user update PermissionsID2 & Rights2, etc.
Then you query this table to generate your report.
Personally, I'd just stick with the UserId, UserName, PermissionID, Rights columns you have now.
Maybe substitute in some text for PermissionID and Rights instead of the numeric values.
Maybe sort the table by PermissionID, User instead of User, PermissionID so the auditor could check the users on each permission type.
If it's acceptable, a strategy I've used, both for designing and/or implementation, is to dump the query unpivoted into either Excel or Access. Both have much friendlier UIs for pivoting data, and a lot more people are comfortable in that environment.
Once you have a design you like, then it's easier to think about how to duplicate it in TSQL.
It seems like the pivot function was designed for situations where you can use an aggregate function on one of the fields. Like if I wanted to know how much revenue each sales person made for company x. I could sum up the price field from a sales table. I would then get the sales person and how much revenue in sales they have. For the permissions though it doesn't make sense to sum/count/etc up the permissionId field or the Rights field.
You may want to look at the following example on creating cross-tab queries in SQL:
http://www.databasejournal.com/features/mssql/article.php/3521101/Cross-Tab-reports-in-SQL-Server-2005.htm
It looks like there are new operations that were included as part of SQL Server 2005 called PIVOT and UNPIVOT
For this type of data transformation you will need to perform both an UNPIVOT and then a PIVOT of the data. If you know the values that you want to transform, then you can hard-code the query using a static pivot, otherwise you can use dynamic sql.
Create tables:
CREATE TABLE Table_User
([UserId] int, [UserName] varchar(5))
;
INSERT INTO Table_User
([UserId], [UserName])
VALUES
(1, 'John'),
(2, 'Joe'),
(3, 'James')
;
CREATE TABLE Table_UserPermissions
([UserId] int, [PermissionId] int, [Rights] int)
;
INSERT INTO Table_UserPermissions
([UserId], [PermissionId], [Rights])
VALUES
(1, 10, 1),
(1, 11, 2),
(1, 12, 3),
(2, 11, 2),
(2, 12, 3),
(3, 10, 2)
;
Static PIVOT:
select *
from
(
select userid,
username,
value,
col + '_'+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) src
pivot
(
max(value)
for col in (permissionid_1, rights_1,
permissionid_2, rights_2,
permissionid_3, rights_3)
) piv
order by userid
See SQL Fiddle with Demo
Dynamic PIVOT:
If you have an unknown number of permissionids and rights, then you can use dynamic sql:
DECLARE
#query AS NVARCHAR(MAX),
#colsPivot as NVARCHAR(MAX)
select #colsPivot = STUFF((SELECT ','
+ quotename(c.name +'_'+ cast(t.rn as varchar(10)))
from
(
select row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) t
cross apply sys.columns as C
where C.object_id = object_id('Table_UserPermissions') and
C.name not in ('UserId')
group by c.name, t.rn
order by t.rn
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select *
from
(
select userid,
username,
value,
col + ''_''+ cast(rn as varchar(10)) col
from
(
select u.userid,
u.username,
p.permissionid,
p.rights,
row_number() over(partition by u.userid
order by p.permissionid, p.rights) rn
from table_user u
left join Table_UserPermissions p
on u.userid = p.userid
) src
unpivot
(
value
for col in (permissionid, rights)
) unpiv
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p
order by userid'
exec(#query)
See SQL Fiddle with demo
The result for both is:
| USERID | USERNAME | PERMISSIONID_1 | RIGHTS_1 | PERMISSIONID_2 | RIGHTS_2 | PERMISSIONID_3 | RIGHTS_3 |
---------------------------------------------------------------------------------------------------------
| 1 | John | 10 | 1 | 11 | 2 | 12 | 3 |
| 2 | Joe | 11 | 2 | 12 | 3 | (null) | (null) |
| 3 | James | 10 | 2 | (null) | (null) | (null) | (null) |