Suppose a system where People have access to Buildings and in the buildings, have access to certain Rooms.
Access is defined according to the associated Permissions tables or in the case of people with full access, full access.
The db tables are defined as follows:
buildings (id INT)
rooms (id INT, building_id INT)
people (id INT, has_full_access BIT)
building_permissions (building_id INT, person_id INT)
room_permissions (room_id INT, person_id INT)
Currently I have table-valued functions that return tables with the authorised ids of buildings and rooms based on the person's id and whether they have full access.
CREATE FUNCTION fn_get_authorised_buildings (#person_id INT, #has_full_access BIT)
RETURNS TABLE AS
RETURN
(
SELECT b.id
FROM buildings b
WHERE #has_full_access = 1
UNION
SELECT b.id
FROM buildings b
INNER JOIN building_permissions bp ON bp.building_id = b.id
WHERE bp.person_id = #person_id
);
CREATE FUNCTION fn_get_authorised_rooms (#person_id INT, #has_full_access BIT)
RETURNS TABLE AS
RETURN
(
SELECT r.id
FROM rooms r
WHERE #has_full_access = 1
UNION
SELECT r.id
FROM rooms r
INNER JOIN room_permissions rp ON rp.room_id = r.id
WHERE rp.person_id = #person_id
);
Throughout the system whenever buildings and rooms are involved I need to filter those tables based on the person's permissions. If the person has full access then all the rows must be included, otherwise only those that they have permission for.
My queries (simplified) look something like this:
DECLARE #person_id INT = 123
DECLARE #has_full_access BIT = 1
DECLARE #authorised_buildings TABLE (id INT)
INSERT INTO #authorised_buildings SELECT id FROM fn_get_authorised_buildings(#person_id , #has_full_access)
DECLARE #authorised_rooms TABLE (id INT)
INSERT INTO #authorised_rooms SELECT id FROM fn_get_authorised_rooms(#person_id, #has_full_access)
--Example A
SELECT *
FROM buildings b
INNER JOIN rooms r ON r.building_id = b.id
WHERE 1 = 1
AND b.id IN (SELECT id FROM #authorised_buildings)
AND r.id IN (SELECT id FROM #authorised_rooms)
--Example B
SELECT *
FROM floors f -- or other tables that are related to rooms
INNER JOIN rooms r ON r.floor_id = f.id
WHERE 1 = 1
AND r.id IN (SELECT id FROM #authorised_rooms)
Is there a better way to do this?
EDIT:
Here is a fiddle with the setup
You can create a view like this that contains all the logic:
CREATE VIEW bulding_rooms_per_person AS
SELECT p.id AS person_id, b.id as building_id, r.id AS room_id
FROM people p
LEFT JOIN building_permissions bp ON bp.person_id = p.id
LEFT JOIN room_permissions rp ON rp.person_id = p.id
INNER JOIN buildings b ON b.id = bp.building_id OR p.has_full_access = 1
INNER JOIN rooms r ON r.building_id = b.id AND (r.id = rp.room_id OR p.has_full_access = 1)
And then just query over it:
SELECT * from bulding_rooms_per_person WHERE person_id = #person_id
Related
I have a stored procedure that accepts a user defined table type, which is just a list of ints.
If the table is not set (Either sent in as NULL, or has no rows), then my existing query to return data is OK. But I need to only return Ids that are in that table variable.
So if the table variable has data, I would like to INNER JOIN on it, to only return matching Ids.
So that the moment, it's a basic query like this: (Example)
SELECT ...
FROM MyTable
Where UserId = 1
But I need to somehow:
SELECT ...
FROM MyTable m
INNER JOIN #MyTableVariable v ON v.Id = m.Id, --But only if #MyTableVariable has data
Where UserId = 1
Can I do the inner join, only when there's data in #MyTableVriable? Or maybe EXITSTS would help?
Of course, a relatively simple approach (assuming your SELECT statement is not too complicated and you don't mind "duplicating" it) would be:
IF EXISTS (SELECT 1 FROM #MyTableVariable)
BEGIN
SELECT ...
FROM MyTable m
INNER JOIN #MyTableVariable v ON v.Id = m.Id, --But only if #MyTableVariable has data
Where UserId = 1;
END
ELSE
BEGIN
SELECT ...
FROM MyTable m
Where UserId = 1;
END
Otherwise
SELECT ...
FROM MyTable m
LEFT OUTER JOIN #MyTableVariable v ON v.Id = m.Id, --But only if #MyTableVariable has data
Where UserId = 1
AND
(
(EXISTS (SELECT 1 FROM #MyTableVariable)
AND v.Id Is Not NULL)
OR
(NOT EXISTS (SELECT 1 FROM #MyTableVariable))
);
I'll stand corrected if I'm wrong (just don't have the opportunity to test it myself right now), but I believe that even if NULL was passed in as the #MyTableVariable value, SQL would still see it as an empty table (with the corresponding structure of the UDT)
SQL Server provides conditional statemets; then, you can try this approach bellow, with a conditional select, based on your table variable status (in this example, checking if is not empty):
IF(EXISTS(SELECT 1 FROM #MyTableVariable))
BEGIN
SELECT ...
FROM MyTable m
INNER JOIN #MyTableVariable v ON v.Id = m.Id
WHERE UserId = 1
END
ELSE
BEGIN
SELECT ...
FROM MyTable
WHERE UserId = 1
END
Hello all I have a requirement where I need to filter the rows with multiple conditions and exclude the result if a single entry exists in matching. Here are my sample tables
DECLARE #CUSTOMER TABLE
(
CUSTOMERID INT,
CUSTOMERNAME NVARCHAR(100)
)
DECLARE #ORDER TABLE
(
ORDERID INT,
CUSTOMERID INT,
ISSPECIALORDER INT,
SPECIALORDERID INT
)
DECLARE #SPECIALORDERDTL TABLE
(
SPECIALORDERID INT,
SPECIALORDERDATAID INT
)
DECLARE #SPECIALORDERDATA TABLE
(
SPECIALORDERDATAID INT,
SPECIALORDERMASTERID INT
)
INSERT INTO #CUSTOMER VALUES (100,'CUSTOMER1'),(200,'CUSTOMER2'),(300,'CUSTOMER3'),(400,'CUSTOMER4`enter code here`')
INSERT INTO #ORDER VALUES (1,100,0,1),(2,100,1,1),(3,100,1,2),(4,200,0,1),(5,200,1,1),(6,200,1,4),(7,300,1,5),(8,400,1,6)
INSERT INTO #SPECIALORDERDTL VALUES(1,1),(2,1),(3,2),(4,4)
INSERT INTO #SPECIALORDERDATA VALUES(1,1),(2,1),(3,1),(4,2),(5,2) -- 2 a special order
SELECT C.CUSTOMERID,C.CUSTOMERNAME
FROM #ORDER O
INNER JOIN #CUSTOMER C ON C.CUSTOMERID=O.CUSTOMERID
INNER JOIN #SPECIALORDERDTL SO ON SO.SPECIALORDERID = O.SPECIALORDERID
INNER JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
WHERE SOD.SPECIALORDERID <> 2 AND O.ISSPECIALORDER =0
GROUP BY C.CUSTOMERID,C.CUSTOMERNAME
ORDER BY C.CUSTOMERNAME
When I have an entry in #SPECIALORDERDTL with SPECIALORDERMASTERID as 2 I need to consider them as special entries and exclude those. So my query should return only the customer with 100.
It is not clear from your description or SQL what exactly want. From my understanding:
DECLARE #CUSTOMER TABLE
(
CUSTOMERID INT,
CUSTOMERNAME NVARCHAR(100)
)
DECLARE #ORDER TABLE
(
ORDERID INT,
CUSTOMERID INT,
ISSPECIALORDER INT,
SPECIALORDERID INT
)
DECLARE #SPECIALORDERDTL TABLE
(
SPECIALORDERID INT,
SPECIALORDERDATAID INT
)
DECLARE #SPECIALORDERDATA TABLE
(
SPECIALORDERDATAID INT,
SPECIALORDERMASTERID INT
)
INSERT INTO #CUSTOMER VALUES
(100,'CUSTOMER1'),
(200,'CUSTOMER2'),
(300,'CUSTOMER3'),
(400,'CUSTOMER4')
INSERT INTO #ORDER VALUES
(1,100,0,1),
(2,100,1,1),
(3,100,1,2),
(4,200,0,1),
(5,200,1,1),
(6,200,1,4),
(7,300,1,5),
(8,400,1,6)
INSERT INTO #SPECIALORDERDTL VALUES(1,1),(2,1),(3,2),(4,4)
INSERT INTO #SPECIALORDERDATA VALUES(1,1),(2,1),(3,1),(4,2),(5,2) -- 2 a special order
SELECT C.CUSTOMERID,C.CUSTOMERNAME
from #Customer c
where exists (select * from #ORDER o where o.CustomerId = c.CustomerId)
and not exists (
select *
from #ORDER O
LEFT JOIN #SPECIALORDERDTL SO ON SO.SPECIALORDERID = O.SPECIALORDERID
LEFT JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
WHERE (SO.SPECIALORDERID IS NULL
or SOD.SPECIALORDERMASTERID = 2 --AND O.ISSPECIALORDER =0
) AND O.CustomerId = c.CustomerId
);
GO
CUSTOMERID | CUSTOMERNAME
---------: | :-----------
100 | CUSTOMER1
db<>fiddle here
Assuming I understand the question, I think a conditional aggregation in the having clause is probably the simplest way to get the result you want:
SELECT C.CUSTOMERID, C.CUSTOMERNAME
FROM #CUSTOMER As C
JOIN #ORDER O
ON C.CUSTOMERID = O.CUSTOMERID
JOIN #SPECIALORDERDTL SO
ON O.SPECIALORDERID = SO.SPECIALORDERID
JOIN #SPECIALORDERDATA SOD
ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
GROUP BY C.CUSTOMERID, C.CUSTOMERNAME
HAVING COUNT(CASE WHEN SOD.SPECIALORDERMASTERID = 2 THEN 1 END) = 0
The having clause will filter out every customer where at least one of the orders associated with them have a specialordermasterid of 2.
From your description it sounds like not every customer will have an entry in SPECIALORDERDTL or SPECIALORDERDTA so you don't want to inner join to those tables.
What you need is a "not exists" correlated subquery to check that the customers do not have a matching row in those tables.
So you can remove the inner joins to SPECIAL* tables and add something like:-
where not exists (select null from SPECIALORDERDTL SO where
SO.SPECIALORDERID = O.SPECIALORDERID and SO.SPECIALORDERMASTERID = 2)
From your description I'm not quite sure where "SOD.SPECIALORDERID <> 2 AND O.ISSPECIALORDER =0" fit into it, so please give further details of outputs if you can't resolve using subquery.
Following your clarification, please try something like this:-
SELECT distinct C.CUSTOMERID,C.CUSTOMERNAME
FROM #ORDER O
INNER JOIN #CUSTOMER C ON C.CUSTOMERID=O.CUSTOMERID
where not exists
(select null from #SPECIALORDERDTL SO
INNER JOIN #SPECIALORDERDATA SOD ON SO.SPECIALORDERDATAID = SOD.SPECIALORDERDATAID
where SO.SPECIALORDERID = O.SPECIALORDERID and
SOD.SPECIALORDERMASTERID = 2
)
order by C.CUSTOMERNAME
I have 2 tables. One is #crecs that stores list of CIDs and a #temp1 that stores clone CVIDs. For the ID in the Crecs table, I want to loop through the #temp1 table to find max(CVID) and pull out the date of the Cloned CVID for that CID based on a condition.
I have boiled down the requirement to below tables:
create table #temp1(cid int,cvid int,flag varchar(5), date1 date, clone int)
insert into #temp1
values ('43','1001','A','1/1/2015',null),
('43','1002','R','2/1/2015',1001),
('43','1003','R','3/1/2015',1002),
('43','1004','R','4/1/2015',1003)
create table #crecs(cid int)
insert into #crecs values(43),(44),(45)
select * from #crecs
select * from #temp1
My query:
select t2.cid,max(t2.cvid),t2.clone,t1.cvid,t1.date1
from #temp1 t2 , #temp1 t1
join #crecs c on c.cid = t1.cid
where t2.clone = t1.cvid
and t1.clone is null and t1.flag = 'A'
group by t2.cid,t2.cvid,t2.clone,t1.cvid,t1.date1
drop table #temp1,#crecs
Desired output:
Below is my rextester link:
http://rextester.com/GSIG28211
Any help?!
You can use a recursive CTE to travel through each clone, then retrieve the last level with MAX() and display it's record.
;WITH RecursiveClones AS
(
SELECT
CID = C.cid,
OriginalCVID = T.cvid,
ClonedCVID = T.cvid,
Level = 0
FROM
#crecs AS C
LEFT JOIN #temp1 AS T ON C.cid = T.cid
WHERE
NOT EXISTS (SELECT 'does not have a clone' FROM #temp1 AS X WHERE X.cvid = T.clone)
UNION ALL
SELECT
CID = R.cid,
OriginalCVID = R.OriginalCVID,
ClonedCVID = T.cvid,
Level = R.Level + 1
FROM
RecursiveClones AS R
INNER JOIN #temp1 AS T ON R.ClonedCVID = T.clone
),
LastCloneByCID AS
(
SELECT
R.CID,
MaxLevel = MAX(R.Level)
FROM
RecursiveClones AS R
GROUP BY
R.CID
)
SELECT
L.CID,
R.ClonedCVID,
R.OriginalCVID,
T.date1
FROM
LastCloneByCID AS L
LEFT JOIN RecursiveClones AS R ON
L.CID = R.CID AND
L.MaxLevel = R.Level
LEFT JOIN #temp1 AS T ON
T.cid = L.CID AND
T.CVID = R.OriginalCVID
I want update a few interdependent tables that each have 100 columns. A simplified version of what I'm trying to do is:
Update a
Set a.[2] = b.[1]*c.[mult]
from table1 a join table2 b on a.[id] = b.[id]
join table3 c on a.[id] = c.[id]
Update b
Set b.[2] = a.[2]*d.[mult]
from table2 b join table1 b on a.[id] = a.[id]
join table4 d on b.[id] = d.[id]
And then loop it for column 3, 4, 5, ..., 100 for table a and table b. I'm new to dynamic SQL and reading a few existing posts on similar topic doesn't solve my question, especially in this case table1 and table2 depend on each other.
Thanks a lot for the help!
If you have multiple columns to update, you can do them all at once with the same update statement. You have to list each one.
Update a
Set a.[2] = b.[1]*c.[mult],
a.[3] = b.[2]*c.[mult],
a.[4] = b.[3]*c.[mult],
a.[5] = b.[4]*c.[mult],
// etc.
a.[100] = b.[99]*c.[mult]
from table1 a join table2 b on a.[id] = b.[id]
join table3 c on a.[id] = c.[id]
Update b
Set b.[2] = a.[2]*d.[mult],
b.[3] = a.[3]*d.[mult],
b.[4] = a.[4]*d.[mult],
b.[5] = a.[5]*d.[mult],
// etc.
b.[100] = a.[100]*d.[mult]
from table2 b join table1 b on a.[id] = a.[id]
join table4 d on b.[id] = d.[id]
However, if you're trying to number your columns and access them by index, then your table structure is poorly designed. Perhaps your tables should have a row for each index value and ID. This allows you to select or join by ID and index. For example,
Create Table table1
(
[id] int,
[index] int,
[value] int,
Primary Key ([id], [index])
)
Create Table table2
(
[id] int,
[index] int,
[value] int,
Primary Key ([id], [index])
)
Then you can join them like this:
Update a
Set a.[value] = b.[value]*c.[mult]
from table1 a join table2 b on a.[id] = b.[id] and a.[index] = b.[index]
join table3 c on a.[id] = c.[id]
I have to COUNT some rows from multiple tables. Before I can do multiple COUNT I will have to subselect. The problem here is that I need to JOIN some values in order to get the right result.
SELECT
sponsor.Name As SponsorName,
COUNT(participants.[Table]) AS ParticipantCount,
( SELECT
COUNT(guestcards.[Table])
FROM
guestcards
WHERE
guestcards.EventID = #EventID
AND
guestcards.[Table] = #Table
AND
guestcards.SponsorID = participants.SponsorID
-- Here lies the problem.
-- I will need to check up on another value to ensure I get the right rows, but participants.SponsorID is not here because of no join :-(
) AS GuestParticipantCount
FROM
participants
LEFT JOIN
sponsor
ON
sponsor.ID = participants.SponsorID
WHERE
participants.EventID = #EventID
AND
participants.[Table] = #Table
GROUP BY
sponsor.Name
Guestcards table holds: sponsorid, eventid, tablename
Participantstable holds: sponsorid, eventid, tablename
Sponsor table holds: id, name
I need to count how many "Participants" there are and how many "Guestcards" that in a particulary event. These participants have a table (where they should sit) and so does the guestcards. I need to check up on if its the same "table" where they sit.
So I need to count how many are sitting at table "A1" or table "A2" etc.
The result I am after is like:
"Sponsor Name has 5 participants and 3 guestcards. They sit on A1"
I hope I made my self clear
Here's exact equivalent of you query (grouping on sponsor.Name):
SELECT sponsor.name,
COALESCE(SUM(participantCount), 0),
COALESCE(SUM(guestcardsCount), 0)
FROM (
SELECT sponsorId, COUNT(*) AS participantCount
FROM participants
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) p
FULL JOIN
(
SELECT sponsorId, COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) g
ON g.sponsorId = p.sponsorId
FULL JOIN
sponsor s
ON s.id = COALESCE(p.sponsorId, g.sponsorId)
GROUP BY
s.sponsorName
However, I believe you want something more simple:
SELECT sponsorName, participantCount, guestcardsCount
FROM sponsor s
CROSS APLLY
(
SELECT COUNT(*) AS participantCount
FROM participants
WHERE sponsorId = s.id
AND eventId = #eventId
AND [table] = #table
) p
CROSS APLLY
(
SELECT COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE sponsorId = s.id
AND eventId = #eventId
AND [table] = #table
) g
Update:
SELECT sponsor.name,
COALESCE(participantCount, 0),
COALESCE(guestcardsCount, 0)
FROM (
SELECT sponsorId, COUNT(*) AS participantCount
FROM participants
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) p
FULL JOIN
(
SELECT sponsorId, COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) g
ON g.sponsorId = p.sponsorId
JOIN sponsor s
ON s.id = COALESCE(p.sponsorId, g.sponsorId)