I have tables in my db called Items, which are in a relationships with ItemParents table. One item can have one parent, one parent can have multiple items (something like file-folder tree structure). I have a third table called ParentPermissions and in this table I have the permissions if a user depending on its role can view the parent. If he can view the parent, he can view the item too. In this table we save the relationships between the parent and the userroles that CAN NOT view the parent.
For example, for parentId = 1, if there is no data in the table ParentPermissions for this parent, all users can see it. If there is data with values "5,6" in ParentPermissions for parentId = 1, then the users with roles 5 and6 can't see this folder. Users with role 4 can see it.
This is my query:
SELECT count(*)
FROM Items item
INNER JOIN ItemParents parent ON parent.id = item.parentId
LEFT OUTER JOIN ParentPermissions permissions ON permissions.folderId = parent.id
WHERE permissions.view IS NULL OR (permissions.view != CONVERT(VARCHAR(5), #userRole + ',%') AND permissions.view != CONVERT(VARCHAR(5), #userRole) AND permissions.view != CONVERT(VARCHAR(5), '%,' + #userRole))
I want to somehow optimize this query because it is called a lot of times. How do I do that?
You can try the following:
Create temporary table containing only the DENY documents for the current user
CREATE TABLE #CurrentUserParentPermissions
(
ParentID INT
)
INSERT INTO #CurrentUserParentPermissions (ParentID)
SELECT *
FROM ParentPermissions
WHERE (permissions.view != CONVERT(VARCHAR(5), #userRole + ',%') ....
Then apply the left join using this table
SELECT count(*)
FROM Items item
INNER JOIN ItemParents parent ON parent.id = item.parentId
LEFT OUTER JOIN #CurrentUserParentPermissions permissions ON permissions.folderId = parent.id
WHERE permissions.view IS NULL;
Related
I have a bit of a complicated scenario. I have two tables, employee and agency. An employee may or may not have an agency, but if an employee has an agency I want the select clause to check another condition on the agency, but if the employee does not have an agency its fine I want to fetch the employee. I'm not sure how to write the select statement for this. This is what I have come up with so far
select * from employee e left join
agency a on a.id = e.agencyID and a.valid = true;
However the problem with this is that it fetches both employees without agencies which is fine, but it also fetches employees with agencies where a.valid = false. The only option I can think of is to do an union but I'm looking for something more simpler.
A UNION could actually be the solution that performs best, but you can write the query without UNION like this:
select *
from employee e
left join agency a
on a.id = e.agencyID
where coalesce(a.valid, true);
That will accept agencies where valid IS NULL, that is, result rows where the agency part was substituted with NULLs by the outer join.
You want except the condition that both table match(agency.id = employee.agencyID) and also agency.id is false. The following query will express the condition.
SELECT
e.*,
a.*
FROM
employee e
LEFT JOIN agency a ON a.id = e.agencyID
WHERE
NOT EXISTS (
SELECT
1
FROM
agency
WHERE
a.id = e.agencyID
AND a.valid IS FALSE)
ORDER BY
e.id;
I'm facing an issue and hope you will be able to guide me in the right direction.
A facility can have 1..n sub-facilities. Sub-facilities can have 1..n sub-facilities as well. And this can be a never ending relationship.
In the database, we only have keys to connect the parent and first children.
Here's a short schema:
--Facility has : facilityid|parentid
---child1 has : facilityid|parentid to facility
----child2 has : facilityid|parentid to child 1 / no key to main parent
-----child2 has : facilityid|parentid to child 2 / no key to main parent
What I am trying to do is update a column named value and set it to true for a parent and all of its children and sub-children included. Right now, since I am only able to see the first parent and its children but not the sub-children, I can only update the value of those. But that leaves all my sub-children not updated.
Knowing that there is no link between sub-children and main parent, how would I accomplish that?
Here's a query that gives me a parent and all of its children.
WITH attributes AS (
select fa.facilityid, a.id
from caip_attribute a
join caip_facility_attribute fa on a.id = fa.attributeid)
select a.facilityid as parentfacilityid, a.id as parentattributeid, f.id as facilitychildid, fa.attributeid as childattributeid
from attributes a
join caip_facility f on f.parentid = a.facilityid
join caip_facility_attribute fa on fa.facilityid = f.id
join caip_attribute at on at.id = fa.attributeid
where at.definitionTypeKey = 'AUTO_CREATE_PROCESS'
order by f.id asc
The update statement is missing here but this is how I get the values that will later need to be updated.
Thank you!
To update all descendants of a parent you can use a recursive CTE.
Suppose we have a data table with relations set using self-referencing foreign key column parent_id:
CREATE TABLE t (
id SERIAL PRIMARY KEY,
value BOOL DEFAULT FALSE,
parent_id INT,
FOREIGN KEY (parent_id) REFERENCES t(id)
);
First, a query that returns a parent with id = 1 and all it's descendants would be like this:
WITH RECURSIVE children(id, value, parent_id) AS (
SELECT id, value, parent_id FROM t WHERE id = 1
UNION
SELECT t.id, t.value, t.parent_id FROM t
INNER JOIN children ON children.id = t.parent_id
)
SELECT * FROM children
Second, to update a value of parent and all it's descendants we can set value for all rows with ids in our select query:
UPDATE t SET value = true
WHERE id IN (
WITH RECURSIVE children(id, value, parent_id) AS (
SELECT id, value, parent_id FROM t WHERE id = 1
UNION
SELECT t.id, t.value, t.parent_id FROM t
INNER JOIN children ON children.id = t.parent_id
)
SELECT id FROM children
)
Check a demo
I have a many to many relation with three columns, (owner_id,property_id,ownership_perc) and for this table applies (many owners have many properties).
So I would like to find all the owner_id who has many properties (property_id) and connect them with other three tables (Table 1,3,4) in order to get further information for the requested result.
All the tables that I'm using are
Table 1: owner (id_owner,name)
Table 2: owner_property (owner_id,property_id,ownership_perc)
Table 3: property(id_property,building_id)
Table 4: building(id_building,address,region)
So, when I'm trying it like this, the query runs but it returns empty.
SELECT address,region,name
FROM owner_property
JOIN property ON owner_property.property_id = property.id_property
JOIN owner ON owner.id_owner = owner_property.owner_id
JOIN building ON property.building_id=building.id_building
GROUP BY owner_id,address,region,name
HAVING count(owner_id) > 1
ORDER BY owner_id;
Only when I'm trying the code below, it returns the owner_id who has many properties (see image below) but without joining it with the other three tables:
SELECT a.*
FROM owner_property a
JOIN (SELECT owner_id, COUNT(owner_id)
FROM owner_property
GROUP BY owner_id
HAVING COUNT(owner_id)>1) b
ON a.owner_id = b.owner_id
ORDER BY a.owner_id,property_id ASC;
So, is there any suggestion on what I'm doing wrong when I'm joining the tables? Thank you!
This query:
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
returns all the owner_ids with more than 1 property_ids.
If there is a case of duplicates in the combination of owner_id and property_id then instead of COUNT(property_id) use COUNT(DISTINCT property_id) in the HAVING clause.
So join it to the other tables:
SELECT b.address, b.region, o.name
FROM (
SELECT owner_id
FROM owner_property
GROUP BY owner_id
HAVING COUNT(property_id) > 1
) t
INNER JOIN owner_property op ON op.owner_id = t.owner_id
INNER JOIN property p ON op.property_id = p.id_property
INNER JOIN owner o ON o.id_owner = op.owner_id
INNER JOIN building b ON p.building_id = b.id_building
ORDER BY op.owner_id, op.property_id ASC;
Always qualify the column names with the table name/alias.
You can try to use a correlated subquery that counts the ownerships with EXISTS in the WHERE clause.
SELECT b1.address,
b1.region,
o1.name
FROM owner_property op1
INNER JOIN owner o1
ON o1.id_owner = op1.owner_id
INNER JOIN property p1
ON p1.id_property = op1.property_id
INNER JOIN building b1
ON b1.id_building = p1.building_id
WHERE EXISTS (SELECT ''
FROM owner_property op2
WHERE op2.owner_id = op1.owner_id
HAVING count(*) > 1);
This is a very general question. I found some questions and discussions on more specific problems on SO, but I am quite sure, that many of you have already solved this one:
input:
A table that has a tree structure in one field.
An arbitrary id of a database record x.
question:
How can I get the root of the tree of x?
I found out that there should be a way to implement this recursively, but I couldn't achieve it yet.
The root element can be found in the same way as child elements of a given root,
but the query must search in the opposite direction.
Take a look at simple demo: --> http://www.sqlfiddle.com/#!17/fdc8a/1
This query retrieves all childrens of a given root:
WITH RECURSIVE childs( id, parent_id )
AS (
-- get parent
SELECT id, parent_id
FROM tab
WHERE id = 10
UNION ALL
-- get all children
SELECT t.id, t.parent_id
FROM childs c
JOIN tab t
ON t.parent_id = c.id
)
SELECT * from childs;
and this query retrieves all parents of a given child node:
WITH RECURSIVE parents( id, parent_id )
AS (
-- get leaf children
SELECT id, parent_id
FROM tab
WHERE id = 14
UNION ALL
-- get all parents
SELECT t.id, t.parent_id
FROM parents p
JOIN tab t
ON p.parent_id = t.id
)
SELECT * from parents
-- WHERE parent_id is null;
if only the root node is needed, a clause WHERE parent_id IS NULL filters out all except the root.
I have a tree structure table with columns:
id,parent,name.
Given a tree A->B->C,
how could i get the most top parent A's ID according to C's ID?
Especially how to write SQL with "with recursive"?
Thanks!
WITH RECURSIVE q AS
(
SELECT m
FROM mytable m
WHERE id = 'C'
UNION ALL
SELECT m
FROM q
JOIN mytable m
ON m.id = q.parent
)
SELECT (m).*
FROM q
WHERE (m).parent IS NULL
To implement recursive queries, you need a Common Table Expression (CTE).
This query computes ancestors of all parent nodes. Since we want just the top level, we select where level=0.
WITH RECURSIVE Ancestors AS
(
SELECT id, parent, 0 AS level FROM YourTable WHERE parent IS NULL
UNION ALL
SELECT child.id, child.parent, level+1 FROM YourTable child INNER JOIN
Ancestors p ON p.id=child.parent
)
SELECT * FROM Ancestors WHERE a.level=0 AND a.id=C
If you want to fetch all your data, then use an inner join on the id, e.g.
SELECT YourTable.* FROM Ancestors a WHERE a.level=0 AND a.id=C
INNER JOIN YourTable ON YourTable.id = a.id
Assuming a table named "organization" with properties id, name, and parent_organization_id, here is what worked for me to get a list that included top level and parent level org ID's for each level.
WITH RECURSIVE orgs AS (
SELECT
o.id as top_org_id
,null::bigint as parent_org_id
,o.id as org_id
,o.name
,0 AS relative_depth
FROM organization o
UNION
SELECT
allorgs.top_org_id
,childorg.parent_organization_id
,childorg.id
,childorg.name
,allorgs.relative_depth + 1
FROM organization childorg
INNER JOIN orgs allorgs ON allorgs.org_id = childorg.parent_organization_id
) SELECT
*
FROM
orgs order by 1,5;