SQL - How to optimize the query - sql

I have wrote the following query which extracts all users which are in child departments of current user's department.
Current user cames from client app, but I Decalred it here in SQL for tests reason.
DECLARE #UserID INT = 72
SELECT *
FROM users
WHERE Department_Id IN (
SELECT DISTINCT Id /*, idp*/
FROM departments
WHERE idp IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)
OR Department_Id IN (
SELECT DISTINCT idp
FROM departments
WHERE idp IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)
I wanted to select the Id and the idp from departments to have a short query, but when i use this way it returns me an SQL error :
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
This is because my list should contain only one column, but not 2.
Please advice me any way to reduce this query, especially the second part (after OR) which is a copy-paste of first (before OR)
Thank you.

Try using EXISTS like this
SELECT *
FROM users u
WHERE EXISTS( SELECT *
FROM departments
WHERE idp IN (SELECT Department_Id FROM users WHERE Id = #UserID)
AND (id = u.Department_Id
OR idp = u.Department_Id) )

A few thoughts...
Nested IN subqueries are unlikely to be friendly.
You don't need DISTINCT when using IN
I'd use GROUP BY to deal with the 1:many relationships, but as your answer is using an alternative structure, I'll try to keep close to what you have...
DECLARE #UserID INT = 72
SELECT
*
FROM
users AS associates
WHERE
EXISTS (
SELECT
*
FROM
users
INNER JOIN
departments
ON departments.idp = users.Department_Id
WHERE
users.id = #user_id
AND ( departments.id = associates.department_id
OR departments.idp = associates.department_id)
)
If you did use the GROUP BY approach, you avoid sub-queries and correlated-sub-queries all together...
DECLARE #UserID INT = 72
SELECT
associates.id
FROM
users
INNER JOIN
departments
ON departments.idp = users.Department_Id
INNER JOIN
users AS associates
ON associates.department_id = departments.id
OR associates.department_id = departments.idp
WHERE
users.id = #user_id
GROUP BY
associates.id
If there any other fields in associates that you need, just add them to the SELECT and the GROUP BY.

SELECT *
FROM users
WHERE Department_Id IN (
SELECT myId FROM
( SELECT Id AS myId
FROM departments
UNION ALL
SELECT idp AS myId
FROM departments
) A
WHERE A.myId IN (
SELECT Department_Id
FROM users
WHERE Id = #UserID
)
)

Related

SQL SUBQUERY ON SELF

I have a table with the following data:
licence_number
date_of_birth
organisation
I want to do a query where:
Get the licence_numbers and dobs in organisation1 where the same
licence numbers and dobs are in organisation2.
I know it cant be that hard, but im struggling.
You can group by license_number and date_of_birth where organization is set to either of the two interesting organizations, and count how many distinct organizations there are in a group.
If there are two out of two possible in a single group, you have a hit.
SELECT license_number, date_of_birth
FROM mytable
WHERE organisation IN ('organisation1', 'organisation2')
GROUP BY license_number, date_of_birth
HAVING COUNT(DISTINCT organisation) = 2;
...or you can use INTERSECT;
SELECT license_number, date_of_birth
FROM mytable WHERE organisation = 'organisation1'
INTERSECT
SELECT license_number, date_of_birth
FROM mytable WHERE organisation = 'organisation2'
An SQLfiddle to test both.
select
from t t0
where
organization = 'organization1'
and
exists (
select 1
from t
where
organization = 'organization2'
and
licence_number = t0.licence_number
and
date_of_birth = t0.date_of_birth
)
You can just self join the table where the licence number and the dates are the same but the organisation isn't:
SELECT DISTINCT p1.licence_number, p1.date_of_birth
FROM people p1
INNER JOIN people p2
ON p1.licence_number = p2.licence_number AND
p1.date_of_birth = p2.date_of_birth AND
p1.organisation <> p2.organisation
SQL Fiddle here
Is it a JOIN or 1 table?
Select
[licence_number],
[date_of_birth],
[organisation]
From YourTable
Where organisation1 = organisation2
--OR
Select
[licence_number],
[date_of_birth],
[organisation]
From YourTable
Where organisation1 IN ('organisation2','organisation3','organisation3')
Order By [licence_number]

How to exclude some rows from a SELECT Statement when 2 keys match?

I have 3 tables in my system: Courses, Scores and Users. Scores is a table which has the test results for each course and each user. So I have the ScoreID, The CourseID the UserID and the Score itself.
I want to show in some page the list of courses that the user didn't finished yet. So I want it to show all the courses excluding those the user has records in the Scores table (meaning he already has finished it).
How do I exclude the rows from a SELECT statement when certain CourseID and UserID match at the same time?
Assuming that this is for just one user, Mark Bannister's answer can be simplified a little...
SELECT
*
FROM
Courses
WHERE
NOT EXISTS (SELECT * FROM Scores WHERE CourseID = Courses.CourseID AND UserID = #userID)
Try:
select *
from Courses c
cross join Users u
where not exists
(select null from Scores s where s.CourseID = c.CourseID and s.UserID = u.UserID)
select *
from Courses
where not exists
(
select null from Scores where Scores.CourseID = Courses.CourseID
and Scores.UserID = Courses.UserID
)
Assuming you are using SQL Server you can
CROSS APPLY the courses and users, creating every possible combinations of courses and users
use NOT EXISTS to filter out those records where a UserID exists.
SQL Statement
SELECT *
FROM Courses c
CROSS APPLY Users u
WHERE NOT EXISTS (
SELECT *
FROM Scores
WHERE UserID = u.UserID
AND ScoreID = c.ScoreID
)
In case you are using any other DBMS, following should work on most DBMS's
SELECT *
FROM Courses AS c
, Users AS u
WHERE NOT EXISTS (
SELECT *
FROM Scores
WHERE UserID = u.UserID
AND ScoreID = c.ScoreID
)

MySQL- complex data query in a single statement

Consider the following structure :
alt text http://aeon-dev.org/pap/pap_db.png
Ignore the table user_token.
Now, imagine that you need to get all the roles related to an user, wich may be through it's related groups or directly related to him. In case the same role appears related to a group and the user directly, the role related to the user will prevail over the role given by the group.
Is there any chance this could be done in a single query?
Cheers!
Use:
SELECT DISTINCT r.name AS role_name
FROM USER u
LEFT JOIN USER_HAS_ROLE uhr ON uhr.user_id = u.id
LEFT JOIN USER_HAS_GROUP uhg ON uhg.user_id = u.id
LEFT JOIN GROUP_HAS_ROLE ghr ON ghr.group_id = uhg.group_id
LEFT JOIN ROLE r ON r.id = uhr.role_id
OR r.id = ghr.role_id
WHERE u.username = ?
try this:
Select role_id
From user_has_role
Where userId = #UserId
Union
Select role_id
From user_has_group g
Join Group_has_Role gr
On gr.GroupId = g.GroupId
Where userId = #UserId
This query will get every role a single user has either directly or given by a group
SELECT * FROM ROLE WHERE ID IN (
SELECT ROLE_ID
FROM USER_HAS_ROLE
WHERE USER_ID = 1
UNION
SELECT ROLE_ID
FROM USER_HAS_GROUP UG
INNER JOIN GROUP_HAS_ROLE GR ON UG.GROUP_ID = GR.GROUP_ID
WHERE USER_ID = 1
)
You could find all distinct roles for user 1 like:
select distinct role_id
from (
select uhr.user_id
, uhr.role_id
from user_has_role uhr
union all
select uhg.user_id
, ghr.role_id
from user_has_group uhg
join group_has_role ghr
on ghr.group_id = uhg.group_id
where not exists
(
select *
from user_has_role uhr
where uhr.user_id = uhg.user_id
and uhr.role_id = ghr.role_id
)
) user2role
where user_id = 1
Not sure how a role related to a user should "prevail", but you can assign priorities to them in the union.

how can i rewrite a select query in this situation

Here are two table in parent/child relationship.
What i need to do is to select students with there average mark:
CREATE TABLE dbo.Students(
Id int NOT NULL,
Name varchar(15) NOT NULL,
CONSTRAINT PK_Students PRIMARY KEY CLUSTERED
(
CREATE TABLE [dbo].[Results](
Id int NOT NULL,
Subject varchar(15) NOT NULL,
Mark int NOT NULL
)
ALTER TABLE [dbo].[Results] WITH CHECK ADD CONSTRAINT [FK_Results_Students] FOREIGN KEY([Id])
REFERENCES [dbo].[Students] ([Id])
I wrote a query like this :
SELECT name , coalesce(avg(r.[mark]),0) as Avmark
FROM students s
LEFT JOIN results r ON s.[id]=r.[id]
GROUP BY s.[name]
ORDER BY ISNULL(AVG(r.[mark]),0) DESC;
But the result is that all of students with there avg mark in desc order.What i need is to restrict result set with students that have the highest average mark agaist other,i.e.if the are two students with avg mark 50 and 1 with 25 i need to display only those students with 50.If there are only one student with highest avg mark- only he must appear in result set.How can i do this in best way?
SQL Server 2005+, using CTEs:
WITH grade_average AS (
SELECT r.id,
AVG(r.mark) 'avg_mark'
FROM RESULTS r
GROUP BY r.id),
highest_average AS (
SELECT MAX(ga.avg_mark) 'highest_avg_mark'
FROM grade_average ga)
SELECT DISTINCT
s.name,
ga.avg_mark
FROM STUDENTS s
JOIN grade_average ga ON ha.id = s.id
JOIN highest_average ha ON ha.highest_avg_mark = ga.avg_mark
Non-CTE equivalent:
SELECT DISTINCT
s.name,
ga.avg_mark
FROM STUDENTS s
JOIN (SELECT r.id,
AVG(r.mark) 'avg_mark'
FROM RESULTS r
GROUP BY r.id) ga ON ha.id = s.id
JOIN SELECT MAX(ga.avg_mark) 'highest_avg_mark'
FROM (SELECT r.id,
AVG(r.mark) 'avg_mark'
FROM RESULTS r
GROUP BY r.id) ga) ha ON ha.highest_avg_mark = ga.avg_mark
If you're using a relatively new version of MS SQL server, you can use WITH to make this simple to write:
WITH T AS (
SELECT
name,
coalesce(avg(r.[mark]),0) as mark
FROM students s
LEFT JOIN results r ON s.[id]=r.[id]
GROUP BY s.[name])
SELECT name as 'ФИО', mark as 'Средний бал'
FROM T
WHERE T.mark = (SELECT MAX(mark) from T)
Is it as simple as this? For all versions of SQL Server 2000+
SELECT TOP 1 WITH TIES
name, ISNULL(avg(r.[mark]),0) as AvMark
FROM
students s
LEFT JOIN
results r ON s.[id]=r.[id]
GROUP BY
s.[name]
ORDER BY
ISNULL(avg(r.[mark]),0) DESC;
SELECT name as 'ФИО',
coalesce(avg(r.[mark]),0) as 'Средний бал'
FROM students s
LEFT JOIN results r
ON s.[id]=r.[id]
GROUP BY s.[name]
HAVING AVG(r.[mark]) >= 50
ORDER BY ISNULL(AVG(r.[mark]),0) DESC
about HAVING clause

What's wrong with this MySQL query? SELECT * AS `x`, how to use x again later?

The following MySQL query:
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
where `sID` in (select `siteID` from `sites` where `foo` = "bar")
order by `timestamp` desc limit 100
…returns an error:
Unknown column 'sID' in 'IN/ALL/ANY subquery'
I don't understand what I'm doing wrong here. The sID thing is not supposed to be a column, but the 'alias' (what is this called?) I created by executing (select siteID from users where userID = uID) as sID. And it’s not even inside the IN subquery.
Any ideas?
Edit: #Roland: Thanks for your comment. I have three tables, actions, users and sites. The table actions contains a userID field, which corresponds to an entry in the users table. Every user in this table (users) has a siteID.
I'm trying to select the latest actions from the actions table, and link them to the users and sites table to find out who performed those actions, and on which site. Hope that makes sense :)
You either need to enclose it into a subquery:
SELECT *
FROM (
SELECT userID as uID, (select siteID from users where userID = actions.userID) as sID,
FROM actions
) q
WHERE sID IN (select siteID from sites where foo = "bar")
ORDER BY
timestamp DESC
LIMIT 100
, or, better, rewrite it as a JOIN
SELECT a.userId, u.siteID
FROM actions a
JOIN users u
ON u.userID = a.userID
WHERE siteID IN
(
SELECT siteID
FROM sites
WHERE foo = 'bar'
)
ORDER BY
timestamp DESC
LIMIT 100
Create the following indexes:
actions (timestamp)
users (userId)
sites (foo, siteID)
The column alias is not established until the query processor finishes the Select clause, and buiulds the first intermediate result set, so it can only be referenced in a group By, (since the group By clause operates on that intermediate result set) if you want ot use it this way, puit the alias inside the sub-query, then it will be in the resultset generated by the subquery, and therefore accessible to the outer query. To illustrate
(This is not the simplest way to do this query but it illustrates how to establish and use a column alias from a subquery)
select a.userID as uID, z.Sid
from actions a
Join (select userID, siteID as sid1 from users) Z,
On z.userID = a.userID
where Z.sID in (select siteID from sites where foo = "bar")
order by timestamp desc limit 100
Try the following:
SELECT
a.userID as uID
,u.siteID as sID
FROM
actions as a
INNER JOIN
users as u ON u.userID=a.userID
WHERE
u.siteID IN (SELECT siteID FROM sites WHERE foo = 'bar')
ORDER BY
a.timestamp DESC
LIMIT 100
I think the reason for the error is that the alias isn't available to the WHERE instruction, which is why we have HAVING.
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
HAVING `sID` in (select `siteID` from `sites` where `foo` = "bar")
order by `timestamp` desc limit 100
Though i also agree with the other answers that your query could be better structured.
Try the following
SELECT
a.userID as uID
,u.siteID as sID
FROM
actions as a
INNER JOIN
users as u ON u.userID = a.userID
INNER JOIN
sites as s ON u.siteID = s.siteID
WHERE
s.foo = 'bar'
ORDER BY
a.timestamp DESC
LIMIT 100
If you wish to use a field from the select section later you can try a subselect
SELECT One,
Two,
One + Two as Three
FROM (
SELECT 1 AS One,
2 as Two
) sub
I don't know whether this was not in the SQL standard 11 years ago, but I found it the easiest way to use HAVING:
select `userID` as uID,
(select `siteID` from `users` where `userID` = uID) as `sID`,
from `actions`
order by `timestamp` desc limit 100
HAVING `sID` in (select `siteID` from `sites` where `foo` = "bar")