Sort the table in sequential conditions? - mysql

I have a users table with 5 columns, id, age, is_premium, is_male, is_customer where id is the primary key.
First statement I do is. This statement has the potential of returning 0 rows:
SELECT * FROM users
WHERE is_premium = 1 AND
is_name = 0 AND
is_customer = 0
Then ONLY from the rows I got from the above query, I want to find the person with the largest age.
SELECT * FROM <from the above query>
WHERE id = (SELECT MAX(ID) <from the above query>)
Question: How do make these 2 separate SQL statements into a single statement and what is the most efficient way of doing this?

why not directly:
SELECT *
FROM users
WHERE is_premium = 1
AND is_name = 0
AND is_customer = 0
ORDER BY age DESC, id ASC
LIMIT 1
for mysql version 8 and above you can also use common table expressions (CTE):
WITH D AS (
SELECT * FROM users
WHERE is_premium = 1
AND is_name = 0
AND is_customer = 0
)
SELECT *
FROM D
WHERE AGE = (SELECT MAX(AGE) FROM D)
ORDER BY ID
LIMIT 1

Assuming you have a primary key column called id, just move the query in a sub-query:
SELECT *
FROM users
WHERE id = (
SELECT id
FROM users
WHERE is_premium = 1 AND is_name = 0 AND is_customer = 0
ORDER BY age DESC
LIMIT 1
)

suppose you will have multiple users with same max() age
select * from users t1
inner join (
select max(age) maxage from users
where is_premium=1 and is_name=0 and is_customer=0) t2
on t2.maxage = t1.age
where is_premium=1 and is_name=0 and is_customer=0
or if you don't want to repeat your conditions.
select * from users t1
inner join(
select
(select max(t.age) from users t where t.id = t2.id) as maxage,
t2.id
from users t2
where is_premium=1 and is_name=0 and is_customer=0) t3 on t3.id = t1.id

Related

select the last record in each group along with count

We are having a following sqlite3 table named 'atable'
id student assignment grade
-----------------------------------
1 A 123 9
2 A 456 9
3 A 234 8
4 B 534 7
5 B 654 9
6 C 322 7
id is unique and incremented for each records. We are fetching latest assignment for each user by running query
SELECT student, assignment, grade from atable where id in
(select max(id) from atable group by student) order by id desc
This is working fine. However, we also need to fetch number of assignments for each user where user received a particular grade in the same query, say 9.
Any idea suggestion how to enhance or rewrite above query to return count as well. As mentioned, we are using sqlite3.
Thanks
You can use this correlated query:
SELECT t.student, t.assignment, t.grade,
(SELECT COUNT(*) FROM atable s
WHERE s.student = t.student and s.grade >= 9) as total_above_9
from atable t
where t.id in
(select max(id) from atable group by student)
order by t.id desc
It would be better to join to a derived table that contains an aggregated version of the original table:
select t1.student, t1.assignment, t1.grade, t2.cnt
from mytable as t1
join (
select student, max(id) as id,
count(case when grade = 9 then 1 end) as cnt
from mytable
group by student
) as t2 on t1.id = t2.id
Try this;)
select t1.student, t1.assignment, t1.grade, t2.count
from atable t1
inner join (select max(id) as id, count(if(grade=9, 1, null)) as count from atable group by student) t2
on t1.id = t2.id
order by t1.id desc

How to optimise below sql query?

What I'm doing here is getting the reservations and the user which are duplicate. Here reservation ticket can not be printed twice. If a user prints a ticket tracker table updates with that record. If a user prints the same ticket twice it's marked as a duplicate. What subquery does here is return some reservation ids which are marked as duplicates.
SELECT t1.id AS res_id,
t1.tx_id,
t1.tx_date,
t1.bx_date,
t1.method,
t1.theater_id,
t1.showtime_id,
t1.category_id,
t1.amount,
t1.fname,
t1.status,
t1.mobile,
u.username,
t2.*
FROM `reservation` AS t1
INNER JOIN
( SELECT *
FROM `tracker`
WHERE reservation_id IN
( SELECT reservation_id
FROM `tracker`
GROUP BY reservation_id HAVING ( METHOD = 1
AND TYPE = 0
AND COUNT(*) > 1 )
OR ( METHOD = 1
AND TYPE = 1
AND COUNT(*) > 1 )
OR ( METHOD = 2
AND TYPE = 2
AND COUNT(*) > 0 )
OR ( METHOD = 3
AND TYPE = 0
AND COUNT(*) > 0 )
OR ( METHOD = 3
AND TYPE = 1
AND COUNT(*) > 1 )
OR ( METHOD = 3
AND TYPE = 3
AND COUNT(*) > 0 )) ) AS t2 ON t1.id = t2.reservation_id
INNER JOIN `users` AS u ON u.id = t2.user_id
WHERE t2.resolved = 0
AND t2.duplicate = 1
ORDER BY t2.issue_date DESC, t1.id DESC
EXPLAIN Command of the above query.
What should I do? If I'm index which keys should I use? How can I decide which keys to index? I know subquery slows me down What procedures should I follow to eliminate the slowness?
In MySQL, exists subqueries are often faster than in subqueries. You might try:
SELECT t1.id AS res_id, t1.tx_id, t1.tx_date, t1.bx_date,t1.method, t1.theater_id, t1.showtime_id,
t1.category_id, t1.amount, t1.fname, t1.status, t1.mobile, u.username, t2.*
FROM `reservation` t1 INNER JOIN
(SELECT *
FROM `tracker` t
WHERE EXISTS (SELECT 1
FROM `tracker` t3
where t3.reservation_id = t.reservation_id
GROUP BY reservation_id
HAVING (METHOD = 1 AND TYPE = 0 AND COUNT(*) > 1) OR
(METHOD = 1 AND TYPE = 1 AND COUNT(*) > 1) OR
(METHOD = 2 AND TYPE = 2 AND COUNT(*) > 0) OR
(METHOD = 3 AND TYPE = 0 AND COUNT(*) > 0) OR
(METHOD = 3 AND TYPE = 1 AND COUNT(*) > 1) OR
(METHOD = 3 AND TYPE = 3 AND COUNT(*) > 0)
)
) t2
ON t1.id = t2.reservation_id INNER JOIN
`users` AS u ON u.id = t2.user_id
WHERE t2.resolved = 0 AND t2.duplicate = 1
ORDER BY t2.issue_date DESC, t1.id DESC
I notice the subquery is using Hidden Columns in the having clause. It may not be doing what you expect. Normally, the query would include method and type in the group by clause or have an expression such as max(Method).

complex sql statement to different rows

I have a simple table which has 4 fields:
indexID
UserID
text_1
IsFinal
This table may have multiple values for each UserID, and IsFinal field can have only a single value 1 or 0.
What I'm trying to do is to make a select statement which will return the user IDs if IsFinal only equal 0. The problem there may be multiple records for the same userID, some having IsFinal equal to 0 and only 1 with IsFinal equal to 1.
My problem here is this: for every UserID, if it has a record with Isfinal = 1, I want to ignore all records with the same UserID, otherwise I want to return its records. I don't know if that can be done by SQL statement only or not.
Seems like you want an anti-join, i.e. you first need to establish which user IDs have IsFinal = 1, then use that result set to return all user IDs not in that list.
There are various ways to implement an anti-join.
NOT IN:
SELECT *
FROM atable
WHERE UserID NOT IN (
SELECT UserID
FROM atable
WHERE IsFinal = 1
);
NOT EXISTS:
SELECT *
FROM atable t1
WHERE NOT EXISTS (
SELECT *
FROM atable t2
WHERE t1.UserID = t2.UserID
AND t2.IsFinal = 1
);
LEFT JOIN + WHERE IS NULL:
a)
SELECT *
FROM atable t1
LEFT JOIN (
SELECT *
FROM atable
WHERE IsFinal = 1
) t2 ON t1.UserID = t2.UserID
WHERE t2.UserID IS NULL;
b)
SELECT *
FROM atable t1
LEFT JOIN atable t2
ON t1.UserID = t2.UserID AND t2.IsFinal = 1
WHERE t2.UserID IS NULL;
It may so happen that they will be equally efficient in your database, but it still may be a good idea to test each of them to at least avoid ending up with one that performs worse than the others.
I think this is what you are looking for:
SELECT a.*
FROM translations a
INNER JOIN (SELECT UserID FROM translations WHERE IsFinal = 1) b
ON a.UserID = b.UserID
WHERE IsFinal = 0;
TRY ( not tested )
SELECT t1.* FROM table t1
INNER JOIN table t2 USING(indexID)
WHERE t1.IsFinal <>1
GROUP BY t1.UserID

How do I select items in range of a SQL query?

My database is in MySQL.
Assume I have the following table:
id number
1 45
2 25
3 66
4 43
......
......
......
30 54
31 21
etc ... etc.
I want to have a query like so:
select * from myTable where number = 25
but I want to also include 2 more items, one above it and one below it (based on ID).
the result set of my query would turn up with the following result set: 1, 2, 3.
If I selected the number 66, then the result set would include 2, 3, 4. Etc, etc.
The idea would be to range the query by saying, hey, I want anything that has an ID equal to 1 minus this queries' id, and also one plus this queries' id.
I hope this makes sense.
Any help would be great.
Thanks!
P.S.
The point of this is to capture events in a log so that I can see what happened before and after a certain event happened
SELECT t.*
FROM
myTable AS t
JOIN
( SELECT id
FROM myTable
WHERE number = 25
) AS my
ON t.id BETWEEN my.id - 1 AND my.id + 1 ;
Notice that this will not show 3 rows if your ids have gaps.
Also, if the number you choose (25 in the example) appears more than once (but k times), the result will be 3*k rows.
If there are gaps, as expected, in the id column, you can use this:
SELECT *
FROM
( SELECT t.*
FROM
myTable AS t
JOIN
( SELECT MIN(id) AS id
FROM myTable
WHERE number = 25
) AS my
ON t.id <= my.id
ORDER BY t.id DESC
LIMIT 2
) AS a
UNION ALL
SELECT *
FROM
( SELECT t.*
FROM
myTable AS t
JOIN
( SELECT MIN(id) AS id
FROM myTable
WHERE number = 25
) AS my
ON t.id > my.id
ORDER BY t.id ASC
LIMIT 1
) AS b ;
If there are gaps in the id column and the number is not unique so the parameter (25) can appear more than once (but say k times), you can have a query that returns 3*k rows (almost all the times):
SELECT t.*
FROM
myTable AS t
JOIN
( SELECT id
FROM myTable
WHERE number = 25
) AS ti
ON t.id =
( SELECT tt.id
FROM myTable AS tt
WHERE tt.id < ti.id
ORDER BY tt.id DESC
LIMIT 1
)
OR t.id = ti.id
OR t.id =
( SELECT tt.id
FROM myTable AS tt
WHERE tt.id > ti.id
ORDER BY tt.id ASC
LIMIT 1
) ;
Not sure if it will work (I remember there are some limitations on ORDER BY and LIMIT in UNIONed queries, but don't have mysql instance to check it), but what if you try:
(SELECT t2.id
FROM myTable t1
INNER JOIN myTable t2 ON t2.id > t1.id
WHERE t1.number = 25
ORDER BY t2.id
LIMIT 1)
UNION ALL
(SELECT t2.id
FROM myTable t1
INNER JOIN myTable t2 ON t2.id < t1.id
WHERE t1.number = 25
ORDER BY t2.id DESC
LIMIT 1)
SELECT * FROM table WHERE id IN (SELECT id, id-1, id+1 FROM table WHERE number=25)

mysql: get result from 2 tables, regardless of a field being present in both tables

Quite hard to summarize my question in the title, so it's not totally covered..sorry for that :)
The problem is as follows:
I have two tables, of which one is a user table, and a second is a join table which holds a user/group combination.
I have users who can subscribe to several groups and I want to be able to check if someone is already member of a certain group. I also want to be able to get user information, regardless of the user being a member of the concerned group.
So I have:
Table 1: userID, userName, etc...
Table 2: (with unique composite index) groupID, userID
My best try is this:
SELECT * FROM table1 t1 LEFT JOIN table2 t2 ON t1.userID = t2.userID
WHERE userName = 'John' AND groupID = 1
(to see if John is member of group 1)
I think the problem is in the WHERE clause. The query will only return users who are member of groupID = 1. But what I want is to have null returned if John is not a member of this group.
Any ideas, is this even possible? Thanks! Fab
You need to move the condition table2.groupID = 1 from the WHERE clause to the ON conditions (otherwise the LEFT JOIN is cancelled):
SELECT u.*
, ug.groupID
FROM table1 AS u
LEFT JOIN table2 AS ug
ON ug.userID = u.userID
AND ug.groupID = 1
WHERE u.userName = 'John'
Another option is this approach (similar to LEFT JOIN):
SELECT *
, ( SELECT ug.groupID
FROM table2 AS ug
WHERE ug.userID = u.userID
AND ug.groupID = 1
) AS groupID
FROM table1 AS u
WHERE userName = 'John'
You should move the WHERE part to the JOIN
SELECT *
FROM table1 t1
LEFT JOIN table2 t2
ON t1.userID = t2.userID
And userName = 'John'
AND groupID = 1
edit I generated my own data. Try this
With Users as
(
Select 1 UserId, 'John' UserName
Union Select 2, 'Tom'
),
Groups as
(
Select 1 GroupId, 'Admin' GroupName
Union Select 2, 'Power Users'
Union Select 3, 'Users'
),
UserGroups as
(
Select 1 UserId, 1 GroupId
Union Select 2, 1
Union Select 2, 3
)
Select *
From Groups
Left Join (UserGroups
Inner Join Users
On Users.UserId = UserGroups.UserId
And UserName = 'John'
)
On Groups.GroupId = UserGroups.GroupId
Results:
GroupId GroupName UserId GroupId UserId UserName
----------- ----------- ----------- ----------- ----------- --------
1 Admin 1 1 1 John
2 Power Users NULL NULL NULL NULL
3 Users NULL NULL NULL NULL
You probably want to do this:
SELECT * FROM table1 t1 LEFT JOIN table2 t2 ON t1.userID = t2.userID
WHERE userName = 'John'
HAVING groupID = 1 OR groupID IS NULL
ORDER BY groupID DESC
LIMIT 1