Postgresql WHERE clause using conditional sub-queries - postgresql

I have a situation where each of the clients has users and each user can access to information about one or more branches.
We also have sys admins who can see everything and in database don't have any sites assigned to them. It just says the user is sys admin, so our system does not restrict the access.
I need to make a database query where I extract the list of branches the user has access to, but if the user is sys admin, I want to extract the list of all branches in the system.
I was trying something like this, but it does not work:
Select sites.name, sites.id
FROM sites
WHERE
sites.id IN (
CASE
WHEN (select u.level FROM users "u" WHERE u.username = 'JohnBrown') ='ROLE_SYSTEM_ADMIN'
THEN
(select id FROM sites)
ELSE
(select s2.id FROM users_have_sites uhs2
left join users u2 ON u2.id = uhs2.user_id
left join sites s2 ON s2.id = uhs2.site_id
where u2.username = 'JonhBrown')
END
)
I am getting this error:
ERROR: more than one row returned by a subquery used as an expression

I think something like this would work for you:
SELECT s.name, s.id
FROM sites s
LEFT JOIN users_have_sites uhs ON uhs.site_id = s.id
LEFT JOIN users u ON u.id = uhs.user_id AND u.username = 'JohnBrown'
WHERE (CASE WHEN (SELECT u.level FROM users WHERE u.username = 'JohnBrown') = 'ROLE_SYSTEM_ADMIN'
THEN TRUE ELSE FALSE END
OR u.id IS NOT NULL);
The LEFT JOINs do not filter out records from the sites table like an INNER JOIN would, so any site that meets either of the conditions in the WHERE clause will be in the result. This means that if your subquery shows that the user is a sys admind or if there is a record for that user and site is found in the users_have_sites table, those sites will be in the result set.
EDIT: Another fairly easy to read solution would be something like this:
SELECT s.name, s.id
FROM sites s,
users_have_sites uhs,
users u
WHERE u.username = 'JohnBrown'
AND (u.level = 'ROLE_SYSTEM_ADMIN'
OR (s.id = uhs.site_id AND u.id = uhs.user_id))
GROUP BY s.name, s.id;
The downside of this query is that it uses implicit joins which are not used very much any more. They are generally seen as an older way of doing things and can be less efficient. This will join all rows of on table to all rows of another table and then all of your filtering (and what you would generally think of as join conditions) are all in the WHERE clause. These typed of joins can be less efficient but this one should not be as the WHERE clause makes sure that only 1 result per site.

I think that this does what you want:
select s.name, s.id
from sites s
inner join users u on u.username = 'JohnBrown'
where
u.level = 'ROLE_SYSTEM_ADMIN'
or exists (
select 1
from users_have_sites uhs
where uhs.site_id = s.id and uhs.user_id = u.id
)
Here is another version of the query that you may find easier to follow (I do):
select s.name, s.id
from users u
inner join sites s
on u.level = 'ROLE_SYSTEM_ADMIN'
or exists (
select 1
from users_have_sites uhs
where uhs.site_id = s.id and uhs.user_id = u.id
)
where u.username = 'JohnBrown'

Related

postgresql left join but dont fetch if matching condition found

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;

Replacing nested SELECT

How can I make postgreSQL query like this:
SELECT event_id, user_id FROM public."point"
WHERE user_id = (SELECT id FROM public."user"
WHERE email='test#gmail.com')
with JOINstatement and without nested SELECT statement. Above works but I think it is not optimal. Thanks for your answers.
For your particular case, this should work:
SELECT p.event_id, p.user_id
FROM public."point" p JOIN
public."user" u
ON p.user_id = u.id
WHERE u.email = 'test#gmail.com';
In general, when switching between JOIN and IN, you need to be careful about duplicates. So the general solution would be:
SELECT p.event_id, p.user_id
FROM public."point" p JOIN
(SELECT DISTINCT u.id
FROM public."user" u
WHERE u.email = 'test#gmail.com'
) u
ON p.user_id = u.id ;
But the id is probably already unique in user.

user information and the course name with time spend in a course

Can anybody help me out with the code to get the user's firstname, lastname, the course name and the time spend by the user on that course in moodle 2.6? Using configurable reports doesnot give me the exact solution.
You can use the following sql to find the course participants.
SELECT u.id, u.username, u.firstname, u.lastname FROM mdl_user u JOIN (SELECT DISTINCT eu1_u.id FROM mdl_user eu1_u JOIN mdl_user_enrolments eu1_ue ON eu1_ue.userid = eu1_u.id JOIN mdl_enrol eu1_e ON (eu1_e.id = eu1_ue.enrolid AND eu1_e.courseid = 4) WHERE eu1_u.deleted = 0 AND eu1_u.id <> 1 ) e ON e.id = u.id LEFT JOIN mdl_user_lastaccess ul ON (ul.userid = u.id AND ul.courseid = 4) LEFT JOIN mdl_context ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = 30) ORDER BY u.lastaccess DESC;
There are two ways to find the time spend in a course:
If course completion is enabled and the user has completed the course, then the time spend in the course will be the difference between the timestarted and timecompleted fields in mdl_course_completions table.
You can also calculate the time spent in a course from moodle logs.
hope this helps.

Subquery in JPA

I am trying to write the following SQL query as a JPA query. The SQL query works (MySQL database) but I don't know how to translate it. I get a error token right after the first FROM. There are probably other errors here too because I was not able to find any guides on how to do sub-queries in the from part, aliasing and so on.
SQL query
SELECT tbl.* from (
SELECT u.*, COUNT(u.id) AS question_count FROM app_user AS u
INNER JOIN question AS q ON u.id = q.user_id GROUP BY u.id
) AS tbl ORDER BY tbl.question_count DESC LIMIT 10;
JPA query:
SELECT tbl FROM (SELECT u, COUNT(u.id) question_count FROM User u
INNER JOIN u.questions q ON u.id = q.user_id GROUP BY u.id) tbl
ORDER BY tbl.question_count LIMIT 10")
I can't test this with anything right now, but something along the lines of:
final String queryStr = "SELECT u, COUNT(u.id) FROM User u, Questions q WHERE u.id = q.user_id GROUP BY u.id ORDER BY COUNT(u.id) DESC";
Query query = em().createQuery(queryStr);
query.setMaxResults(10);
List<Object[]> results = query.getResultList(); //Index [0] will contain the User-object, [1] will contain Long with result of COUNT(u.id)

MS Access INNER JOIN most recent entry

I'm having some trouble trying to get Microsoft Access 2007 to accept my SQL query but it keeps throwing syntax errors at me that don't help me correct the problem.
I have two tables, let's call them Customers and Orders for ease.
I need some customer details, but also a few details from the most recent order. I currently have a query like this:
SELECT c.ID, c.Name, c.Address, o.ID, o.Date, o.TotalPrice
FROM Customers c
INNER JOIN Orders o
ON c.ID = o.CustomerID
AND o.ID = (SELECT TOP 1 ID FROM Orders WHERE CustomerID = c.ID ORDER BY Date DESC)
To me, it appears valid, but Access keeps throwing 'syntax error's at me and when I hit OK, it selects a piece of the SQL text that doesn't even relate to it.
If I take the extra SELECT clause out it works but is obviously not what I need.
Any ideas?
You cannot use AND in that way in MS Access, change it to WHERE. In addition, you have two reserved words in your column (field) names - Name, Date. These should be enclosed in square brackets when not prefixed by a table name or alias, or better, renamed.
SELECT c.ID, c.Name, c.Address, o.ID, o.Date, o.TotalPrice
FROM Customers c
INNER JOIN Orders o
ON c.ID = o.CustomerID
WHERE o.ID = (
SELECT TOP 1 ID FROM Orders
WHERE CustomerID = c.ID ORDER BY [Date] DESC)
I worked out how to do it in Microsoft Access. You INNER JOIN on a pre-sorted sub-query. That way you don't have to do multiple ON conditions which aren't supported.
SELECT c.ID, c.Name, c.Address, o.OrderNo, o.OrderDate, o.TotalPrice
FROM Customers c
INNER JOIN (SELECT * FROM Orders ORDER BY OrderDate DESC) o
ON c.ID = o.CustomerID
How efficient this is another story, but it works...