I have the following simple set of SQL Server tables for my 'Walks' DB:
CREATE TABLE [USERS] (
[USERID] INT NOT NULL IDENTITY,
[USERNAME] NVARCHAR(50),
PRIMARY KEY ([USERID])
)
CREATE TABLE [GROUPS] (
[GID] INT NOT NULL IDENTITY,
[GNAME] NVARCHAR(50),
PRIMARY KEY ([GID])
)
CREATE TABLE [GROUPWALKERS] (
[GWID] INT NOT NULL IDENTITY,
[GID] INT DEFAULT 0,
[USERID] INT DEFAULT 0,
PRIMARY KEY ([GWID])
)
CREATE TABLE [WALKS] (
[WID] INT NOT NULL IDENTITY,
[WNAME] NVARCHAR(50),
[WORDER] INT DEFAULT 0,
PRIMARY KEY ([WID])
)
CREATE TABLE [CURRENTSITU] (
[CSID] INT NOT NULL IDENTITY,
[USERID] INT DEFAULT 0,
[WID] INT DEFAULT 0,
[GID] INT DEFAULT 0,
[STARTTIME] DATETIME,
[ENDTIME] DATETIME,
PRIMARY KEY ([CSID])
)
The idea behind the tables is that the USERS table stores the people who use my DB.
The GROUPS table stores the walking group names and the GROUPWALKERS table stores which users are in which group.
The WALKS table contains the walks and the order in which people should walk them.
The CURRENTSITU table keeps an on-going log of where each user is on the walks. It will contain a single row per user id/walk id, eg:
CSID USERID WID GID STARTTIME ENDTIME
1 35 2 2 2021-05-16 09:15
2 36 1 2 2021-05-16 08:30
3 37 2 2 2021-05-16 08:00
There is a USERID 38 and 39 but they haven't started any walks yet
Some example inserts are:
SET IDENTITY_INSERT USERS ON
INSERT INTO USERS (USERID,USERNAME) VALUES (35,'Bill')
INSERT INTO USERS (USERID,USERNAME) VALUES (36,'Bob')
INSERT INTO USERS (USERID,USERNAME) VALUES (37,'Jill')
INSERT INTO USERS (USERID,USERNAME) VALUES (38,'Jean')
INSERT INTO USERS (USERID,USERNAME) VALUES (39,'Jack')
SET IDENTITY_INSERT USERS OFF
SET IDENTITY_INSERT GROUPS ON
INSERT INTO GROUPS (GID,GNAME) VALUES (1,'Group 1')
INSERT INTO GROUPS (GID,GNAME) VALUES (2,'Group 2')
SET IDENTITY_INSERT GROUPS OFF
SET IDENTITY_INSERT GROUPWALKERS ON
INSERT INTO GROUPWALKERS (GWID,GID,USERID) VALUES (1,2,35)
INSERT INTO GROUPWALKERS (GWID,GID,USERID) VALUES (2,2,36)
INSERT INTO GROUPWALKERS (GWID,GID,USERID) VALUES (3,2,37)
INSERT INTO GROUPWALKERS (GWID,GID,USERID) VALUES (4,2,38)
INSERT INTO GROUPWALKERS (GWID,GID,USERID) VALUES (5,2,39)
SET IDENTITY_INSERT GROUPWALKERS OFF
SET IDENTITY_INSERT WALKS ON
INSERT INTO WALKS (WID,WNAME,WORDER) VALUES (1,'Snowdon',0)
INSERT INTO WALKS (WID,WNAME,WORDER) VALUES (2,'Bluebell Wood',1)
INSERT INTO WALKS (WID,WNAME,WORDER) VALUES (3,'Mam Tor',2)
INSERT INTO WALKS (WID,WNAME,WORDER) VALUES (4,'River Seven',3)
INSERT INTO WALKS (WID,WNAME,WORDER) VALUES (5,'Scar Fell',4)
SET IDENTITY_INSERT WALKS OFF
SET IDENTITY_INSERT CURRENTSITU ON
INSERT INTO CURRENTSITU (CSID,USERID,WID,GID,STARTTIME,ENDTIME) VALUES (1,35,2,2,'2021-05-16 09:15',NULL)
INSERT INTO CURRENTSITU (CSID,USERID,WID,GID,STARTTIME,ENDTIME) VALUES (2,36,1,2,'2021-05-16 08:30',NULL)
INSERT INTO CURRENTSITU (CSID,USERID,WID,GID,STARTTIME,ENDTIME) VALUES (3,37,2,2,'2021-05-16 08:00',NULL)
SET IDENTITY_INSERT CURRENTSITU OFF
Now what I'm trying to do is a mini-league table of where everybody is at in the walks so I tried:
select g.GID, u.USERID, w.WORDER, cs.STARTTIME
from GROUPS g
left join GROUPWALKERS gw on g.GID=gw.GID
left join CURRENTSITU cs on cs.GID=g.GID
left join WALKS w on cs.WID=w.WID
inner join USERS u on gw.USERID=u.USERID
where g.GID = 2
order by w.WORDER, cs.STARTTIME, u.USERID
The above is try and grab the group id, user id, what walk they are on in the pre-defined order and the start time of this current walk. I'm then doing a simple 'order by' to create my table.
My problem is that even though I've tried moving round the joins and/or using different joins I just can't get:
GID USERID WORDER STARTTIME
2 37 2 2021-05-16 08:00
2 35 2 2021-05-16 09:15
2 36 1 2021-05-16 08:30
2 38 NULL NULL
2 39 NULL NULL
I get:
GID USERID WORDER STARTTIME
2 37 2 2021-05-16 08:00
2 35 2 2021-05-16 08:00
2 36 2 2021-05-16 08:00
2 38 2 2021-05-16 08:00
2 39 2 2021-05-16 08:00
or I get all of the users, but the users that have done other walks show the start time of their other walks. Only the users who have done no walks at all show up with a WORDER and STARTTIME of Null
Any ideas?
Thanks
Column keys are a little confusing. Might want to name them more verbosely. Be mindful of which table you use as your base (FROM clause). Starting from Groups and GroupStats, you are effectively asking for those details first, then user info if available. A more straightforward approach is to think on what your query is hoping to accomplish, which is primarily a stat chart on users.
Being that's the desired data, I'd start with Users, then join their group membership, then group details, and then group activity and walk detail data as LEFT OUTER JOINs, being that you want to return their data if it exists, and NULL if it does not.
SELECT g.GID
,u.USERID
,w.WORDER
,c.STARTTIME
FROM dbo.USERS AS u
INNER JOIN dbo.GROUPWALKERS AS g ON g.USERID = u.USERID
INNER JOIN dbo.GROUPS AS g2 ON g2.GID = g.GID
LEFT OUTER JOIN dbo.CURRENTSITU AS c ON c.USERID = u.USERID
AND c.GID = g.GID
LEFT OUTER JOIN dbo.WALKS AS w ON w.WID = c.WID;
Related
I want to create a time table for my university's modules in SQL Server Management Studio using a query.
This is my first time programming in SQL and don't know much about it. I have created the database as well as the tables I want to use, using the following code:
USE master
GO
IF EXISTS (SELECT * FROM sys.databases WHERE name = 'myTimetable')
DROP DATABASE myTimetable
GO
CREATE DATABASE myTimetable
GO
USE myTimetable
GO
CREATE TABLE DayTable
(
WeekDay_ID int Identity (1,1) PRIMARY KEY NOT NULL,
Day_Name varchar(10) NOT NULL
)
GO
CREATE TABLE TimeRangeTable
(
DayTime_ID int Identity (1,1) PRIMARY KEY NOT NULL,
TimeInterval varchar(20) NOT NULL
)
GO
CREATE TABLE SubjectTable
(
Course_ID int Identity (1,1) PRIMARY KEY NOT NULL,
CourseCode varchar (10),
CourseName varchar (255) NOT NULL
)
GO
CREATE TABLE ScheduleTable
(
WeekDay_ID int references DayTable(WeekDay_ID),
DayTime_ID int references TimeRangeTable(DayTime_ID),
Course_ID int references SubjectTable(Course_ID),
)
GO
The tables was created correctly and I managed to insert the correct data into the tables, except for my ScheduleTable (the last table created in the above sample code).
Here is the SQL code I used to insert the data:
insert into DayTable values ('Monday')
insert into DayTable values ('Teusday')
insert into DayTable values ('Wensday')
insert into DayTable values ('Thursday')
insert into DayTable values ('Friday')
insert into DayTable values ('Saterday')
insert into DayTable values ('Sunday')
insert into TimeRangeTable values ('07:30 - 08:20')
insert into TimeRangeTable values ('08:30 - 09:20')
insert into TimeRangeTable values ('09:30 - 10:20')
insert into TimeRangeTable values ('10:30 - 11:20')
insert into TimeRangeTable values ('11:30 - 12:20')
insert into TimeRangeTable values ('12:30 - 13:20')
insert into TimeRangeTable values ('13:30 - 14:20')
insert into TimeRangeTable values ('14:30 - 15:20')
insert into TimeRangeTable values ('15:30 - 16:20')
insert into TimeRangeTable values ('16:30 - 17:20')
insert into TimeRangeTable values ('17:30 - 18:20')
insert into SubjectTable values ('WTW115','Discrete Mathematics')
insert into SubjectTable values ('INF214','Database Design')
insert into SubjectTable values ('INL210','Information Seeking and Retreival')
insert into SubjectTable values ('INL240','Social and Ethical Impact')
insert into SubjectTable values ('INF271','System Analysis and Design')
insert into SubjectTable values ('INF154','Introduction to Programming')
-- Struling from this point onward...
insert into ScheduleTable values('1','1','1')
insert into ScheduleTable values('1','2','2')
insert into ScheduleTable values('1','3','3')
insert into ScheduleTable values('1','4','3')
insert into ScheduleTable values('1','5','3')
insert into ScheduleTable values('2','4','1')
insert into ScheduleTable values('2','5','2')
insert into ScheduleTable values('2','6','2')
insert into ScheduleTable values('2','9','4')
insert into ScheduleTable values('2','10','2')
insert into ScheduleTable values('3','1','5')
insert into ScheduleTable values('3','2','5')
insert into ScheduleTable values('3','6','1')
insert into ScheduleTable values('3','7','3')
insert into ScheduleTable values('4','1','4')
insert into ScheduleTable values('4','3','5')
It all executes and inserts the data, but when I display the data for ScheduleTable, is shows the data as Follow:
WeekDay_ID DayTime_ID Course_ID
-------------------------------------------
1 1 1 1
2 1 2 2
3 1 3 3
4 1 4 3
5 1 5 3
6 2 4 1
7 2 5 2
8 2 6 2
9 2 9 4
10 2 10 2
11 3 1 5
12 3 2 5
13 3 6 1
14 3 7 3
15 4 1 4
16 4 3 5
Where I wanted it to show the data instead of just the codes, example of what I wanted:
WeekDay_ID DayTime_ID Course_ID
--------------------------------------------
1 Monday 07:30 - 08:20 WTW115
2 Monday 08:30 - 09:20 INF214
3 Monday 09:30 - 10:20 INL210
4 Monday 10:30 - 11:20 INL210
5 Monday 11:30 - 12:20 INL210
etc...
I know it has something to do with my Schedule table but that is all I know I don't know how to display it in this way as in the example.
Any help will be appreciated.
Time for a few joins:
select --*
d.Day_Name,
tr.TimeInterval,
sbj.CourseCode, sbj.CourseName
from ScheduleTable as sch
join DayTable as d on sch.WeekDay_ID = d.WeekDay_ID
join TimeRangeTable as tr on sch.DayTime_ID = tr.DayTime_ID
join SubjectTable as sbj on sch.Course_ID = sbj.Course_ID;
You could create a view (for the aforementioned statement) for convenience:
create view TimeScheduleView
as
select
d.Day_Name,
tr.TimeInterval,
sbj.CourseCode, sbj.CourseName
from ScheduleTable as sch
join DayTable as d on sch.WeekDay_ID = d.WeekDay_ID
join TimeRangeTable as tr on sch.DayTime_ID = tr.DayTime_ID
join SubjectTable as sbj on sch.Course_ID = sbj.Course_ID;
go
select *
from TimeScheduleView;
I have an exsisting database with 700 row where are stored users, let's call this users. I will have a new column which is "time_code". Each user have a time_code.
I will have these code from an other table where each user_name is associate to a time_code, I call this table time code users. Is there a way to add these code from time code users to users at the corresponding name ?
And can I do this only with SQL ?
Assume you initial dataset
CREATE TABLE USERS (USER_NO NUMBER (3), NAME VARCHAR(5));
CREATE TABLE TIME_CODE_USERS (NAME VARCHAR(5), TIME_CODE VARCHAR(3));
INSERT INTO USERS VALUES (1,'A');
INSERT INTO USERS VALUES (2,'B');
INSERT INTO USERS VALUES (3,'C');
INSERT INTO TIME_CODE_USERS VALUES ('A', 'GMT');
INSERT INTO TIME_CODE_USERS VALUES ('B', 'IST');
INSERT INTO TIME_CODE_USERS VALUES ('C', 'CET');
Now alter the target table to add your column
ALTER TABLE USERS ADD TIME_CODE VARCHAR(3);
Run the update to populate the target
UPDATE USERS
SET TIME_CODE = (SELECT TIME_CODE
FROM TIME_CODE_USERS
WHERE USERS.NAME = TIME_CODE_USERS.NAME)
WHERE EXISTS (SELECT TIME_CODE
FROM TIME_CODE_USERS
WHERE USERS.NAME = TIME_CODE_USERS.NAME);
Check the update
SELECT * FROM USERS;
| USER_NO | NAME | TIME_CODE |
|---------|------|-----------|
| 1 | A | GMT |
| 2 | B | IST |
| 3 | C | CET |
UPDATE u
SET u.time_code = tcu.time_code
FROM users u INNER JOIN time_code_users tcu
ON u.user_name = tcu.user_name
ALTER TABLE users ADD time_code varchar(40);
UPDATE users SET users.time_code = (
SELECT time_code FROM time_code_users
WHERE users.username = time_code_users.username
);
I have the following SQL Server database structure I have to use to query data. The model could be wrong; I appreciate arguments if that's the case so I can ask for changes. If not, I need a query to get tabbed data in the format I will detail below.
The structure goes like this:
CLIENTS:
ClientID ClientName
-----------------------
1 James
2 Leonard
3 Montgomery
ATTRIBUTES:
AttributeID AttributeName
-----------------------------
1 Rank
2 Date
3 Salary
4 FileRecordsAmount
ATTRIBUTES_STRING:
ClientID AttributeID AttributeStringValue
1 1 Captain
2 1 Chief Surgeon
3 1 Chief Engineer
ATTRIBUTES_NUMERIC:
ClientID AttributeID AttributeNumericValue
1 4 187
2 4 2
3 4 10
The result I need would be the following:
RESULTS:
----------------------------------------------------------
ClientID ClientName Rank FileRecordsAmount
1 James Captain 187
2 Leonard Chief Surgeon 2
3 Montgomery Chief Engineer 10
How can I achieve this?
Thank you very much!
EDIT: The challenging issue here (for me) is that the attributes are dynamic... I have 5 tables of attributes (ATTRIBUTES_STRING, ATTRIBUTES_NUMERIC, ATTRIBUTES_DATE, ATTRIBUTES_BIT, ATTRIBUTES_INT) and the user should be able to set up it's own attributes.
You need an SQL join. It will look something like this:
select
CLIENTS.ClientID,
CLIENTS.ClientName,
ATTRIBUTES_STRING1.AttributeStringValue as Rank,
ATTRIBUTES_NUMERIC2.AttributeNumericValue as FileRecordsAmount
from
CLIENTS,
ATTRIBUTES ATTRIBUTES1,
ATTRIBUTES ATTRIBUTES2,
ATTRIBUTES_STRING ATTRIBUTES_STRING1,
ATTRIBUTES_NUMERIC ATTRIBUTES_NUMERIC2
where CLIENTS.ClientID = ATTRIBUTES_STRING1.ClientID
and CLIENTS.ClientID = ATTRIBUTES_NUMERIC2.ClientID
and ATTRIBUTES_STRING1.AttributeID = ATTRIBUTES1.AttributeID
and ATTRIBUTES_NUMERIC2.AttributeID = ATTRIBUTES2.AttributeID
and ATTRIBUTES1.AttributeName = 'Rank'
and ATTRIBUTES2.AttributeName = 'FileRecordsAmount'
;
Here is the SQL Fiddle for reference. This is my first EAV schema so I wouldn't put too much trust in it :)
Edit: Schema provided below for reference:
create table CLIENTS (
ClientID integer primary key,
ClientName varchar(50) not null
);
insert into CLIENTS values (1,'James');
insert into CLIENTS values (2,'Leonard');
insert into CLIENTS values (3,'Montgomery');
create table ATTRIBUTES (
AttributeID integer primary key,
AttributeName varchar(50) not null
);
create index ATTRIBUTE_NAME_IDX on ATTRIBUTES (AttributeName);
insert into ATTRIBUTES values (1,'Rank');
insert into ATTRIBUTES values (2,'Date');
insert into ATTRIBUTES values (3,'Salary');
insert into ATTRIBUTES values (4,'FileRecordsAmount');
create table ATTRIBUTES_STRING (
ClientID integer,
AttributeID integer not null,
AttributeStringValue varchar(255) not null,
primary key (ClientID, AttributeID)
);
insert into ATTRIBUTES_STRING values (1,1,'Captain');
insert into ATTRIBUTES_STRING values (2,1,'Chief Surgeon');
insert into ATTRIBUTES_STRING values (3,1,'Chief Engineer');
create table ATTRIBUTES_NUMERIC (
ClientID integer,
AttributeID integer not null,
AttributeNumericValue numeric(10, 5) not null,
primary key (ClientID, AttributeID)
);
insert into ATTRIBUTES_NUMERIC values (1,4,187);
insert into ATTRIBUTES_NUMERIC values (2,4,2);
insert into ATTRIBUTES_NUMERIC values (3,4,10);
Edit: Modified the select to make it easier to extend with extra attributes
Loooking for a way to retrieve community from a large dataset I came across an article about the algorithm which seems to be apropriate for large datasets. Anyway the data is stored two tables: users (nodes) and connections and I would like to retrieve the communities by pure sql queries without help of custom applications (I'm using SQL Server 2008).
The algorithm to retrieve the cliques is the following:
Read the graph G
Generate set neighbors(v) for every vertex of G
for each vertex v of G
call recursive_find_cliques(v, neighbors(v))
end for
Function recursive_find_cliques(x, n)
for each vertex t ∈ n by ascending order calculate set sigma
if sigma is not empty
extend x with t
call recursive_find_cliques(x, sigma)
end if
end for
where sigma is the set of vertices that could constitute triangles with v and its neighbors.
I already created a stored procedure which returns a table of neighbors of selected node but so far I haven't delat with sql functions and advanced queries so the question is the following:
Does anyone know how to rewrite the
algorithm above in sql in order to get
the set of cliques? As the question
might be a little abstract, I may
point out that the main problem is to
create a recursive function
(recursive_find_cliques(x, n)) which
takes a table (n) as an argument).
Thank you!
EDIT:
Here is sthe stored procedure created so far:
CREATE PROCEDURE [dbo].[Peamc_Test]
AS
BEGIN
SET XACT_ABORT ON
BEGIN TRAN
SET NOCOUNT ON;
CREATE TABLE #Users
(
UserId int NOT NULL,
userLabel varchar(50) PRIMARY KEY NOT NULL,
Observed bit NOT NULL
)
CREATE TABLE #Neighbors
(
UserId int NOT NULL,
userLabel varchar(50) NOT NULL PRIMARY KEY,
Retrieved bit NOT NULL
)
CREATE TABLE #ConnectedVertices
(
UserId int NOT NULL,
userLabel varchar(50) NOT NULL PRIMARY KEY,
)
CREATE TABLE #Cliques
(
CliqueId int NOT NULL,
UserId varchar(50) NOT NULL,
)
DECLARE #UsersCount int
DECLARE #ii int
DECLARE #User varchar(50)
DECLARE #NeighborsCount int
INSERT INTO #Users(UserId, userLabel, Observed) SELECT user_id, userLabel, 0 FROM dbo.test_users WHERE user_id IS NOT NULL
SELECT #UsersCount = COUNT(*) FROM #Users
SELECT #ii = 1
WHILE #ii <= #UsersCount
BEGIN
--select user
SELECT TOP 1 #User = userLabel FROM #Users WHERE Observed = 0 ORDER BY UserId
UPDATE #Users SET Observed = 1 WHERE userLabel = #User
--Get user's neighbors
DELETE FROM #Neighbors
INSERT INTO #Neighbors(UserId, userLabel, Retrieved)
SELECT u.user_id, t2.neighbor, 0 FROM ( SELECT CALLING_NEIGHBORS.neighbor FROM ( SELECT mc.calling_party AS neighbor FROM monthly_connections_test mc WHERE mc.called_party = #User) AS CALLING_NEIGHBORS INNER JOIN (SELECT mc.called_party AS neighbor FROM monthly_connections_test mc WHERE mc.calling_party = #User) AS CALLED_NEIGHBORS ON CALLING_NEIGHBORS.neighbor = CALLED_NEIGHBORS.neighbor) AS t2 INNER JOIN test_users u ON t2.neighbor = u.userLabel
SELECT #NeighborsCount = COUNT(*) FROM #Neighbors
SELECT #ii = #ii + 1
--HERE the function recursive_find_cliques has to search for cliques and insert the found ones in #cliques
END
SELECT * FROM #Cliques
END
It does'not return anything yet as it is not finished. It though retrieves all neighbors for the currently selected nodes and the next step is to implement recursive_find_cliques function.
I realised that my first answer only works when each clique has at least one user who is not referred to by any others in that clique. In other words, closed cliques like A-B, B-C, C-A will not be found.
Here is a solution which solves this. Again we have users with IDs, now 1..20. There are several cases of neighbouring relations that need to be handled:
Compared to the simple case, it is harder to find a unique starter for each clique.
We achieve this with a little sleight of hand:
Reorder the neighbours so that for all references A-B, A is less than B, ignoring any A=B.
From these, remove any A-X references if there are any X-A, which could cause a loop. This will never remove references to A completely because X-A remains and A-X will be added in the recursion.
The resultant set are the 'starting' users and we use them to prime the CTE:
-- Get all pairs, where UserA < UserB, dropping any A=B or B=A
WITH LRNeighbours(A, B) AS (
SELECT
Neighbours.UserA, Neighbours.UserB
FROM
Neighbours
WHERE
Neighbours.UserA < Neighbours.UserB
UNION ALL
SELECT DISTINCT
Neighbours.UserB, Neighbours.UserA
FROM
Neighbours
WHERE
Neighbours.UserA > Neighbours.UserB
),
-- Isolate those that are not referred to by a higher numbered key
Starters(userid) AS (
SELECT DISTINCT
A
FROM
LRNeighbours
WHERE
A NOT IN (
SELECT
B
FROM
LRNeighbours
)
),
-- The recursive Common Table Expression
cliques(userid, clique) AS (
-- Number starters 1..N
SELECT
userid, ROW_NUMBER() OVER(ORDER BY userid) AS clique
FROM
Starters
UNION ALL
-- Recurse, adding users referred by siblings, avoiding starters themselves
SELECT
B, clique
FROM
LRNeighbours INNER JOIN
cliques ON
LRNeighbours.A = cliques.userid
AND B NOT IN (
SELECT
userid
FROM
starters
)
)
SELECT DISTINCT
clique, userid
FROM
cliques
ORDER BY
clique, userid
Results:
1 1
1 2
2 3
2 4
3 5
3 6
3 7
3 8
4 9
4 10
4 11
4 12
4 13
5 14
5 15
5 16
5 17
5 18
5 19
5 20
CREATE TABLE [dbo].[Users](
[UserID] [int] IDENTITY(1,1) NOT NULL,
[UserName] [varchar](50) NOT NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[Neighbours](
[UserA] [int] NOT NULL,
[UserB] [int] NOT NULL
) ON [PRIMARY]
Users populated with 1..8 and Neighbours
UserA UserB
1 2
2 3
4 5
4 6
5 7
7 8
Then:
WITH cliques(userid, clique) AS (
SELECT
userid, ROW_NUMBER() OVER(ORDER BY userid) AS clique
FROM
Users
WHERE
users.UserID NOT IN (
SELECT
UserB
FROM
Neighbours
)
UNION ALL
SELECT
Neighbours.UserB, clique
FROM
neighbours
INNER JOIN cliques
ON Neighbours.UserA = cliques.userid
)
SELECT
clique, cliques.userid
FROM
cliques
ORDER BY
clique, userid
Result:
clique userid
1 1
1 2
1 3
2 4
2 5
2 6
2 7
2 8
See : Recursive Queries Using Common Table Expressions
I've added a two LABELS and two GOTO statements
CREATE PROCEDURE [dbo].[Peamc_Test]
AS
BEGIN
SET XACT_ABORT ON
BEGIN TRAN
SET NOCOUNT ON;
CREATE TABLE #Users
(
UserId int NOT NULL,
userLabel varchar(50) PRIMARY KEY NOT NULL,
Observed bit NOT NULL
)
CREATE TABLE #Neighbors
(
UserId int NOT NULL,
userLabel varchar(50) NOT NULL PRIMARY KEY,
Retrieved bit NOT NULL
)
CREATE TABLE #ConnectedVertices
(
UserId int NOT NULL,
userLabel varchar(50) NOT NULL PRIMARY KEY,
)
CREATE TABLE #Cliques
(
CliqueId int NOT NULL,
UserId varchar(50) NOT NULL,
)
DECLARE #UsersCount int
DECLARE #ii int
DECLARE #User varchar(50)
DECLARE #NeighborsCount int
INSERT INTO #Users(UserId, userLabel, Observed) SELECT user_id, userLabel, 0 FROM dbo.test_users WHERE user_id IS NOT NULL
SELECT #UsersCount = COUNT(*) FROM #Users
SELECT #ii = 1
WHILE #ii <= #UsersCount
BEGIN
--select user
SELECT TOP 1 #User = userLabel FROM #Users WHERE Observed = 0 ORDER BY UserId
UPDATE #Users SET Observed = 1 WHERE userLabel = #User
--Get user's neighbors
DELETE FROM #Neighbors
INSERT INTO #Neighbors(UserId, userLabel, Retrieved)
SELECT u.user_id, t2.neighbor, 0 FROM ( SELECT CALLING_NEIGHBORS.neighbor FROM ( SELECT mc.calling_party AS neighbor FROM monthly_connections_test mc WHERE mc.called_party = #User) AS CALLING_NEIGHBORS INNER JOIN (SELECT mc.called_party AS neighbor FROM monthly_connections_test mc WHERE mc.calling_party = #User) AS CALLED_NEIGHBORS ON CALLING_NEIGHBORS.neighbor = CALLED_NEIGHBORS.neighbor) AS t2 INNER JOIN test_users u ON t2.neighbor = u.userLabel
SELECT #NeighborsCount = COUNT(*) FROM #Neighbors
SELECT #ii = #ii + 1
GOTO Clique_Find
--HERE the function recursive_find_cliques has to search for cliques and insert the found ones in #cliques
--------------------
Clique_Return:
--------------------
END
SELECT * FROM #Cliques
END
--------------------
Clique_Find:
--------------------
-- Code goes here
-- Code goes here
-- Code goes here
-- Code goes here
-- Code goes here
-- Code goes here
GOTO Clique_Return
Given the tables:
role: roleid, name
permission: permissionid, name
role_permission: roleid, permissionid
I have a set of permissions, and I want to see if there is an existing role that has these permissions, or if I need to make a new one. Note that I already know the permissionid, so really the permission table can be ignored - I just included it here for clarity.
Is this possible to do in a SQL query? I imagine it would have to be a dynamically-generated query.
If not, is there any better way than the brute force method of simply iterating over every role, and seeing if it has the exact permissions?
Note, I'm looking for a role that has the exact set of permissions - no more, no less.
You can select all roles that have the subset of permissions you are looking for. Count the number of permissions and see if it's exactly equal to the number of permissions you need:
select r.roleid
from role r
where not exists (select * from role_permissions rp where rp.roleid = r.roleid and rp.permissionid not in (1,2,3,4)) -- id of permissions
and (select count(*) from role_permissions rp where rp.roleid = r.roleid) = 4 -- number of permissions
Having made a hash of my first answer to this question, here is a slightly left field alternative which works but does involve adding data to the database.
The trick is to add a column to the permission table that holds a unique value for each row.
This is a fairly common pattern and will give precise results. The downside is you have to code your way around hiding the numerical equivalents.
id int(10)
name varchar(45)
value int(10)
Then the contents will become:
Permission: Role Role_Permission
id name value id name roleid permissionid
-- ---- ----- -- ---- ------ ------------
1 Read 8 1 Admin 1 1
2 Write 16 2 DataAdmin 1 2
3 Update 32 3 User 1 3
4 Delete 64 1 4
2 1
2 3
2 4
Then each combination of roles gives a unique value:
SELECT x.roleid, sum(value) FROM role_permission x
inner join permission p
on x.permissionid = p.id
Group by x.roleid
Giving:
roleid sum(value)
------ ----------
1 120 (the sum of permissions 1+2+3+4 = 120)
2 104 (the sum of permissions 1+3+4 = 104)
Now where did I leave that corkscrew...
This is an old sql trick (works in Oracle, at least):
SELECT roleid FROM role_permission t1
WHERE NOT EXISTS (
(SELECT permissionid FROM role_permission t2 WHERE t2.roleid = t1.roleid
MINUS
SELECT permissionid FROM role_permission WHERE roleid = 'Admin')
UNION
(SELECT permissionid FROM role_permission t2 WHERE roleid = 'Admin'
MINUS
SELECT permissionid FROM role_permsission t2 WHERE t2.roleid = t1.roleid)
)
Also not validated. Red wine always sounds good.
You basically need to check if there is a role that has the exact number of distinct permissions as you are checking for.
I have checked this stored procedure on SQL Server 2005 and it returns only those role ids that have an exact match of permission ids to those in the passed in list of comma separated permission ids -
CREATE PROC get_roles_for_permissions (#list nvarchar(max)) -- #list is a comma separated list of your permission ids
AS
SET NOCOUNT ON
BEGIN
DECLARE #index INT, #start_index INT, #id INT
DECLARE #permission_ids TABLE (id INT)
SELECT #index = 1
SELECT #start_index = 1
WHILE #index <= DATALENGTH(#list)
BEGIN
IF SUBSTRING(#list,#index,1) = ','
BEGIN
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT INTO #permission_ids ([id]) VALUES (#id)
SELECT #start_index = #index + 1
END
SELECT #index = #index + 1
END
SELECT #id = CAST(SUBSTRING(#list, #start_index, #index - #start_index ) AS INT)
INSERT INTO #permission_ids ([id]) VALUES (#id)
SELECT
r.roleid
FROM
role r
INNER JOIN
role_permission rp
ON r.roleid = rp.roleid
INNER JOIN
#permission_ids ids
ON
rp.permissionid = ids.id
GROUP BY r.roleid
HAVING(SELECT COUNT(*)
FROM role_permission
WHERE roleid = r.roleid) = (SELECT COUNT(*) FROM #permission_ids)
END
Example Data
CREATE TABLE [dbo].[role](
[roleid] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50)
)
CREATE TABLE [dbo].[permission](
[permissionid] [int] IDENTITY(1,1) NOT NULL,
[name] [nvarchar](50)
)
CREATE TABLE [dbo].[role_permission](
[roleid] [int],
[permissionid] [int]
)
INSERT INTO role(name) VALUES ('Role1')
INSERT INTO role(name) VALUES ('Role2')
INSERT INTO role(name) VALUES ('Role3')
INSERT INTO role(name) VALUES ('Role4')
INSERT INTO permission(name) VALUES ('Permission1')
INSERT INTO permission(name) VALUES ('Permission2')
INSERT INTO permission(name) VALUES ('Permission3')
INSERT INTO permission(name) VALUES ('Permission4')
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 1)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 2)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (1, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 2)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (2, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (3, 3)
INSERT INTO role_permission(roleid, permissionid) VALUES (3, 4)
INSERT INTO role_permission(roleid, permissionid) VALUES (4, 4)
EXEC get_roles_for_permissions '3,4' -- RETURNS roleid 3
Maybe use a subquery along the lines of ...
SELECT * FROM role r
WHERE r.rolid = (SELECT x.roledid
FROM role_permission
WHERE x.permissionid in (1,2,3,4);
Sorry, haven't validated this, but having just spent an hour debugging PHP code for another question I feel the need for a glass of red wine.