MySQL syntax: bigger than value, but NOT IN (...) - mysql

Tables:
bookings: id, user_id, object_id, date1, ...
booking_status: id, book_id, status
status is int, range from 1 to 9 (request, confirmed, paid, cancelled by user and that sort of stuff), so I need all bookings, where status is at least 4 (which means paid) but no value bigger than 4 (which would mean cancelled etc).
Till now the SELECT looks about like this (I left out some fields (...) to shorten it):
SELECT b.date1, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id)
WHERE ((b.object_id=$object_id) AND (bs.status NOT IN (5,6,7,8,9)));"
...but it still selects those bookings that have booking status bigger than 4 as well. Any ideas how I need to change the query??
Thank you very much in advance!
UPDATE: thank you all again, I am amazed with how many great ideas you have come up with! There is really many ways to do it and I learned a lot from you, so thank you again! I will try all your suggestions and see for the performance, for now I mixed your solutions to this query, which works for now but I need to test it further:
SELECT b.date, ..., u.name FROM bookings b
LEFT JOIN user u ON (b.user_id = u.id)
LEFT JOIN booking_status bs ON (b.id=bs.book_id AND bs.status<=4)
WHERE (b.object_id=$object_id) HAVING MAX(bs.status)=4
it does not return multiple rows, but returns the rows with 4, excludes the rows with more than 4 and has no subqueries...
EDIT 2: I edited the query again... with HAVING MAX(bs.status)=4 it then works...
EDIT 3: sorry, after testing different cases I have to admit I was much too fast by saying it works...

If I'm understanding correctly, what you need is this:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON b.user_id = u.id
WHERE b.object_id = ...
AND EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status = 4
)
AND NOT EXISTS
( SELECT 1
FROM booking_status bs
WHERE bs.book_id = b.id
AND bs.status > 4
)
;
The problem with your current query is that it filters out all joined rows where bs.status > 4, but it doesn't filter out joined rows that have the same bookings.id as a joined row where bs.status > 4.

inspired by ruakh's solution without the correlated subqueries:
select ...
from bookings b
join (select book_id from booking_status group by book_id having max(status) = 4
) bs on b.id = bs.book_id
left join user u on b.user_id = u.id

Another way
SELECT id,
user_id,
object_id,
date1
FROM bookings b
INNER JOIN (SELECT book_id
FROM booking_status
GROUP BY book_id
HAVING MAX(CASE
WHEN status >= 4 THEN status
END) = 4) bs
ON bs.book_id = b.id
(or version without subselect in response to comments)
SELECT b.id,
b.user_id,
b.object_id,
b.date1
FROM bookings b
INNER JOIN booking_status bs
ON bs.book_id = b.id
GROUP BY b.id /* Other RDBMSs would require all columns listed*/
HAVING MAX(CASE
WHEN bs.status >= 4 THEN bs.status
END) = 4

Here's a method without subqueries:
SELECT b.date1, ..., u.name
FROM bookings b
LEFT JOIN user u ON u.id = b.user_id
JOIN booking_status bs1
ON bs1.book_id = b.id AND bs1.status = 4
LEFT JOIN booking_status bs2
ON bs2.book_id = b.id AND bs2.status > 4
WHERE b.object_id = $object_id
AND b2.book_id IS NULL
This query eliminates any row where there is a status for the same id that is greater than 4.

Related

I need to get specific ids from db if these are in current and last quarter using SQL

[DB Table]
SELECT b.first_name, b.last_name, a.pod_name, a.category, c.user_id,
SUM(IF(QUARTER(CURDATE())-1 OR (QUARTER(CURDATE())-2) AND a.user_id, 1, 0)) AS flag FROM kudos a
INNER JOIN users b ON a.user_id = b.id INNER JOIN users_groups c ON a.user_id = c.user_id
INNER JOIN groups d ON c.group_id = d.id WHERE a.group_name = 'G2' AND d.id IN (7,8,9,11,12,13,14,15,16,17,21,22,23,24,25,26,27,28)
AND QUARTER(CURDATE())-1 = a.quarter ORDER BY a.final_score+0 DESC
I need to get the user_ids of those users which are both in quarter 1 and 2 from table.
Tried above query but failed to get expected results.
Can someone please guide me on this?
if you only need user_id then you can do this :
select user_id
from tablename
where quarter in (1,2)
group by user_id
having count(distinct quarter) = 2
another way is to use window function, assuming you have one user id in each quarter:
select * from (
select * , count(*) over (partition by user_id) cn
from tablename
where quarter in (1,2)
) t where cn = 2

Getting max record on varchar field

I have this query
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN
services s ON a.id = s.account_id
INNER JOIN
facilities f ON f.id = a.facility_id
INNER JOIN
account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id) latest_note ON latest_note.account_id = a.id
WHERE
a.facility_id = 56
My problem comes from
(SELECT
account_id, MAX(content) content, MAX(created)
FROM
notes
GROUP BY account_id)
Content is a varchar field and I am needed to get the most recent record. I now understand that MAX will not work on a varchar field the way that I want it. I am not sure how to be able to get the corresponding content with the MAX id and group that by account id on in this join.
What would be the best way to do this?
My notes table looks like this...
id account_id content created
1 1 This is a test 2011-03-16 02:06:40
2 1 More test 2012-03-16 02:06:40
Here are two choices. If your content is not very long and don't have funky characters, you can use the substring_index()/group_concat() trick:
(SELECT account_id,
SUBSTRING_INDEX(GROUP_CONCAT(content ORDER BY created desc SEPARATOR '|'
), 1, '|') as content
FROM notes
GROUP BY account_id
) latest_note
ON latest_note.account_id = a.id
Given the names of the columns and tables, that is likely not to work. Then you need an additional join or a correlated subquery in the from clause. I think that might be easiest in this case:
select . . .,
(select n.content
from notes n
where n.account_id = a.id
order by created desc
limit 1
) as latest_note
from . . .
The advantage to this method is that it only gets the notes for the rows you need. And, you don't need a left join to keep all the rows. For performance, you want an index on notes(account_id, created).
SELECT
s.account_number,
a.id AS 'ASPIRION ID',
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS 'STATUS',
astat.definition,
latest_note.content AS 'LAST NOTE',
a.insurance_company
FROM
accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(SELECT account_id, MAX(created) mxcreated
FROM notes GROUP BY account_id) latest_note ON latest_note.account_id = a.id and
latest_note.mxcreated = --datetime column from any of the other tables being used
WHERE a.facility_id = 56
You have to join on the max(created) which would give the latest content.
Or you can change the query to
SELECT account_id, content, MAX(created) mxcreated
FROM notes GROUP BY account_id
as mysql allows you even if you don't include all non-aggregated columns in group by clause. However, unless you join on the max date you wouldn't get the correct results.
The last created record is the one for which does not exist a newer one. Hence:
SELECT
s.account_number,
a.id AS "ASPIRION ID",
a.patient_first_name,
a.patient_last_name,
s.admission_date,
s.total_charge,
astat.name AS "STATUS",
astat.definition,
latest_note.content AS "LAST NOTE",
a.insurance_company
FROM accounts a
INNER JOIN services s ON a.id = s.account_id
INNER JOIN facilities f ON f.id = a.facility_id
INNER JOIN account_statuses astat ON astat.id = a.account_status_id
INNER JOIN
(
SELECT account_id, content
FROM notes
WHERE NOT EXISTS
(
SELECT *
FROM notes newer
WHERE newer.account_id = notes.account_id
AND newer.created > notes.created
)
) latest_note ON latest_note.account_id = a.id
WHERE a.facility_id = 56;

INNER JOIN with 2 CASE

I try to do an INNER JOIN depending of the result of CASE.
SELECT *
FROM album
INNER JOIN (
SELECT album_type CASE album.album_type
WHEN 1 THEN "album_int"
WHEN 2 THEN "album_ext"
END FROM album)
AS type ON type.album_id = album.id
WHERE album.id = 6
LIMIT 1;
I have see some examples on this site, but nothing work in my case.
Someone can help me?
I have 3 tables (album, album_int and album_ext).
I want to join album_int or album_ext to album.
if album.album_type = 1 I join album_int,
else if album.album_type = 2 I join album_ext.
In album_int and album_int I have a column name album_id (same unique
id in album)
You do not need any case expressions or even a where clause.
SELECT a.`id`, COALESCE(aint.`something`, aext.`something`) as something, ...
FROM ALBUM A
LEFT OUTER JOIN ALBUM_INT AS AINT ON A.ID = AINT.ALBUM_ID AND A.ALBUM_TYPE = 1
LEFT OUTER JOIN ALBUM_EXT AS AEXT ON A.ID = AEXT.ALBUM_ID AND A.ALBUM_TYPE = 2
AND A.ALBUM_TYPE = 1 as a join condition means that ALBUM_INT will ONLY join to ALBUM rows where album_type = 1
similarly:
AND A.ALBUM_TYPE = 2 as a join condition means that ALBUM_EXT will ONLY join to ALBUM rows where album_type = 2
please note that an answer previously given by Gordon Linoff uses the same join logic, I have just attempted to emphasize how it meets the described requirements.
case doesn't work on tables. You can use left join:
SELECT *
FROM album a LEFT JOIN
album_int ai
ON ai.album_id = a.id AND a.album_type = 1 LEFT JOIN
album_ext ae
ON ae.album_id = a.id AND a.album_type = 2
WHERE a.id = 6 AND a.album_type IN (1, 2)
LIMIT 1;
To get values into the same column, you then need to use coalesce().
Note: This does the right thing if there is at most one match in each table.
To Join to one of two tables conditionally, create a join column on each table and UNION them together as such:
SELECT *
FROM albums a
INNER JOIN (
SELECT 1 AS album_type, album_id, colA, colB
FROM album_int
UNION ALL
SELECT 2 AS album_type, album_id, colA, colB
FROM album_ext ) AS b
ON a.album_type = b.album_type
AND a.album_id = b.album_id

User CASE or subquery?

I have 3 tables: tickets, tickets_users and users. My problem is that in the users table I have 2 types of users: requesters and solvers: type 1 and 2.
I want to select the ticket, requester (if any, can be multiple) and solver (if any, can be multiple).
I'm thinking something in the lines of:
SELECT
t.id, t.description,
(u.id where u.type = 1) AS requester,
(u.id where u.type = 2) AS solver
FROM
tickets t
INNER JOIN
tickets_users tu ON t.id = tu.ticket_id
INNER JOIN
users u ON tu.user_id = u.id
Obviously this does not work.
The tables look like this:
Tickets:
ID Description
1 Description 1
2 Description 2
Tickets_users
ID Ticket_ID User_id Type
1 3 4 1
2 5 8 2
Users
ID Name
1 John
2 Mary
Thanks,
In the meantime I think I found a solution using a sub-query in the join clause, but to me it looks rudimentary:
SELECT
t.id, t.name AS ticket_name, type1.users_id AS requester,
type2.users_id AS solver
FROM
tickets t
INNER JOIN
(SELECT users_id, tickets_id
FROM tickets_users
WHERE TYPE = 1) type1 ON t.id = type1.tickets_id
INNER JOIN
(SELECT users_id, tickets_id
FROM tickets_users
WHERE TYPE = 2) type2 ON t.id = type2.tickets_id
Should be something like this.
SELECT
t.id, t.description, u.id, userType
CASE WHEN u.type = 1 THEN 'requester' ELSE 'solver' END
FROM
tickets t
INNER JOIN tickets_users tu ON t.id = tu.ticket_id
INNER JOIN users u ON tu.user_id = u.id
Please provide sqlfiddle in case above example does not work.
With Joins,
SELECT
t.id, t.description, requesters.id, solvers.id
FROM
tickets t
INNER JOIN tickets_users tu ON t.id = tu.ticket_id
LEFT JOIN users requesters ON (tu.user_id = requesters.id AND requesters.type=1)
LEFT JOIN users solvers ON (tu.user_id = solvers.id AND solvers.type=2)
As you can tell, users table is joined twice (as requesters and as solvers with additional conditions). The reason LEFT JOIN is used is because if there's a record with no requesters or no solvers, INNER JOIN would completely ignore the whole record until it has a requester and a solver.

how to count the two count function's return value in once sql query

I have three tables A,B,C.Their relation is A.id is B's foreign key and B.id is C's foreign key.I need to sum the value when B.id = C.id and A.id = B.id ,I can count the number by query twice. But now I need some way to count the summation just once time !
My inefficient solution
select count(C.id) from C,B where C.id = B.id; //return the value X
select count(A.id) from C,B where A.id = B.id; //return the value Y
select X + Y; // count the summation fo X and Y
How can I optimize ? Thks! :)
PS:
My question is from GalaXQL,which is a SQL interactive tutorial.I have abstract the problem,more detail you can check the section 17.SELECT...GROUP BY... Having...
You can do these things in one query. For instance, something like this:
select (select count(*) from C join B on C.id = B.id) +
(select count(*) from C join A on C.id = A.id)
(Your second query will not parse because A is not a recognized table alias.)
In any case, if you are learning SQL, the first thing you should learn is modern join syntax. The implicit joins that you are using were out of date 15 years ago and have been part of the ANSI standard for over 20 years. Learn proper join syntax.
Try Like This
select sum(cid) (
select count(*) as cid from C join B on C.id = B.id
union all
select count(*) as cid from A join B on A.id = B.id ) as tt
try this one:
select
(select count(*) from C join B on C.id = B.id)
union
(select count(*) from C join A on C.id = A.id)