MySQL Join and Subqueries - mysql

I currently have the following tables:
Case_Workflows
case_id (PK) | work_id (PK) | date
1 | 1 | 2011-12-12
1 | 4 | 2011-12-13
2 | 6 | 2011-12-18
Workflows
work_id (PK) | status_id
1 | 1
2 | 1
3 | 1
4 | 2
5 | 2
6 | 3
Statuses
status_id (PK) | title
1 | abc
2 | def
3 | ghi
What I am attempting to do is pull a count of the total number of cases with a specific status such as 'abc'. The snag is that each case can have multiple workflows and I only want the single most recent one for each case.
The end result should be:
Status: abc - Count: 2
This is what I have so far:
SELECT COUNT(cases.case_id) as countNum
FROM $this->_caseTable
JOIN case_workflows
ON cases.case_id = cases_workflows.case_id
JOIN workflows
ON cases_workflows.workflow_id = workflows.workflow_id
JOIN statuses
ON workflow.status_id = statuses.status_id
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'
What I am unsure on is how to first select the latest work_id for each case, and then grabbing its status_id to match it to a WHERE clause such as WHERE statuses.title = 'abc'

SELECT COUNT(*) as countNum
FROM $this->_caseTable
JOIN workflows
ON workflows.workflow_id =
( SELECT workflow_id
FROM cases_workflows AS mcwf
WHERE mcwf.case_id = cases.case_id
ORDER BY date DESC
LIMIT 1
)
JOIN statuses
ON workflow.status_id = statuses.status_id
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'
AND statuses.title = 'abc'

From what I'm understanding here, you need to add statuses.title to your SELECT clause, and then add a GROUP BY clause:
SELECT statuses.title, COUNT(cases.case_id) as countNum
FROM $this->_caseTable
JOIN (SELECT case_id, work_id, max(date)
FROM case_workflows
GROUP BY work_id
WHERE case_id = cases.case_id) cw
ON cases.case_id = cw.case_id
JOIN workflows
ON cw.workflow_id = workflows.workflow_id
JOIN statuses
ON workflow.status_id = statuses.status_id
GROUP BY statuses.title
WHERE cases.date > '2011-12-12'
AND cases.date <= '2011-12-18'

Related

SQL Find Get Item that does not have specific status

I have the following table:
case_id | run_id | status_id
1 | 44 | 1
1 | 45 | 3
1 | 46 | 1
2 | 44 | 3
2 | 45 | 3
2 | 46 | 3
The table above is the results of the following query:
SELECT t.case_id, t.run_id, t.status_id
FROM `test` t,
(SELECT id FROM `run` WHERE created_on BETWEEN '2019-10-01' AND CURRENT_DATE()) r
WHERE t.run_id = r.id
I'm trying to select only the case_ids that have only 3 in the status_id.
so the query should return case_id 2.
You can use proper, explicit, standard JOIN syntax . . . and then aggregation and filter with a HAVING clause:
select t.case_id
from test t join
run r
on t.run_id = r.id
where r.created_on BETWEEN '2019-10-01' AND CURRENT_DATE()
group by t.case_id
having min(t.status_id) = max(t.status_id) and min(t.status_id) = 3;
You can use aggregation and filter with a having clause:
select t.case_id
from test t
inner join run r on r.id = t.run_id
where r.created_on between '2019-10-01' and current_date()
group by t.case_id
having count(r.status_id <> 3) = 0

MySQL select total latest updates of a type in the last N days

In MySQL, I have a table things which holds things owned by a user_id. The table thing_updates holds updates to things, and have a status and a date_submitted which is a unix timestamp of when the update was made. things do not necessarily have a corresponding row in thing_updates, such as when an update has not yet been made. Sample data:
Table: things
id | user_id
1 | 1
2 | 1
3 | NULL
Table: thing_updates
id | thing_id | status | date_submitted
1 | 1 | x | 123456789
2 | 1 | y | 234567890
3 | 3 | x | 123456789
I have managed to get the latest status of each thing before the date 999999999 assigned to user_id = 1 with the query below.
select t.id, tu.status, t.user_id
from things as t
left join thing_updates as tu
on tu.thing_id = t.id
where (
date_submitted in (
select max(tu2.date_submitted)
from thing_updates as tu2
where date_submitted < 999999999
group by thing_id
)
or date_submitted is null
)
and t.user_id = 1
This will give me something akin to:
id | status | user_id
1 | y | 1
2 | NULL | 1
As you can see, the status y is shown because it is more recent than x and before 999999999. There are 2 results in total and this query seems to work fine.
Now I would like to get total results which have a certain status for today, yesterday, the day before, etc until 10 days ago. To do this I have created another table called chart_range which holds the numbers 0 to 9. For instance:
Table: chart_range
offset
0
1
2
...
9
I hoped to use the offset value as follows:
select cr.offset, count(x.id) as total_x
from chart_range as cr
left join (
select t.id, tu.status, t.user_id
from things as t
left join thing_updates as tu
on tu.thing_id = t.id
where (
date_submitted in (
select max(tu2.date_submitted)
from thing_updates as tu2
where date_submitted < unix_timestamp(date_add(now(), interval - cr.offset + 1 day))
group by thing_id
)
or date_submitted is null
)
and t.user_id = 1
) as x on tu.status = 'x'
group by cr.offset
order by cr.offset asc
The end goal is to get a result like this:
offset | total_x
0 | 2 <-- such as in the 999999999 example above
1 | 5
2 | 7
3 | 4
...
9 | 0
However my query does not work as cr.offset cannot be referenced in an uncorrelated subquery. How can I modify this query to work?

Mysql query with group

I have the following table:
id club_id club_option_id created
---|-------|--------------|-------
1 | 1 | 2 | 2015-02-11 16:31:23
2 | 1 | 3 | 2015-02-11 16:31:23
3 | 2 | 2 | 2015-03-06 08:16:02
I would like to select the club (club_id) who has both options (2 and 3)
I don't get any results with the following query:
SELECT club_id FROM
club_options_to_clubs
WHERE club_option_id = 2 AND club_option_id = 3
GROUP BY club_id
How can I get club_id 1 as a result?
You can do as below -
select
t1.club_id from table_name t1
where t1.club_option_id = 2
and exits (
select 1 from table_name t2
where t1.club_id = t2.club_id
and t2.club_option_id = 3
)
The other way is
select club_id from table_name
where club_option_id in (2,3)
group by club_id
having count(*) = 2
With the above approach if you need to check multiple club_option_id pass them in the in function and then use the number in having count(*) = n
Here n = number of items in the in function.

Mysql - Select at least one or select none

I have a table as so...
----------------------------------------
| id | name | group | number |
----------------------------------------
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 3 | james | 2 | 2 |
| 4 | steven | 2 | 5 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
----------------------------------------
I'm running a select like so:
SELECT * FROM table WHERE number IN (2,3);
The problem im trying to solve is that I want to only grab get results from groups that have 1 or more rows of each number. For instance the above query is returning id's 1-2-3-5-6, when I'd like the results to exclude id 3 since the group of '2' can only return 1 result for the number of '2' and not for BOTH 2 and 3, since there's no row with the number 3 for the group 2 i'd like it to not even select id 3 at all.
Any help would be great.
Try it this way
SELECT *
FROM table1 t
WHERE number IN(2, 3)
AND EXISTS
(
SELECT *
FROM table1
WHERE number IN(2, 3)
AND `group` = t.`group`
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
)
or
SELECT *
FROM table1 t JOIN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
) q
ON t.`group` = q.`group`;
or
SELECT *
FROM table1
WHERE `group` IN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
);
Sample output (for both queries):
| ID | NAME | GROUP | NUMBER |
|----|-------|-------|--------|
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
Here is SQLFiddle demo
On this, you can approach from a fun way with multiple joins for what you WANT qualified, OR, apply a prequery to get all qualified groups as others have suggested, but readability is a bit off for me..
Anyhow, here's an approach going through the table once, but with joins
select DISTINCT
T.id,
T.Name,
T.Group,
T.Number
from
YourTable T
Join YourTable T2
on T.Group = T2.Group AND T2.Group = 2
Join YourTable T3
on T.Group = T3.Group AND T3.Group = 3
where
T.Number IN ( 2, 3 )
So on the first record, it is pointing to by it's own group to the T2 group AND the T2 group is specifically a 2... Then again, but testing the group for the T3 instance and T3's group is a 3.
If it cant complete the join to either of the T2 or T3 instances, the record is done for consideration, and since indexes work great for joins like this, make sure you have one index for your NUMBER criteria, and another index on the (GROUP, NUMBER) for those comparisons and the next query sample...
If doing by more than this simple 2, but larger group, prequery qualified groups, then join to that
select
YT2.*
from
( select YT1.group
from YourTable YT1
where YT1.Number in (2, 3)
group by YT1.group
having count( DISTINCT YT1.group ) = 2 ) PreQualified
JOIN YourTable YT2
on PreQualified.group = YT2.group
AND YT2.Number in (2,3)
Maybe this,if I understand you
SELECT id FROM table WHERE `group` IN
(SELECT `group` FROM table WHERE number IN (2,3)
GROUP BY `group`
HAVING COUNT(DISTINCT number)=2)
SQL Fiddle
This will return all ids where BOTH numbers exist in a group.Remove DISTINCT if you want ids for groups where just one numbers is in.

Mysql: event table :chronological consecutive join

I have the following table:
user_id | Membership_type | start_Date
1 | 1 | 1
1 | 1 | 2
1 | 2 | 3
1 | 3 | 4
with several users, and i need to find out for each user when the membership type changes and what the change is, in the following format (start date is datetime, put it here in int for ease of understanding)
user_id |Membership_change| change_Date
1 | 1 to 2 | 3
1 | 2 to 3 | 4
I have tried
select m1.user_id, concat(m1.Membership_type, ' to ',m2.Membership_type), m2.start_date
from table_membership m1
join table_membership m2
on m1.user_id=m2.user_id and m1.start_date<m2.start_date and m1.membership_type<>m2.membership_type
but this will return
user_id |Membership_change| change_Date
1 | 1 to 2 | 3
1 | 1 to 2 | 3
1 | 1 to 3 | 4
1 | 2 to 3 | 4
The duplicate 1 to 2 is not a problem to remove through a grouping, but I cannot seem to be able to think of a way to avoid having the 1 to 3 result. I basically just need to join chronologically from one membership to the next
Any ideas would be appreciated!
Edit: Had an idea to add the column m1.start_date and group by account_id and m1.start_date, so I would only get the first row where each entry is joined. Also a pre-sort by date before the joins, to make sure they are all in order. Will test.
You are missing GROUP BY
select
m1.user_id,
concat(m1.Membership_type, ' to ',m2.Membership_type),
m2.start_date
from table_membership m1
join table_membership m2
on m1.user_id = m2.user_id
and m1.start_date < m2.start_date
and m1.membership_type <> m2.membership_type
GROUP BY user_id, Membership_change, change_Date
Had an idea to add the column m1.start_date and group by account_id and m1.start_date, so I would only get the first row where each entry is joined. Also a pre-sort by date before the joins, to make sure they are all in order.
select m.user_id, m.membership_change, m.change_date from
(
select
m1.user_id,
concat(m1.Membership_type, ' to ',m2.Membership_type) as membership_change,
m2.start_date as change_date,
m1.start_date
from (select * from table_membership order by start_date asc)m1
join (select * from table_membership order by start_date asc)m2
on m1.user_id = m2.user_id
and m1.start_date < m2.start_date
and m1.membership_type <> m2.membership_type
GROUP BY m1.user_id, m1.start_Date
)m group by 1,2,3