SQL count second column for-each unique row of first column - mysql

I have this sample many-to-many table for students & subjects in a university
student | subject
-----------------
James | English
James | Physics
Paul | Mathematics
Paul | English
Paul | English
Paul | French
Jake | French
Jake | Mathematics
Paul | English
I need to know the SQL query for getting the count of subjects for each student like
student | # of subjects
------------------------
James | 2
Paul | 3
Jake | 2

All you need is GROUP BY student and COUNT(DISTINCT):
SELECT student, COUNT(DISTINCT subject) AS "# of subjects"
FROM students_subjects
GROUP BY student;

You need to group
CREATE TABLE `student_subject` (
`name` varchar(256) DEFAULT NULL,
`subject` varchar(256) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `student_subject`
--
INSERT INTO `student_subject` (`name`, `subject`) VALUES
('James', 'English'),
('James', 'Physics'),
('Paul', 'Mathematics'),
('Paul', 'English'),
('Paul', 'English'),
('Paul', 'French'),
('Jake', 'Mathematics'),
('Jake', 'French');
SELECT name, COUNT(distinct subject) AS "subject_count"
FROM student_subject
GROUP BY name,subject order by name desc
#########output##############
Name subject_count
('Jake' , 2),
('James', 2),
('Paul' , 3);

SELECT student, count(*)
FROM table
GROUP BY student

You need to group the select by student:
SELECT student, count(*) as NumberOfSubjects
FROM table_name
GROUP BY student

Select student , count(subject) from Table Group by student

Related

I have to filter records by retrieving from SQL table. I want the values in the same field in my table column in sql to not show again

I am not able to do so please help. In the code I have written below there are two values in Speciality for program id 1. So is there a way to filter so that value are not shown again in the filtered results i.e. free lunch as specified here. While filtering i am getting checkbox like below, when I am retrieving from database.
a Free meal, Free lunch
b Free lunch
c Free Dinner
I want a to only show Free meal
INSERT INTO `programs` (`ProgramID`, `UserID`,`Speciality`) VALUES
(1, 'huy45', 'Free meal, Free lunch'),
(2, 'ga32','Free lunch'),
(3, 'sharvar3','Free Dinner'),
There is repeated informations in your table, and you don't want it. DRY !.
I would use another table to store the speciality, such as :
Speciality
id | name
----+-------------
1 | Free meal
2 | Free lunch
3 | Free dinner
So you can easilly use a foreign key to store such informations in your table programs
Next, you don't want to store serialized informations. This goes against the purpose of using a RDBMS.
I would structure the table programs like this :
ProgramID | UserID | SpecialityID
-----------+------------+--------------
1 | 'huy45' | 1
1 | 'huy45' | 2
2 | 'ga32' | 2
3 | 'sharvar3' | 3
To retrieve the ProgramID, UserID and Speciality for the Speciality name 'Free meal', ou can use then this query :
SELECT p.`ProgramID`,
p.`UserID`,
s.`name` AS "Speciality Name"
FROM `programs` p
INNER JOIN `Speciality` s
ON p.SpecialityID = s.id
WHERE s.`name` = 'Free lunch';
Schema (MySQL v5.7)
CREATE TABLE Speciality (
`id` INTEGER,
`name` VARCHAR(11)
);
INSERT INTO Speciality
(`id`, `name`)
VALUES
(1, 'Free meal'),
(2, 'Free lunch'),
(3, 'Free dinner');
CREATE TABLE programs (
`ProgramID` INTEGER,
`UserID` VARCHAR(10),
`SpecialityID` INTEGER
);
INSERT INTO programs
(`ProgramID`, `UserID`, `SpecialityID`)
VALUES
(1, 'huy45', 1),
(1, 'huy45', 2),
(2, 'ga32', 2),
(3, 'sharvar3', 3);
Query #1
SELECT p.`ProgramID`,
p.`UserID`,
s.`name` AS "Speciality Name"
FROM `programs` p
INNER JOIN `Speciality` s
ON p.SpecialityID = s.id
WHERE s.`name` = 'Free lunch';
| ProgramID | UserID | Speciality Name |
| --------- | ------ | --------------- |
| 1 | huy45 | Free lunch |
| 2 | ga32 | Free lunch |
View on DB Fiddle

LIMIT number of rows in a JOIN between MySQL tables

What I have
I have the following two tables in a MySQL database (version 5.6.35).
CREATE TABLE `Runs` (
`Name` varchar(200) NOT NULL,
`Run` varchar(200) NOT NULL,
`Points` int(11) NOT NULL
) DEFAULT CHARSET=latin1;
INSERT INTO `Runs` (`Name`, `Run`, `Points`) VALUES
('John', 'A08', 12),
('John', 'A09', 3),
('John', 'A01', 15),
('Kate', 'A02', 92),
('Kate', 'A03', 1),
('Kate', 'A04', 33),
('Peter', 'A05', 8),
('Peter', 'A06', 14),
('Peter', 'A07', 5);
CREATE TABLE `Users` (
`Name` varchar(500) NOT NULL,
`NumberOfRun` int(11) NOT NULL
) DEFAULT CHARSET=latin1;
INSERT INTO `Users` (`Name`, `NumberOfRun`) VALUES
('John', 2),
('Kate', 1),
('Peter', 3);
ALTER TABLE `Runs`
ADD PRIMARY KEY (`Run`);
What is my target
John have Users.NumberOfRun=2, so I will extract the 2 top records from Runs table
Kate have Users.NumberOfRun=1, so I will extract the 1 top record from Runs table
Peter have Users.NumberOfRun=3, so I will extract the 3 top records from Runs table
I would like to came to the following result
+-------+-----+--------+
| Name | Run | Points |
+-------+-----+--------+
| John | A01 | 15 |
| John | A08 | 12 |
| Kate | A02 | 92 |
| Peter | A06 | 14 |
| Peter | A05 | 8 |
| Peter | A07 | 5 |
+-------+-----+--------+
What I have tried
First of all, if it was SQL Server I would use ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ) AS [rn] function to the Runs table and then make a JOIN with the Users table on Users.NumberOfRun<=[rn].
I have read this document but it seems that PARTITONING in MySQL it is available since version 8.X, but I am using the 5.6.X version.
Finally, I have tried this query, based on this Stackoverflow answer:
SELECT t0.Name,t0.Run
FROM Runs AS t0
LEFT JOIN Runs AS t1 ON t0.Name=t1.Name AND t0.Run=t1.Run AND t1.Points>t0.Points
WHERE t1.Points IS NULL;
but it doesn't give me the row number, which is essentially for me to make a JOIN as described above.
SQL Fiddle to this example.
A combination of 'group_concat' and 'find_in_set', followed by the filtering using the position returned by 'find_in_set' will do the job for you.
GROUP_CONCAT will sort the data in descending order of points first.
GROUP_CONCAT(Run ORDER BY Points DESC)
FIND_IN_SET will then retrieve the number of rows you want to include in the result.
FIND_IN_SET(Run, grouped_run) BETWEEN 1 AND Users.NumberOfRun
The below query should work for you.
SELECT
Runs.*
FROM
Runs
INNER JOIN (
SELECT
Name, GROUP_CONCAT(Run ORDER BY Points DESC) grouped_run
FROM
Runs
GROUP BY Name
) group_max ON Runs.Name = group_max.Name
INNER JOIN Users ON Users.Name = Runs.Name
WHERE FIND_IN_SET(Run, grouped_run) BETWEEN 1 AND Users.NumberOfRun
ORDER BY
Runs.Name Asc, Runs.Points DESC;

SQL Join commands to discover highest level of product

So basically what I am trying to do is this... My task is to find people who have "CSM", "LSP" or "CSD" but also joining information from Table "client_personal" that matches the "client_no" from the results of the search on "CSM", "LSP", or "CSD"
Im sure Im doing this all "bass ackwords" but end goal is to show customer information including other fields in table "client_personal" so sales can do a sales pitch for those who have "CSM" but not records of "LSP" or "CSD" (considered higher levels) - Unfortunately there is 1: an assload of mess, tons of dupes, and multiple entries and 2: theres not an easier database to work with that would have "highest level achieved".
SELECT client_no,product,status,paid_date
FROM client_educational
LEFT JOIN client_personal
ON client_educational.client_no = client_personal.client_no
WHERE product='CSM'
OR product='LSP'
OR product='CSD'
Heres the databases that I am interested in:
client_educational
client_no product status pd_date
500000 CSM pd-cert 2018-06-20
500001 CSM pd-cert 2018-06-20
500001 LSP pd-cert 2018-06-20
500002 CSM pd-cert 2018-06-20
500002 LSP pd-cert 2018-06-20
500002 CSD pd-cert 2018-06-20
client_personal
client_no name email phone
500000 John Doe mail#mail.com 555-555-5555
500001 John Shmoe mail#mail.com 555-555-5555
500002 John Howe mail#mail.com 555-555-5555
So what Im needing is not only to combine the results, but also display only those who only have product "CSM" but not "LSP" or "CSD" by using the "client_no" row as the unique identifier....
Something like this...
client_no product status pd_date name email phone
500000 CSM pd-cert 2018-06-20 John Doe mail#mail.com 555-555-5555
Try it use left join
select
T1.`client_no`,
T2.`product`,
T2.`status`,
T2.`pd_date`,
T1.`name`,
T1.`email`,
T1.`phone`
from client_personal T1
left join client_educational T2 on T1.`client_no` = T2.`client_no`
and T2.`product` = 'CSM'
left join client_educational T3 on T1.`client_no` = T3.`client_no`
and T3.`product` IN('LSP', 'CSD')
where T2.`client_no` is not null
and T3.`client_no` is null
| client_no | product | status | pd_date | name | email | phone |
| 500000 | CSM | pd-cert | 2018-06-20 00:00:00 | John Doe | mail#mail.com | 555-555-5555 |
TEST DDL
CREATE TABLE client_educational
(`client_no` int, `product` varchar(3), `status` varchar(7), `pd_date` datetime)
;
INSERT INTO client_educational
(`client_no`, `product`, `status`, `pd_date`)
VALUES
(500000, 'CSM', 'pd-cert', '2018-06-20 00:00:00'),
(500001, 'CSM', 'pd-cert', '2018-06-20 00:00:00'),
(500001, 'LSP', 'pd-cert', '2018-06-20 00:00:00'),
(500002, 'CSM', 'pd-cert', '2018-06-20 00:00:00'),
(500002, 'LSP', 'pd-cert', '2018-06-20 00:00:00'),
(500002, 'CSD', 'pd-cert', '2018-06-20 00:00:00')
;
CREATE TABLE client_personal
(`client_no` int, `name` varchar(10), `email` varchar(13), `phone` varchar(12))
;
INSERT INTO client_personal
(`client_no`, `name`, `email`, `phone`)
VALUES
(500000, 'John Doe', 'mail#mail.com', '555-555-5555'),
(500001, 'John Shmoe', 'mail#mail.com', '555-555-5555'),
(500002, 'John Howe', 'mail#mail.com', '555-555-5555')
;
DB Fiddle - TEST DEMO
You could use conditional aggregation checks for your criteria display only those who only have product "CSM" but not "LSP" or "CSD"
select cp.client_no,cp.name,cp.email,cp.phone
from client_personal cp
join client_educational ce on cp.client_no = ce.client_no
group by cp.client_no,cp.name,cp.email,cp.phone
having sum(ce.product = 'CSM') > 0
and sum(ce.product IN('LSP', 'CSD')) = 0
In mysql when an expression is used inside sum(a= b) it will result as a boolean 0/1 so you can get your conditional count using above.
Another way using exists
select cp.*
from client_personal cp
join client_educational ce on cp.client_no = ce.client_no
where ce.product = 'CSM'
and not exists (
select 1
from client_educational
where product IN('LSP', 'CSD')
and client_no = cp.client_no
)
Demo for both queries

MySQL Fetch Result Using Multiple Joins in one query?

I have below tables
tbl_user
uid first_name last_name email_id
1 steve martin steve1#gmail.com
2 mark lee mark1#gmail.com
3 nelson wise nelson23#gmail.com
tbl_tier
tier_id tier_name points_required
1 Silver 100
2 Gold 200
3 Platinum 300
tbl_tier_earned
id tier_id uid
1 1 1
2 2 1
3 3 1
4 1 2
5 2 2
6 1 3
I need unique users with their current tiers like:
first_name last_name email_id current_tier
steve martin steve1#gmail.com Platinum
mark lee mark1#gmail.com Gold
I have tried below query but it gives me only 1 result:
SELECT u.first_name,u.last_name,u.email_id, t.tier_name
FROM tbl_tier_earned AS tte
INNER JOIN tbl_user AS u
ON u.uid = tte.uid
INNER JOIN tbl_tier AS t
ON tte.tier_id = t.tier_id
WHERE u.email_id!=""
ORDER BY t.points_required DESC LIMIT 0,1
How can I retrieve above data using mysql query?
It appears that the current tier is given by the max tier_id in the tbl_tier_earned table, for each user. One approach here would be to join the user table to a subquery on the tbl_tier_earned table which finds the max tier.
SELECT
u.first_name,
u.last_name,
u.email_id,
COALESCE(t2.tier_name, 'NA') AS current_tier
FROM tbl_user u
LEFT JOIN
(
SELECT uid, MAX(tier_id) AS max_tier_id
FROM tbl_tier_earned
GROUP BY uid
) t1
ON u.uid = t1.uid
LEFT JOIN tbl_tier t2
ON t1.max_tier_id = t2.tier_id;
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE tbl_user
(`uid` int, `first_name` varchar(6), `last_name` varchar(6), `email_id` varchar(18))
;
INSERT INTO tbl_user
(`uid`, `first_name`, `last_name`, `email_id`)
VALUES
(1, 'steve', 'martin', 'steve1#gmail.com'),
(2, 'mark', 'lee', 'mark1#gmail.com'),
(3, 'nelson', 'wise', 'nelson23#gmail.com')
;
CREATE TABLE tbl_tier
(`tier_id` int, `tier_name` varchar(8), `points_required` int)
;
INSERT INTO tbl_tier
(`tier_id`, `tier_name`, `points_required`)
VALUES
(1, 'Silver', 100),
(2, 'Gold', 200),
(3, 'Platinum', 300)
;
CREATE TABLE tbl_tier_earned
(`id` int, `tier_id` int, `uid` int)
;
INSERT INTO tbl_tier_earned
(`id`, `tier_id`, `uid`)
VALUES
(1, 1, 1),
(2, 2, 1),
(3, 3, 1),
(4, 1, 2),
(5, 2, 2),
(6, 1, 3)
;
Query 1:
SELECT c.first_name, c.last_name, c.email_id,
(SELECT tier_name from tbl_tier WHERE points_required = max(b.points_required)) as current_tier
FROM tbl_tier_earned a
INNER JOIN tbl_tier b ON a.tier_id = b.tier_id
INNER JOIN tbl_user c ON a.uid = c.uid
GROUP BY c.first_name, c.last_name, c.email_id
Results:
| first_name | last_name | email_id | current_tier |
|------------|-----------|--------------------|--------------|
| mark | lee | mark1#gmail.com | Gold |
| nelson | wise | nelson23#gmail.com | Silver |
| steve | martin | steve1#gmail.com | Platinum |

JOIN query in MySQL produces wrong result

I have two tables complaints and complaints_reply in my MySQl database. Users can add complaints which are stored in complaints the complaints reply are stored in complaints_reply table. I am trying to JOIN both these table contents on a specific condition. Before I mention what I am trying to get and the problem I faced, I will explain the structure of these two tables first.
NB: The person who adds complaints is complaint owner & person who adds a complaint reply is complaint replier. Complaint owner can also add replies. So he can either be the complaint owner or the complaint replier. The two tables have a one-to-many relationship. A complaint can have more than one complaint reply. member_id in complaint table represents complaint owner & mem_id in complaints_reply represent complaint replier
DESIRED OUTPUT:
Join the two tables and fetch values and show the complaint and complaint’s reply as a single result set. But the condition is kinda tricky. The last added complaint reply from the complaints_reply table should be fetched for the complaint in complaints table in such a way that the complaint owner should not be the complaint replier. I use posted_date & posted_time from complaints_reply table to fetch the last added complaint reply for a complaint & that complaint replier has to be shown in the result set.
So, from the sample data the tables contain now, the output that I should get is:
+------+---------+----------+-------------+-------------------+
| id | title |member_id |last_replier |last_posted_dt |
+------+---------+----------+-------------+-------------------+
| 1 | x | 1000 |2002 | 2015-05-2610:11:17|
| 2 | y | 1001 |1000 | 2015-05-2710:06:16|
+------+---------+----------+-------------+-------------------+
But what I got is:
+------+---------+----------+-------------+-------------------+
| id | title |member_id |last_replier |last_posted_dt |
+------+---------+----------+-------------+-------------------+
| 1 | x | 1000 |1001 | 2015-05-2610:11:17|
| 2 | y | 1001 |2000 | 2015-05-2710:06:16|
+------+---------+----------+-------------+-------------------+
The date is correct, but the returned complaint replier last_replier is wrong.
This is my query.
SELECT com.id,
com.title,
com.member_id,
last_comp_reply.last_replier,
last_comp_reply.last_posted_dt
FROM complaints com
LEFT JOIN
(SELECT c.id AS complaint_id,
c.member_id AS parent_mem_id,
cr.mem_id AS last_replier,
max(cr.posted_dt) AS last_posted_dt
FROM
(SELECT cr.complaint_id,cr.mem_id,c.id,c.member_id,(CONCAT(cr.posted_date,cr.posted_time)) AS posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id,
cr.mem_id,
posted_dt)cr,
complaints c
WHERE cr.complaint_id=c.id
GROUP BY cr.complaint_id,
c.id,
c.member_id) AS last_comp_reply ON com.id=last_comp_reply.complaint_id
Table structure for table complaints
CREATE TABLE IF NOT EXISTS `complaints` (
`id` int(11) NOT NULL,
`title` varchar(500) NOT NULL,
`member_id` int(11) NOT NULL,
`posted_date` date NOT NULL,
`posted_time` time NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
Indexes for table complaints
ALTER TABLE `complaints`
ADD PRIMARY KEY (`id`);
AUTO_INCREMENT for table complaints
ALTER TABLE `complaints`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=3;
Dumping data for table complaints
INSERT INTO `complaints` (`id`, `title`, `member_id`, `posted_date`, `posted_time`) VALUES
(1, 'x', 1000, '2015-05-05', '02:06:15'),
(2, 'y', 1001, '2015-05-14', '02:08:10');
Table structure for table complaints_reply
CREATE TABLE IF NOT EXISTS `complaints_reply` (
`id` int(11) NOT NULL,
`complaint_id` int(11) NOT NULL,
`comments` text NOT NULL,
`mem_id` int(11) NOT NULL,
`posted_date` date NOT NULL,
`posted_time` time NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=10 ;
Indexes for table complaints_reply
ALTER TABLE `complaints_reply`
ADD PRIMARY KEY (`id`);
AUTO_INCREMENT for table complaints_reply
ALTER TABLE `complaints_reply`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=10;
Dumping data for table complaints_reply
INSERT INTO `complaints_reply` (`id`, `complaint_id`, `comments`, `mem_id`, `posted_date`, `posted_time`) VALUES
(1, 1, 'reply1', 2000, '2015-05-08', '02:07:08'),
(2, 1, 'reply2', 2001, '2015-05-06', '06:05:08'),
(3, 1, 'reply3', 1000, '2015-05-14', '02:12:13'),
(4, 2, 'hola', 1000, '2015-05-27', '10:06:16'),
(5, 2, 'hello', 2000, '2015-05-04', '03:09:09'),
(6, 2, 'gracias', 1001, '2015-05-31', '06:12:18'),
(7, 1, 'reply4', 1001, '2015-01-04', '04:08:12'),
(8, 2, 'puta', 1001, '2015-06-13', '06:12:18'),
(9, 1, 'reply5', 1000, '2015-06-01', '04:08:12'),
(10, 1, 'reply next', 2002, '2015-05-26', '10:11:17');
P.S.
To give an idea about what my query is all about, I'll explain the sub query that is used to combine the tables & give result based on the condition: complaint owner should not be the complaint replier is:
SELECT cr.complaint_id,
cr.mem_id,
c.id,
c.member_id,
(CONCAT(cr.posted_date,cr.posted_time)) AS posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id,
cr.mem_id,
posted_dt
And the result for this is:
+--------------+---------+----------+-------------+-------------------+
| complaint_id | mem_id | id |member_id | posted_dt |
+--------------+---------+------- +-------------+-------------------+
| 1 | 1001 | 1 |1000 | 2015-01-0404:08:12|
| 1 | 2000 | 1 |1000 | 2015-05-0802:07:08|
| 1 | 2001 | 1 |1000 | 2015-05-0606:05:08|
| 1 | 2002 | 1 |1000 | 2015-05-2610:11:17|
| 2 | 1000 | 2 |1001 | 2015-05-2710:06:16|
| 2 | 2000 | 2 |1001 | 2015-05-0403:09:09|
+--------------+---------+----------+-------------+-------------------+
member_id here represents complaint owner and mem_id represents complaint replier
The inner query gives the result based on the condition, then everything after this goes haywire. I don't know where I made mistake. The complaint replies added by complaint owner is not fetched in this table. So far so good. Is there any alternative way to get the result from here?
This query gives the result.
SELECT com.id AS complaint_id,
com.member_id AS parent_mem_id,
crep.mem_id AS last_replier,
crl.last_posted_dt
FROM complaints com
LEFT JOIN complaints_reply crep ON com.id=crep.complaint_id
JOIN
(SELECT cr.complaint_id,
max(CONCAT(cr.posted_date,'_',cr.posted_time)) AS last_posted_dt
FROM complaints_reply cr,
complaints c
WHERE cr.complaint_id=c.id
AND cr.mem_id!=c.member_id
GROUP BY cr.complaint_id)crl ON CONCAT(crep.posted_date,'_',crep.posted_time)=crl.last_posted_dt
AND crep.complaint_id=crl.complaint_id