SQL query optimization practice - sql

I want to pratice my skills in SQL query optimization for small databases.
I have created a very simple database:
I populated it with hunderds of thousands of entries and need some complex, stupid and unoptimized queries to make some tests and practice my optimization skills. Right now I tried to write some queries but they don't make much impact on the database.
Which key words should I use? Could someone help?

I find it hard to come up with queries that are really bad :-) Here are some ideas. Hopefully, others will come up with more. I suggest you put your tables (empty or even with some sample rows) in https://dbfiddle.uk/, where we can try our queries. I don't know for instance, if I don't have any syntax errors in below queries.
Find all videos that are either owned or commented by a user named xyz:
select distinct v.*
from videos v
left join user_have_videos uv on uv.video_id = v.video_id
left join users u1 on u1.user_id = uv.user_id
left join comments c on c.video_id = v.video_id
left join users u2 on u2.user_id = c.user_id
where u1.user_name = :user_name
or u2.user_name = :user_name;
Find all users that own at least three different videos:
select distinct u.*
from users u
left join user_have_videos uv1 on uv1.user_id = u.user_id
left join videos v1 on v1.video_id = uv1.video_id
left join user_have_videos uv2 on uv2.user_id = u.user_id
left join videos v2 on v2.video_id = uv2.video_id
left join user_have_videos uv3 on uv3.user_id = u.user_id
left join videos v3 on v3.video_id = uv3.video_id
where v1.video_id <> v2.video_id
and v1.video_id <> v3.video_id
and v2.video_id <> v1.video_id
and v2.video_id <> v3.video_id
and v3.video_id <> v1.video_id
and v3.video_id <> v2.video_id;
Find all users that commented on John Wayne:
select distinct u.*
from users u
join comments c on c.user_id = u.user_id
where lower(comments) like '%john wayne%';
And here is one where you shall find out what this does and make it better:
with cte(user_id, video_id, num) as
(
select u.user_id, v.video_id, 1
from videos v
join user_have_videos uv on uv.video_id = v.video_id
join users u on u.user_id = uv.user_id
union all
select u.user_id, min(v.video_id), min(cte.num) + 1
from videos v
join user_have_videos uv on uv.video_id = v.video_id
join users u on u.user_id = uv.user_id
join cte on cte.user_id = u.user_id
and cte.video_id < v.video_id
group by u.user_id
)
select distinct u.*
from users u
join cte on cte.user_id = u.user_id and cte.num >= 10;

Related

Multiple SQL jointure

I will try to be the most intelligible possible as I'm a very beginner in SQL.
Here is my issue, I have a database with answers from questionnaries.
Each database have a user.id, unique.id, date, final score. And I would like to extract those data with a simple table which group the score of each questionnary by user.id and by date.
I have tried this :
SELECT A.user_id, SUBSTRING(A.created_at,1,10), A.SCORE, B.SCORE,
C.SCORE, D.SCORE, E.SCORE
FROM A
LEFT JOIN B
ON A.user_id = A.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(B.created_at,1,10)
LEFT JOIN C
ON A.user_id = C.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(C.created_at,1,10)
LEFT JOIN D
ON A.user_id = D.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(D.created_at,1,10)
LEFT JOIN E
ON A.user_id = E.user_id AND SUBSTRING(A.created_at,1,10) = SUBSTRING(E.created_at,1,10)
This is almost successful but I find out if some one did not participate to the A questionnary then I don't have anything about it.
I hope, I have been clear enough.
Thank you all,
You must manage your query with USER table (if you have it), as follow:
SELECT U.user_id, SUBSTRING(A.created_at,1,10), A.SCORE, B.SCORE,
C.SCORE, D.SCORE, E.SCORE
FROM USER U
LEFT JOIN A
ON U.user_id = A.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(A.created_at,1,10)
LEFT JOIN B
ON U.user_id = B.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(B.created_at,1,10)
LEFT JOIN C
ON U.user_id = C.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(C.created_at,1,10)
LEFT JOIN D
ON U.user_id = D.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(D.created_at,1,10)
LEFT JOIN E
ON U.user_id = E.user_id AND SUBSTRING(U.created_at,1,10) = SUBSTRING(E.created_at,1,10)
I would suggest two things, depending on how restricted you are to modify your tables.
Just use one table. Add a column testType or something like this. You can ever foreign key it and use only the keys there. That would make the recordset smaller.
Then put all your results in that one table with the matching keys.
Use UNION if the tables are all the same and have just different entries, then query this union.
There is no need for a JOIN in your case, it's better to adapt the structure.
Otherwise you can try FULL JOIN to keep all the data.

transform union query to join query

I have a simple database schema composed of 3 tables
User
id
name
matricule
Document
id
serial
user_id(owner of document : foreign key to User(id))
User_Document (join table)
user_id
document_id
I want all document serial from both user sources : owner of document and join table . The query is filtered by a list of users matricule
I managed to achieve the desired goal with union query :
select d.serial from Document d
INNER JOIN users u ON u.id = d.user_id
where u.matricule IN ('1234')
UNION
select d.serial from Document d
inner join User_Document ud on d.id = ud.document_id
inner join users u on ud.user_id = u.id
where u.matricule IN ('1234')
How to arrive to the same result with only a join query ? I need as well skip document with no serial ( this case is possible)
Thank you very much
Try this:
SELECT d.serial
FROM Document d
LEFT JOIN User_Document ud
ON d.id = ud.document_id
LEFT JOIN users u
on ud.user_id = u.id
LEFT JOIN users u2
ON d.user_id = u2.id
WHERE d.serial IS NOT NULL
AND
(
ISNULL(u.matricule,'') IN ('1234')
OR ISNULL(u2.matricule,'') IN ('1234')
)
Why do you want to remove the joins? Probably the most efficient query would use exists:
select d.*
from documents d
where d.serial is not null and
(exists (select 1
from users u
where u.id = d.user_id and u.matricule = '1234'
) or
exists (select 1
from user_document ud join
users u
on u.id = d.user_id
where ud.document_id = d.id and u.matricule = '1234'
)
);
For this, you would then want indexes on users(id, matricule), user_documents(document_id, user_id), and documents(serial, user_id, document_id).
The use of indexes saves the elimination of duplicates -- which should be a big win for this type of query.

Postgres hangs when a possible null column value is used in SELECT statement

I'm trying to mash some user data from several tables together in Postgres in order to display information in a table on a web page. Users may or may not have "contact" information, and I'm trying to include the "contact" email address as part of what gets returned in the select query.
The query I'm using boils down to the following:
select u.user_id, r.role_id, u.first_name, u.last_name, c.email, o.state_active, ce.name
from user
join user_role r on u.user_id = r.user_id
join company c on r.company_id = c.company_id
join object_state o on u.state_id = o.state_id
left outer join user_contact uc on u.user_id = uc.user_id and uc.default_flag = 'Y' and uc.status = 'A'
left outer join contact c on uc.contact_id = c.contact_id and c.contact_type like 'E%'
I keep running into problems around the outer joined tables at the bottom. It appears that if I do a SELECT * sort of query, I'll get back everything I expect, but as soon as I run the above query, the statement just hangs and never returns values. If, however, I remove the c.email portion of the select statement, everything returns quickly with no problems at all.
Am I missing some sort of core feature of Postgres where I'm unable to select a column that might be null, or is there something else obvious that I'm completely missing as far as this query goes? I don't understand why it works fine if I don't require the email, but not otherwise.
Could you provide a sqlfiddle with a sample of your database to test our solutions please ?
You can try this :
select u.user_id, r.role_id, u.first_name, u.last_name, COALESCE(c.email,NULL), o.state_active, ce.name
from user
join user_role r on u.user_id = r.user_id
join company c on r.company_id = c.company_id
join object_state o on u.state_id = o.state_id
left outer join user_contact uc on u.user_id = uc.user_id and uc.default_flag = 'Y' and uc.status = 'A'
left outer join contact c on COALESCE(uc.contact_id,-1) = c.contact_id and c.contact_type like 'E%'
Here, you should look at the COALESCE.

SQL SELECT statement with users, roles and rights

Lets say that I have tables:
Users
Users_in_Roles
Roles
Rights_in_Roles
Rights
Keys are standard( UserFk, RoleFk, RightFk)
The question is: how to get all users that are in role with right X (id = 100)
I Have no idea how to touch this problem. Please help and sorry for my english.
SELECT [dbo].[System_Users].[Id]
,[UserName]
,[FirstName]
,[LastName]
,[Email]
,RoleFk
,[dbo].[System_Roles].Name
FROM [dbo].[System_Users]
INNER JOIN [dbo].[System_Roles_System_Users]
ON [dbo].[System_Roles_System_Users].UserFk = [dbo].[System_Users].Id
INNER JOIN [dbo].[System_Roles]
ON [dbo].[System_Roles].Id = [dbo].[System_Roles_System_Users].RoleFk
WHERE ?
I tryied sth like that, could you tell me what iw wrong?
SELECT
DISTINCT System_Users.Id,
System_Users.FullName
FROM System_Users
INNER JOIN Dict_Rights_System_Users
ON System_Users.Id = Dict_Rights_System_Users.UserFk
INNER JOIN System_Roles_System_Users
ON System_Roles_System_Users.UserFk = System_Users.Id
WHERE
RightFk = 136
OR
136 IN (SELECT Dict_Rights_System_Roles.RightFk FROM Dict_Rights_System_Roles WHERE
Dict_Rights_System_Roles.RoleFk = System_Roles_System_Users.RoleFk)
ORDER BY System_Users.FullName ASC
You will need to JOIN the tables on the key relationships. The basic structure will be:
select u.Id,
u.UserName,
u.FirstName,
u.LastName,
u.Email,
r.RoleFk,
r.Name RoleName,
rt.Name RightName
from users u
inner join users_in_roles ur
on u.id = ur.userfk
inner join roles r
on ur.rolefk = r.id
inner join rights_in_roles rr
on r.rolefk = rr.rolefk
inner join rights rt
on rr.rightfk = rt.id
where rt.id = 100
If you need help learning JOIN syntax here is a great reference:
A Visual Explanation of SQL Joins
You can try with this:
SELECT *
FROM Users u
WHERE EXISTS (
SELECT ur.RoleFk
FROM Users_in_Roles ur
WHERE u.UserPk = ur.UserFk
AND EXISTS
(
SELECT 1
FROM Rights_in_Roles rr
WHERE rr.RoleFk = ur.RoleFk
AND rr.RightFk = 100
)
)
OR EXISTS (
SELECT 1
FROM Users_Rights uri
WHERE u.UserPk = uri.UserFk
AND uri.RightFk = 100
)
Note that the above query doesn't return RoleFk and Name for the role.
Another approach would be:
SELECT u.Id
,u.UserName
,u.FirstName
,u.LastName
,u.Email
,rr.RoleFk
,r.Name
FROM Users u
-- get users that are in role that has right
LEFT JOIN
Users_in_Roles ur ON
ur.UserFk = u.UserPk
LEFT JOIN
Rights_in_Roles rr ON
rr.RoleFk = ur.RoleFk
AND rr.RightFk = 100
LEFT JOIN
Rights r ON
r.RolePk = rr.RoleFk
-- get users that have a right granted to them directly
LEFT JOIN
Users_Rights uri ON
u.UserPk = uri.UserFk
AND uri.RightFk = 100
WHERE rr.RoleFk IS NOT NULL OR uri.UserFk IS NOT NULL

SQL join help for friend list

I have three database tables: users, user_profiles and friends:
users
id
username
password
user_profiles
id
user_id
full_name
friends
id
usera_id
userb_id
What would be a query which finds the friends list of any user and also joins the table users and user_profiles to get the profile and user information of that friend?
Use:
SELECT f.username,
up.*
FROM USERS f
JOIN USER_PROFILES up ON up.user_id = f.id
JOIN FRIENDS fr ON fr.userb_id = f.id
JOIN USERS u ON u.id = fr.usera_id
WHERE u.username = ?
...assuming userb_id is the friend id.
This may not be the best way to do it, but this felt like the logical way:
select a.id , a.friend_id ,
Users.username
from
( SELECT id , IF(usera_id = 1, userb_id , usera_id) friend_id
FROM friends
where usera_id = 1 OR userb_id = 1 ) a
left join Users on a.friend_id = Users.id
this uses a mySQL function so probably wont work in Oracle/MSSQL
Falling back on bad habits (not using JOIN notation in the FROM clause):
SELECT a.id, a.username, a.full_name,
b.id, b.username, b.full_name
FROM friends AS f, users AS ua, users AS ub,
user_profiles AS a, user_profiles AS b
WHERE f.usera_id = ua.id
AND f.userb_id = ub.id
AND a.user_id = ua.id
AND b.user_id = ub.id
The key point is using table aliases (all those 'AS' clauses) and referencing the same table more than once when necessary.
Someone could write this with JOIN instead.
Some modification to eugene y's answer, will this work?
SELECT * FROM users u
JOIN friends f ON (f.userb_id = u.id OR f.usera_id = u.id)
JOIN user_profiles p ON u.id = p.user_id
WHERE u.id = ?