I'm practicing MySQL for an upcoming exam and need some help.
I have this db:
USER(Code, Name, Surname, Age)
THEATRE(Name, City, Capacity)
SUBSCRIPTION(ID, UserCode, TheatreName, Amount)
With these referential integrity constraints:
SUBSCRIPTION.UserCode->USER.Code
SUBSCRIPTION.TheatreName->THEATRE.Name
For exercise I need to write the query which determines code, name and surname of the users older than 50 and who has more than one subscription WITHOUT using the COUNT function.
I know that maybe a self-join could help but I really don't know how. Can anyone help me? Thank you very much.
You can use
EXISTS:
SELECT u.Code, u.Name, u.Surname
FROM USER u
WHERE u.Age > 50
AND EXISTS (
SELECT 1 FROM SUBSCRIPTION s WHERE u.Code = s.UserCode
)
Or JOIN
SELECT DISTINCT u.Code, u.Name, u.Surname
FROM USER u
JOIN SUBSCRIPTION s
ON u.Code = s.UserCode
WHERE u.Age > 50
Edited:
SELECT DISTINCT u.Code, u.Name, u.Surname
FROM USER u
JOIN SUBSCRIPTION s1
ON u.Code = s1.UserCode
JOIN SUBSCRIPTION s2
ON u.Code = s2.UserCode
WHERE s1.ID <> s2.ID
AND u.Age > 50
I believe the simplest way to accomplish this is to essentially redesign the count function into a sum function with a case statement thusly:
SELECT
u.NAME
, u.SURNAME
, u.CODE
, SUM(CASE WHEN t.SUBSCRIPTION IS NOT NULL THEN 1 ELSE 0 END) as TOTAL_SUBSCRIPTIONS -- IDENTICAL TO COUNT(s.*)
, COUNT(s.*) -- SHOULD MATCH THE TOTAL_SUBSCRIPTIONS
FROM
USER AS u
LEFT JOIN SUBSCRIPTION AS s
ON u.CODE = s.USERCODE
-- LEFT JOIN THEATRE AS t -- commented because I don't see a requirement for this table to be brought forward.
-- ON s.THEATRENAME = t.NAME
WHERE u.AGE > 50
HAVING SUM(CASE WHEN t.SUBSCRIPTION IS NOT NULL THEN 1 ELSE 0 END) > 1
Without using a CASE statment:
SELECT
u.NAME
, u.SURNAME
, u.CODE
, SUM( (select SUM(1) from SUBSCRIPTION WHERE s.USERCODE = u.CODE) ) as TOTAL_SUBSCRIPTIONS -- IDENTICAL TO COUNT(s.*)
FROM
USER AS u
WHERE u.AGE > 50
Related
I have sql script that needed further tweaking to produce a simpler report as shown below. I am looking for the results to be grouped on username.
userName, weeknum, and #of wins
select
p.userID,
s.gameID,
p.pickID,
u.userName,
s.weekNum,
s.homeID,
s.homeScore,
s.visitorID,
s.visitorScore
from nflp_picks p
inner join nflp_users u
on p.userID = u.userID
inner join nflp_schedule s
on p.gameID = s.gameID
where s.weekNum = 6
and u.userName <> 'admin'
order by p.userID, s.gameTimeEastern, s.gameID;
Make you current query as derived table, then do like this:
SELECT userName,
weeknum,
SUM(CASE WHEN pickID=homeID
AND homeScore > visitorScore THEN 1
WHEN pickID=visitorID
AND visitorScore > homeScore THEN 1
ELSE 0 END) AS "# of wins"
FROM
(SELECT
p.userID,
s.gameID,
p.pickID,
u.userName,
s.weekNum,
s.homeID,
s.homeScore,
s.visitorID,
s.visitorScore
FROM nflp_picks p
INNER JOIN nflp_users u
ON p.userID = u.userID
INNER JOIN nflp_schedule s
ON p.gameID = s.gameID
WHERE s.weekNum = 6
AND u.userName <> 'admin'
ORDER BY p.userID, s.gameTimeEastern, s.gameID) a
GROUP BY userName, weeknum;
SUM() over CASE expression checking if pickID value is the same as the winning team.
Here's a trimmed down fiddle example
Currently I have the following two tables (shown here somewhat simplified):
users: (userId, username, email, name)
permissions: (id, userId, permission)
The permission field may contain, for example, something like canRead, canWrite, canDelete, etc. Each user can have any number of permissions.
I have now written a query to display all users and to get the permissions of each user as an extra boolean column. For example, this is what a result of the SQL script should look like for two users registered in the system:
userId: 1, username: "testuser", email: "test#test.com", name: "testname", canRead: 1, canWrite: 0, canDelete: 0
userId: 2, username: "anotheruser", email: "second#test.com", name: "anothername", canRead: 1, canWrite: 1, canDelete: 1
My current query looks like this:
SELECT users.userId, users.username, users.email, users.name,
CASE WHEN(
SELECT DISTINCT permissions.permission
FROM permissions
WHERE permissions.permission = 'canRead' AND users.userId = permissions.userId
) IS NULL THEN 0 ELSE 1 END AS 'canRead',
CASE WHEN(
SELECT DISTINCT permissions.permission
FROM permissions
WHERE permissions.permission = 'canWrite' AND users.userId = permissions.userId
) IS NULL THEN 0 ELSE 1 END AS 'canWrite',
CASE WHEN(
SELECT DISTINCT permissions.permission
FROM permissions
WHERE permissions.permission = 'canDelete' AND users.userId = permissions.userId
) IS NULL THEN 0 ELSE 1 END AS 'canDelete',
FROM users
LEFT JOIN permissions
ON permissions.userId = users.userId
GROUP BY users.userId
The more permissions I add the slower the query becomes. How can I write this query in a more simplified and especially performant way?
You can done this by single JOIN and some data manipulation:
SELECT
users.userId, users.username, users.email, users.name,
COUNT(IF(permissions.permission = 'canRead', 1, null)) > 0 'canRead',
COUNT(IF(permissions.permission = 'canWrite', 1, null)) > 0 'canWrite',
COUNT(IF(permissions.permission = 'canDelete', 1, null)) > 0 'canDelete'
FROM users
LEFT JOIN permissions ON users.userId = permissions.userId
GROUP BY users.userId, users.username, users.email, users.name;
MySQL JOIN fiddle
Simplify it. Max 'em.
SELECT u.userId, u.username, u.email, u.name
, MAX(CASE WHEN p.permission = 'canRead' THEN 1 ELSE 0 END) AS canRead
, MAX(CASE WHEN p.permission = 'canWrite' THEN 1 ELSE 0 END) AS canWrite
, MAX(CASE WHEN p.permission = 'canDelete' THEN 1 ELSE 0 END) AS canDelete
FROM users u
LEFT JOIN permissions p
ON p.userId = u.userId
GROUP BY u.userId, u.username, u.email, u.name;
Remove this (it seems to serve no purpose, but takes time):
LEFT JOIN permissions ON permissions.userId = users.userId
This index on permissions: INDEX(userId, permission) may help. (See caveat below.)
Without the LEFT JOIN, the GROUP BY becomes unnecessary; get rid of it.
Switch to EXISTS. For example
(
SELECT DISTINCT permissions.permission
FROM permissions
WHERE permissions.permission = 'canRead'
AND users.userId = permissions.userId
) IS NULL THEN 0 ELSE 1 END AS 'canRead',
-->
EXISTS ( SELECT 1 FROM permissions
WHERE permissions.permission = 'canRead'
AND users.userId = permissions.userId
) AS 'canRead',
Exists() is likely to be faster than the other Answers because EXISTS is a "semi-join", meaning that it stops when it discovers a matching row. The others have to check all the relevant rows, which takes longer.
"users: (userId, username, email, name) permissions: (id, userId, permission)" implies that you have two unique keys on users? Is id needed at all? If you get rid of id and make userId the PRIMARY KEY, then my index recommendation above becomes redundant.
For further discussion, please provide SHOW CREATE TABLE for each table.
I try to improve this query but I do not made successefully. I use a some left join and subquerys (I don't know another form) We try to get all bookings from users with certain status and the number of bookings multiples related with this booking and get from the log user the origin. The query is slow even if use a limit clausure. I Appreciate all the help can you give me.
This is the query:
SELECT DISTINCT b.uneaque_id, b.id, b.status, b.route_status, b.username, b.purpose, b.transfer, b.pickup_date, b.pickup_time, b.return_time, b.amount, b.default_location, b.start_address_route_comments, b.start_address_route, b.end_address_route_comments, b.end_address_route, u1.first_name, u1.last_name, b.transaction_Id, b.manual_payment, mr.AddressCount, lu.origin
FROM bookingdetails b
LEFT JOIN log_users lu ON lu.uneaque_id = b.uneaque_id AND lu.command_type = 'NEW BOOKING'
LEFT JOIN (
SELECT booking_id, COUNT(*) AS AddressCount FROM booking_multiple_rides GROUP BY booking_id
) mr ON b.id = mr.booking_id,
userdetails u1 WHERE b.username = u1.email
AND u1.user_status IN ('Active', 'Blocked', 'Not_Active')
AND b.default_location = 1
PD: Sorry for my english.
You have a ON b.id = mr.booking_id, userdetails u1 WHERE
you should change with a proper inner join
SELECT DISTINCT b.uneaque_id
, b.id, b.status
, b.route_status
, b.username
, b.purpose
, b.transfer
, b.pickup_date
, b.pickup_time
, b.return_time
, b.amount
, b.default_location
, b.start_address_route_comments
, b.start_address_route
, b.end_address_route_comments
, b.end_address_route
, u1.first_name
, u1.last_name
, b.transaction_Id
, b.manual_payment
, mr.AddressCount
, lu.origin
FROM bookingdetails b
LEFT JOIN log_users lu ON lu.uneaque_id = b.uneaque_id AND lu.command_type = 'NEW BOOKING'
LEFT JOIN (
SELECT booking_id
, COUNT(*) AS AddressCount
FROM booking_multiple_rides GROUP BY booking_id
) mr ON b.id = mr.booking_id
INNER JOIN userdetails u1 ON b.username = u1.email
AND u1.user_status IN ('Active', 'Blocked', 'Not_Active')
AND b.default_location = 1
and be sure you have proper index on
table bookingdetails a composite index on columns ( uneaque_id , id, default_location)
table log_users a composite index on columns (uneaque_id, command_type)
table userdetails a cmposite index on columns (email,user_status )
Tip 1.
Hiding a derived table in a LEFT JOIN is a prescription for sluggishness.
Replace
mr.AddressCount
with
( SELECT COUNT(*)
FROM booking_multiple_rides
WHERE booking_id = b.id
GROUP BY booking_id ) AS AddressCount
and get rid of the LEFT JOIN ( SELECT ... ) AS mr ON ..
Tip 2 Use explicit JOINs, no the old-fashioned "comma-join":
JOiN userdetails u1
ON b.username = u1.email
This won't help performance but it will make things clearer.
Tip 3: If you need an INNER JOIN (u1) after a LEFT JOIN, use parentheses. Else, put the inner joins first, then the left joins. This makes it easier to use, but may screw up the logic.
Tip 4: Don't use LEFT unless you need it. When you dont need it, it confuses the reader (and the Optimizer). (Again, no performance change.)
Tip 5: Why are you using DISTINCT? That takes an extra pass over all the resultset.
If those do not help enough, then provide SHOW CREATE TABLE so we can critique the indexes.
I tried some subqueries but I didn't succed.
Let's suppose we have 2 tables : users(iduser, name), invoices(idinvoice, title, status, iduser)
and we want to show for each user, total invoices "paid", total invoices "unpaid" (that's status field)
I tried query like that
Select users.*, total_invoices_paid, total_invoices_unpaid
from users
LEFT JOIN (SELECT iduser, sum(if(status='paid',1,0)) AS total_invoices_paid, sum(if(status='unpaid',0,1)) AS total_invoices_unpaid
FROM invoices GROUP BY invoices.idinvoice) AS subinvoices
ON subinvoices.iduser=users.iduser
But I got wrong values, I think I missed something, but don't know what
any help please?
Thanks
I think you want this:
select u.*,
sum(i.status = 'paid') as total_invoices_paid,
sum(i.status = 'unpaid') as total_invoices_unpaid
from users u
left join invoices i on u.iduser = i.iduser
group by u.iduser;
A boolean is evaluated to 1 or 0 for true or false respectively in MySQL. Hence, using sum on the condition, we can find count.
Using subquery:
select u.*,
coalesce(i.total_invoices_paid, 0) as total_invoices_paid,
coalesce(i.total_invoices_unpaid, 0) as total_invoices_unpaid,
coalesce(i.total_invoices, 0) as total_invoices,
coalesce(i.total_paid_and_unpaid, 0) as total_paid_and_unpaid,
coalesce(i.total_with_non_null_status, 0) as total_with_non_null_status
from users u
left join (
select iduser,
sum(status = 'paid') as total_invoices_paid,
sum(status = 'unpaid') as total_invoices_unpaid,
count(1) as total_invoices,
sum(status in ('paid','unpaid')) as total_paid_and_unpaid,
count(status) as total_with_non_null_status
from invoices
group by iduser
) i on u.iduser = i.iduser
group by u.iduser;
I need to get empolyees info from employees table, and their total wages from two different tables.
The SQL is approximately like this, but I don't really know how to use joins to do this:
CONCAT(first_name, ' ', last_name) from employees as e
Sum(hours*pay) where date is "THIS MONTH" and employee_id = e.id from taxed_work
Sum(hours*pay) where date is "THIS MONTH" and employee_id = e.id from nontaxed_work
I am not sure how to join these together properly. I don't want to see any of the employees that have not done either kind of work for the month, only those who have. I'm using mysql and will put the data in a table with php
If anyone could tell me how to do the "THIS MONTH" part that would be cool too. Just being lazy on that part, but figured while I was here...
Thanks for the help!
You could use correlated subqueries:
select concat(first_name, ' ', last_name)
, (
select sum(hours*pay)
from taxed_work tw
where tw.employee_id = e.id
and year(tw.date) = year(now())
and month(tw.date) = month(now())
)
, (
select sum(hours*pay)
from nontaxed_work ntw
where ntw.employee_id = e.id
and year(ntw.date) = year(now())
and month(ntw.date) = month(now())
)
from employees e
You can calculate their totals inside subquery.
SELECT a.id ,
CONCAT(first_name, ' ', last_name) FullName,
b.totalTax,
c.totalNonTax,
FROM employees a
LEFT JOIN
(
SELECT employee_id, Sum(hours*pay) totalTax
FROM taxed_work
WHERE DATE_FORMAT(`date`,'%c') = DATE_FORMAT(GETDATE(),'%c')
GROUP BY employee_id
) b ON b.employee_id = a.id
LEFT JOIN
(
SELECT employee_id, Sum(hours*pay) totalTax
FROM nontaxed_work
WHERE DATE_FORMAT(`date`,'%c') = DATE_FORMAT(GETDATE(),'%c')
GROUP BY employee_id
) c ON c.employee_id = a.id
Try this query.
select
CONCAT(first_name, ' ', last_name) as employee_name,
sum(case when t.this_date = 'this_month' then t.hours*t.pay else 0 end),
sum(case when n.this_date = 'this_month' then t.hours*t.pay else 0 end)
from employees e
left join taxed_work t on e.id = t.employee_id
left join nontaxed_work n on e.id = n.employee_id
group by (first_name, ' ', last_name)
Please replace the t.this_date and n.this_date fields with actual field names as I am not aware of the exact table structure. Also, replace the "this_month" value as per your need.