Query MySQL Group by and Having - mysql

I am creating an application for my school and I am in trouble constructing the right query.
I have 2 tables,table1 and table2.
table1
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 2 | 3 | yes | 1 |
| 2 | 2 | yes | 2 |
| 3 | 1 | no | 3 |
---------------------------------------------------------
table2
---------------------------------------------------------
| SubjectID | SubjectName | Number1 | Number2 |
---------------------------------------------------------
| 1 | Name1 | 6 | 4 |
| 2 | Name2 | 4 | 8 |
| 3 | Name3 | 5 | 2 |
---------------------------------------------------------
SubjectID in table1 is foreign key references table2.
I want to build a query sql that gives me the StudentID`s from table1
that didnt miss any Type 3 subject (i.e no row like this
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 3 | 1 | no | 3 |
---------------------------------------------------------
And have completed 75 percent of type 1 (i.e
I find it like this
SELECT t1.StudentID,t1.SubjectID ,t1.Type,t2.Number1 as num
FROM table1 as t1,table2 as t2
WHERE t1.Present=yes and t2.SubjectID=t1.SubjectID
GROUP BY StudentID,SubjectID
HAVING COUNT(*)/num >= 75/100
But I cant combine the two things together.

You can combine queries by giving them aliases and joining as subqueries...
SELECT finisher.StudentID FROM
(
SELECT DISTINCT StudentID
FROM table1 t1
JOIN table2 t2 ON t2.SubjectID = t1.SubjectID
WHERE t1.Present = 'yes' AND t1.Type1 = 1
GROUP BY t1.StudentID, t2.SubjectID
HAVING COUNT(*) / t2.Number2 >= 0.75
) finisher
JOIN
(
SELECT DISTINCT t1.StudentID
FROM table1 t1
LEFT JOIN
(
SELECT DISTINCT StudentID
FROM table1
WHERE Type = 3 AND Present = 'no'
) missed ON missed.StudentID = t1.StudentID
WHERE t1.Type = 3
AND missed.StudentID IS NULL
) notmissed ON finisher.StudentID = notmissed.StudentID
"StudentID`s from table1 that didnt miss any Type 3"... I assume here you don't want to include students without any type 3 rows.

Seems like this is done and duste, but how about...
SELECT x.*
FROM
( SELECT t1.StudentID
, t1.SubjectID
, t1.Type
, t2.Number1 num
FROM table1 t1
JOIN table2 t2
ON t2.SubjectID=t1.SubjectID
WHERE t1.Present='yes'
GROUP
BY t1.StudentID
, t1.SubjectID
HAVING COUNT(*)/num >= 0.75
) x
LEFT
JOIN table1 y
ON y.student_id = x.student_id
AND y.subject_id = x.subject_id
AND y.type = 3
AND y.present = 'no'
WHERE y.student_id IS NULL;

Related

SQL get value based on corresponding min/max(value) from column in another related table

I have the following two tables related by the ID column as the Primary Key. My Goal is to query the values from the "Name" column in Table 1 which correspond to the User_id with the Max and Min "Score" Column Values from Table 2.
Table 1:
| ID | Name |
|----|------|
| 1 | Foo |
| 2 | Bar |
| 3 | Zoo |
| 4 | Bar |
| 5 | Foo |
| 6 | Zar |
Table 2:
| ID | Score |
|----|-------|
| 1 | 98 |
| 2 | 67 |
| 3 | 86 |
| 4 | 59 |
| 5 | 75 |
| 6 | 73 |
The final output should give me something like this:
| Name | Score |
|------|-------|
| Foo | 98 |
| Bar | 59 |
You can try the below -
select name, score
from table1 t1 join table2 t2 on t1.id=t2.id
where
score=(select max(score) from t2)
or
score=(select min(score) from t2)
(
SELECT name, score
FROM table1 NATURAL JOIN table2
ORDER BY 2 ASC LIMIT 1
)
UNION ALL
(
SELECT name, score
FROM table1 NATURAL JOIN table2
ORDER BY 2 DESC LIMIT 1
)
If you are running MySQL 8.0, you can use window functions:
select t1.name, t2.score
from table1 t1
inner join (
select t2.*,
rank() over(order by score) rn_asc,
rank() over(order by score desc) rn_desc
from table2 t2
) t2 on t2.id = t1.id
where 1 in (rn_asc, rn_desc)
The idea is to rank records of table2 by increasing and decreasing score, and use that information for filtering. Note that this allows top and bottom ties.

Get result from joined tables

I have 2 tables:
Table 1:
| jobid | jobname |
| 1 | job a |
| 2 | job b |
Table 2:
| id | jobid | statusid | statusdate | desc |
| 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
| 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
Jobs in table2 can have more then one same "statusid", but different "statusdate" and "desc"
I need to get jobs list with the last "statusid" = 100 like this :
| 1 | job a | 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
SELECT * FROM table1
INNER JOIN table2 ON table1.id = table2.jobid
GROUP BY table1.id
This query return wrong result like:
| 1 | job a | 1 | 1 | | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
You should be able to accomplish that by doing something like this:
Table
drop table if exists table1;
create table table1 (jobid int, jobname char(10));
insert into table1 values (1, 'job a'), (2, 'job b');
drop table if exists table2;
create table table2 (
id int,
jobid int,
statusid int,
statusdate timestamp,
`desc` char(10)
);
insert into table2 values
(1,1,100,'2019.04.25 10:00:00','first')
,(2,2,100,'2019.04.25 11:00:00','first')
,(3,2,100,'2019.04.25 12:00:00','second');
Query
select
t1.*,
t2.*
from table1 t1
inner join (
select jobid, max(statusdate) as maxstatusdate
from table2
group by jobid
) tn on t1.jobid = tn.jobid
inner join table2 t2 on tn.jobid = t2.jobid and tn.maxstatusdate = t2.statusdate;
Results
jobid jobname id jobid statusid statusdate desc
1 job a 1 1 100 25.04.2019 10:00:00 first
2 job b 3 2 100 25.04.2019 12:00:00 second
Explanation
For each job ID, find the maximum status date
Join that to table1 to get information from table1. Common field is jobid
Join their result to table2 that has all the remaining information you want. Common fields are jobid and statusdate. Since we aliased max status date to a different name, make sure we are using the correct name in the join
Example: https://rextester.com/HRSWZ89705
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
(jobid INT NOT NULL PRIMARY KEY
,jobname VARCHAR(12) UNIQUE
);
INSERT INTO table1 VALUES
(1,'job a'),
(2,'job b'),
(3,'job c');
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id SERIAL PRIMARY KEY
,jobid INT NOT NULL
,statusid INT NOT NULL
,statusdate DATETIME NOT NULL
,description VARCHAR(12) NOT NULL
);
INSERT INTO table2 VALUES
(1,1,100,'2019-04-25 10:00:00','first'),
(2,2,100,'2019-04-25 11:00:00','first'),
(3,2,100,'2019-04-25 12:00:00','second');
SELECT a.*
, b.id x_id
, b.statusid
, b.statusdate
, b.description
FROM table1 a
LEFT
JOIN
( SELECT x.*
FROM table2 x
JOIN
( SELECT MAX(id) id
FROM table2
WHERE statusid = 100
GROUP
BY jobid
) y
ON y.id = x.id
) b
ON b.jobid = a.jobid
;
+-------+---------+------+----------+---------------------+-------------+
| jobid | jobname | x_id | statusid | statusdate | description |
+-------+---------+------+----------+---------------------+-------------+
| 1 | job a | 1 | 100 | 2019-04-25 10:00:00 | first |
| 2 | job b | 3 | 100 | 2019-04-25 12:00:00 | second |
| 3 | job c | NULL | NULL | NULL | NULL |
+-------+---------+------+----------+---------------------+-------------+
SELECT
t1.*,t2.* FROM
(SELECT
MAX(id) as id
FROM
table2
WHERE statusid = 100
GROUP BY jobid) AS f
JOIN table2 t2
ON t2.id = f.id
JOIN table1 t1
ON t2.jobid = t1.jobid
The sub query select finds the last id for a row with statusid 100, then it joins the actual table based on this id.
You can reorder this as you wish using the correct joins.

Multiple times values are coming in mysql

I have two tables
tbl1 and tbl2
tbl1 table contains 5 columns name id(pk), email , address ,pid(INDEX),status(ENUM Y,N)
tbl2 table contains 3 columns id(pk) ,pid(INDEX),domain
When i am running this query
SELECT *
FROM tbl1 as l
LEFT JOIN tbl2 as m on l.pid=m.pid
WHERE l.status='Y';
It is giving multiple records . Please note we are making join in pid both pid are not primary key. Please help to get only unique values from both table.
enter image description here
You seem to want to join on the basis of relative position in the tables.A way to do this is row_number simulation using variables.
drop table if exists t1,t2;
create table t1(id int, email varchar(5),address varchar(10),pid int,status varchar(1));
create table t2(id int, pid int, domain varchar(5));
insert into t1 values (1,'aa#aa', 'aaaaa',428,'Y'), (2,'bb#bb', 'bbbbb',428,'n'),(3,'cc#cc', 'ccccc',428,'Y') ;
insert into t2 values (1,428,'mmm'),(2,428,'zzz');
select t1.*,t2.*
from
(
select t1.*,
if(t1.pid <> #pid1, #bn1:=#bn1+1,#bn1:=#bn1) BlockNo1,
if(t1.id <> #id1, #rn1:=#rn1+1, #rn1:=1) rowno1,
#pid1:=t1.pid pid1,
#id1:=t1.id p1
from t1
cross join (select #bn1:=0,#rn1:=0, #pid1:=0 ,#id1:=0) r
where status = 'y'
order by t1.pid,t1.id
) t1
join
(
select t2.id t2id,t2.pid t2pid, t2.domain t2domain,
if(t2.pid <> #pid2, #bn2:=#bn2+1,#bn2:=#bn2) BlockNo2,
if(t2.id <> #id2, #rn2:=#rn2+1, #rn2:=1) rowno2,
#pid2:=t2.pid pid2,
#id2:=t2.id p2
from t2
cross join (select #bn2:=0,#rn2:=0, #pid2:=0 ,#id2:=0) r
order by t2.pid,t2.id
) t2 on (t1.blockno1 = t2.blockno2) and (t1.rowno1 = t2.rowno2)
+------+-------+---------+------+--------+----------+--------+------+------+------+-------+----------+----------+--------+------+------+
| id | email | address | pid | status | BlockNo1 | rowno1 | pid1 | p1 | t2id | t2pid | t2domain | BlockNo2 | rowno2 | pid2 | p2 |
+------+-------+---------+------+--------+----------+--------+------+------+------+-------+----------+----------+--------+------+------+
| 1 | aa#aa | aaaaa | 428 | Y | 1 | 1 | 428 | 1 | 1 | 428 | mmm | 1 | 1 | 428 | 1 |
| 3 | cc#cc | ccccc | 428 | Y | 1 | 2 | 428 | 3 | 2 | 428 | zzz | 1 | 2 | 428 | 2 |
+------+-------+---------+------+--------+----------+--------+------+------+------+-------+----------+----------+--------+------+------+
2 rows in set (0.04 sec)

Attach max from group to every row of results ?

I have similar to the following database structure:
ApplicationService table :
+-----+--------------+----------+---------------+
| id | name | status | application_id
| 1 | Service 1 | 1 | 24
| 2 | Service 2 | 2 | 24
| 3 | Service 3 | 3 | 25
+-----+--------------+----------+----------------
And there is other table with status definitions:
CustomerStatus
+------------+--------------+----------+-----------+
| status_id | name | level | is_closed
| 1 | Status 1 | 2 | 1
| 2 | Status 2 | 1 | 0
| 3 | Status 3 | 3 | 1
+------------+--------------+----------+----------
The status for each row of the ApplicationServices is calculated as the max level status within the records grouped by application_id.
So to get all records from ApplicationServices with statuses would result in something like this:
+-----+--------------+----------+----------------+-------------+-----------
| id | name | status | application_id | status_name | is_closed
| 1 | Service 1 | 1 | 24 | Status 1 | 1
| 2 | Service 2 | 2 | 24 | Status 1 | 1
| 3 | Service 3 | 3 | 25 | Status 3 | 1
+-----+--------------+----------+----------------+-------------+-----------
Is there an efficient way to attach the results with max(level) grouped by application_id to every row of the result set ?
Try this:
SELECT A.id, A.name, A.status, A.application_id, CS.name AS status_name, CS.is_closed
FROM ApplicationService A
INNER JOIN (SELECT AA.application_id, MAX(CS.level) AS maxLevel
FROM ApplicationService AA
INNER JOIN CustomerStatus CS ON AA.status = CS.status_id
GROUP BY AA.application_id
) AS AA ON A.application_id = AA.application_id
INNER JOIN CustomerStatus CS ON AA.maxLevel = CS.level;
See working SQLfiddle here.
You will probably have to perform a sub-query. The subquery would simply be joining the two tables together, grouping/collapsing by application_id and then fetching the maximum is_closed value:
SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id
The max is_closed value should then be accessible by the max_is_closed alias when you incorporate the subquery:
SELECT
t1.id AS id,
t1.name AS name,
t1.status AS `status`,
t1.application_id AS application_id,
t2.name AS status_name,
t3.max_is_closed
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
LEFT JOIN
(SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id) AS t3 ON
t1.application_id = t3.app_id
The output from the query:

MySQL select from specific ID until match condition

Given this table:
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 1 | a | 1 |
+----+-----------+--------+
| 2 | a | 2 |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
| 5 | b | 5 |
+----+-----------+--------+
| 6 | b | 6 |
+----+-----------+--------+
How can I get a new table that begins on id=3 (including) and goes until condition = b (excluding):
+----+-----------+--------+
| id | condition | values |
+----+-----------+--------+
| 3 | a | 3 |
+----+-----------+--------+
| 4 | a | 4 |
+----+-----------+--------+
added fiddle: http://sqlfiddle.com/#!2/9882f7
Basically I want a table between a matching first condition (over a specific column - id) and a second one (over a different column - condition)
You need to stop thinking of SQL data as having any order. Think of SQL data in sets; you have to search by values, not by positions.
SELECT t1.*
FROM t AS t1
JOIN (
SELECT MIN(id) AS id FROM t
WHERE id >= 3 AND `condition` = 'b'
) AS t2
WHERE t1.id >= 3 AND t1.id < t2.id
ORDER BY t1.id
Something like this:
select t.*
from table t
where id >= 3 and id < (select min(t2.id) from table t2 where t2.condition = 'b');
EDIT:
This query works fine on the SQL Fiddle:
select t.*
from t
where id >= 3 and id < (select min(t2.id) from t t2 where t2.condition = 'b');
If I understand what you are asking for, I believe this will work for you:
SELECT id, condition, values
FROM tableName
WHERE id > 2
AND condition != b
ORDER BY id
I hope that works for you.