Sql multiple max value in different dimensions - mysql

I am trying to find a max number of the value but the thing is my code choose only max value even though values are equal so how can I find max value and if there are same make a two rows
Here is the example:
(Ps; data is fake just for example question please let me if something is wrong)
select class.name as className,
school.schoolName as schoolName,
count(school.quantity) as totalNumber from class
join School on school.schoolName = class.schoolName
group by classname, schoolName
order by schoolName
and result is:
className | SchoolNAme | TotalNum
-----------|------------|----------
Math | A | 9
Bio | A | 2
History | A | 7
Music | A | 9
Math | B | 22
English | B | 8
Music | B | 1
History | B | 2
Geo | B | 2
Bio | B | 3
Math | C | 15
Geo | C | 2
and I found each school max number like this:
select ClassName,
SchoolName,
max(totalNumber) as Total
from
(
select class.name as className,
school.schoolName as schoolName,
count(school.quantity) as totalNumber from class
join School on school.schoolName = class.schoolName
group by classname, schoolName
order by schoolName
) t1
group by schoolName
and this is the result I have:
className | SchoolNAme | Total
-----------|------------|----------
Math | A | 9
Math | B | 22
Math | C | 15
but the problem I have as we can see two total number is 9 for school A
so how can I show all max number if they are equal.
this is what I want;
className | SchoolNAme | Total
-----------|------------|----------
Math | A | 9
Bio | A | 9
Math | B | 22
Math | C | 15
Thanks

Max will show 1 row per group, as you are grouping by school name and there are 3 schools you will get 3 groups and therefore the 3 rows returned.
School A class Bio appears from your data to have only 2 rows (count counts the rows per group) so Bio A 9 will never be returned, so the result wanted will never be obtainable. I guess you really expect Music A 9 instead along with Math A 9.
The School table is actually redundant in this case (based upon interpretation of the join and the ensuing data i.e. count is count the number of joined rows and thus the number of class rows).
As such i believe the following produces the results that you are after :-
WITH
cte1 AS
(SELECT name,schoolName, count() AS cnt FROM class
GROUP BY name,schoolName),
cte2 AS
(SELECT name, schoolName, max(cnt) AS m FROM cte1
GROUP BY schoolname)
-- SELECT * FROM cte2
SELECT cte1.name, cte1.schoolName, cte1.cnt FROM cte1
WHERE cte1.cnt = (SELECT m FROM cte2 WHERE cte2.schoolname = cte1.schoolname)
ORDER BY schoolName, name
;
The above was tested using the following :-
DROP TABLE IF EXISTS school;
DROP TABLE IF EXISTS class;
CREATE TABLE IF NOT EXISTS school (schoolName TEXT, quantity INTEGER);
CREATE TABLE IF NOT EXISTS class (name TEXT, schoolName);
INSERT INTO school VALUES ('A',1),('B',1),('C',1);
INSERT INTO class VALUES
('Math','A'),('Math','A'),('Math','A'),('Math','A'),('Math','A'),('Math','A'),('Math','A'),('Math','A'),('Math','A'),
('Bio','A'),('Bio','A'),
('History','A'),('History','A'),('History','A'),('History','A'),('History','A'),('History','A'),('History','A'),
('Music','A'),('Music','A'),('Music','A'),('Music','A'),('Music','A'),('Music','A'),('Music','A'),('Music','A'),('Music','A'),
('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),('Math','B'),
('English','B'),('English','B'),('English','B'),('English','B'),('English','B'),('English','B'),('English','B'),('English','B'),
('Music','B'),
('History','B'),('History','B'),
('Geo','B'),('Geo','B'),
('Bio','B'),('Bio','B'),('Bio','B'),
('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),('Math','C'),
('Geo','C'),('Geo','C')
;
select class.name as className, school.schoolName as schoolName, count() as totalNumber from class
join School on school.schoolName = class.schoolName
group by classname, school.schoolName
order by schoolName, totalNumber DESC
;
select ClassName, SchoolName, max(totalNumber) as Total
from
(
select class.name as className, school.schoolName as schoolName, count(school.quantity) as totalNumber from class
join School on school.schoolName = class.schoolName
group by classname,school.schoolname
order by schoolName
)
group by schoolName;
WITH
cte1 AS
(SELECT name,schoolName, count() AS cnt FROM class
GROUP BY name,schoolName),
cte2 AS
(SELECT name, schoolName, max(cnt) AS m FROM cte1
GROUP BY schoolname)
-- SELECT * FROM cte2
SELECT cte1.name, cte1.schoolName, cte1.cnt FROM cte1
WHERE cte1.cnt = (SELECT m FROM cte2 WHERE cte2.schoolname = cte1.schoolname)
ORDER BY schoolName, name
;
Results
First Query
The first query is your query, the results match the results in the question :-
Second Query
Again this matches the unwanted/wrong results from your question :-
Third Query
This I believe matches what you require :-

Related

Finding users with at least one of every item

For example, I have the following table called, Information
user_id | item
-------------------------
45 | camera
36 | smartphone
23 | camera
1 | glucose monitor
3 | smartwatch
2 | smartphone
7 | smartphone
2 | camera
2 | glucose monitor
2 | smartwatch
How can I check which user_id has at least one of every item?
The following items will not be static and may be different everytime. However in this example there are 4 unique items: camera, smartphone, smartwatch, glucose monitor
Expected Result:
Because user_id : 2 has at least one of every item, the result will be:
user_id
2
Here is what I attempted at so far, however if the list of items changes from 4 unique items to 3 unique items, I don't think it works anymore.
SELECT *
FROM Information
GROUP BY Information.user_id
having count(DISTINCT item) >= 4
One approach would be to aggregate by user_id, and then assert that the distinct item_id count matches the total distinct item_id count from the entire table.
SELECT
user_id
FROM Information
GROUP BY
user_id
HAVING
COUNT(DISTINCT item_id) = (SELECT COUNT(DISTINCT item_id) FROM Information);
You can try to use self-join by count and total count
SELECT t1.user_id
FROM (
SELECT user_id,COUNT(DISTINCT item) cnt
FROM T
GROUP BY user_id
) t1 JOIN (SELECT COUNT(DISTINCT item) cnt FROM T) t2
WHERE t1.cnt = t2.cnt
or exists
Query 1:
SELECT t1.user_id
FROM (
SELECT user_id,COUNT(DISTINCT item) cnt
FROM T
GROUP BY user_id
) t1
WHERE exists(
SELECT 1
FROM T tt
HAVING COUNT(DISTINCT tt.item) = t1.cnt
)
Results:
| user_id |
|---------|
| 2 |
One more way of solving this problem is by using CTE and dense_rank function.
This also gives better performance on MySQL. The Dense_Rank function ranks every item among users. I count the number of distinct items and say pick the users who have the maximum number of distinct items.
With Main as (
Select user_id
,item
,Dense_Rank () over (
Partition by user_id
Order by item
) as Dense_item
From information
)
Select
user_id
From Main
Where
Dense_item = (
Select
Count(Distinct item)
from
information);

Group by names desc - get last entered values for a grouped name

I have an table like that:
id | name | v (lvl)
11 | Jane | 6
12 | John | 5
13 | Jane | 6
14 | John | 5
15 | Jane | 7
16 | Jane | 5
In my autocomplete form now id like to group the names but get the last value (value with biggest id). In the example above would be
Jane | 5
I tried with combinations like distinct, group by, order by. But im always get
Jane | 6
or grouped like this and reversed:
Jane | 6
Jane | 7
Jane | 5
I would need something like this:
SELECT name,lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY lvl DESC
)
GROUP BY name
EDIT: I won't get the highest lvl, i want get the lvl of the highest id, grouped by name. Thats all. My example above would be the best explanation what i like to get.
In the inner query i change the order to DESC for all and in the outer i group it by names. But i get an error for this.
EDIT 2 I finally did at my own. The correct solution (i was already close):
SELECT a.name, a.lvl FROM
(
SELECT DISTINCT name, lvl FROM pora WHERE name LIKE 'Jane' ORDER BY id DESC
)as a
GROUP BY name
LIKE without % is just =
SELECT *
FROM yourTable
WHERE name = 'Jane'
ORDER BY id DESC
LIMIT 1
But because you mention autocomplete functionality you should use:
WHERE name LIKE 'Jane%'
To have the latest, you need to have a field dateAdded which stores the date you ran the insert command.
Following which, you use MAX(dateAdded) to get the latest ID (since, as you mentioned, it may decrease as well)
UPDATE:
if ID doesn't decrease, you can always use MAX(ID)
SELECT MAX(id), v from tablename where name = 'Jane'
UPDATE:
This has been tested:
SELECT ID, v from tableName where ID = (SELECT MAX(ID) as ID from tableName where name like '%Jane%')
Try the following query (h/t #lamak)
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY name
ORDER BY [id] DESC)
FROM poro
)
SELECT *
FROM CTE
WHERE RN = 1

mysql "and" logic within result set

Say I have a data set like the following:
table foo
id | employeeType | employeeID
-------------------------
1 | Developer | 1
2 | Developer | 2
3 | Developer | 3
4 | Manager | 1
5 | Manager | 4
6 | Manager | 5
7 | CEO | 1
8 | CEO | 6
and I wanted to run a query that would return all the employeeids (along with the employeeTypes) where there is a common employee id between all employeeTypes (that's the 'and' logic. ONly employeeIDs that have all employeeTypes will return. employeeType = Developer and employeeType=Manager and employeeType=CEO). For the data above the example output would be
result table
id | employeeType | employeeID
-------------------------
1 | Developer | 1
4 | Manager | 1
7 | CEO | 1
I was able to do this when I only had only TWO employeeTypes by self joining the table like this.
select * from foo as fooOne
join foo as fooTwo
on fooOne.employeeID = fooTwo.employeeID
AND
fooOne.employeeType <> fooTwo.employeeType
that query returns a result set with values from fooTwo when the 'and' logic matches, but again, only for two types of employees. My real use case scenario dictates that I need to be able to handle a variable number of employeeTypes (3, 4, 5, etc...)
Any thoughts on this would be greatly appreciated.
This should return the rows that you want:
SELECT foo.*
FROM
foo
WHERE
employeeID IN (
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) =
(SELECT COUNT(DISTINCT employeeType)
FROM foo)
)
Please see a fiddle here.
The inner query will return the number of distinct employee types:
(SELECT COUNT(DISTINCT employeeType) FROM foo)
The middle query will return all the employee IDs that have the maximum number of employee types:
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) =
(SELECT COUNT(DISTINCT employeeType) FROM foo)
and the outer query will return the whole rows.
You can try a subquery to make it dynamic
SELECT employeeID, employeeType
FROM foo
WHERE employeeID IN (
SELECT employeeID
FROM foo
GROUP BY employeeID
HAVING COUNT(DISTINCT employeeType) = (SELECT COUNT(DISTINCT employeeType) FROM foo)
)
I agree that this might be looked down as a very inefficient/hacky way of doing things, but this should still get the job done. And frankly, I can't see any other way out of this.
SELECT * FROM (
SELECT EMPLOYEE_ID, GROUP_CONCAT(DISTINCT EmployeeType ORDER BY EmployeeType) AS Roles
FROM EMPLOYEES GROUP BY EMPLOYEE_ID
) EMPLOYEE_ROLES
WHERE EMPLOYEE_ROLES.Roles = 'CEO,Developer,Manager';
Note that the comma separated list of roles provided in the end is in the alphabetical order.

Count number of distinct rows for multiple values

Let's consider this table specifing how many times a person bought a property.
+--------+----------+
| user | property |
+--------+----------+
| john | car |
| john | car |
| john | house |
| peter | car |
| peter | car |
| amanda | house |
| amanda | house |
+--------+----------+
I need to know how many times a car was bought once, how many times a house was bought once, etc. Something like this:
+----------+---+---+
| property | 1 | 2 |
+----------+---+---+
| cars | 4 | 2 |
| house | 3 | 1 |
+----------+---+---+
How many times a car was bought? Four, two for peter and two for john.
How many times a car was bought twice? Two, for the same guys.
How many times a house was bought? Three, two for amanda and once for john.
How many times a house was bought twice? Only once, for amanda
Is this possible to do this only using SQL queries?
I don't care about performance or hackish ways.
There are more than two frequencies.
There's a fixed set of time a person can buy a property (5) so it's not problem to specify the columns manually in the query. I mean there's not problem doing something like:
SELECT /* ... */ AS 1, /* ... */ AS 2, /* ... */, AS 3 /* ... */
SELECT DISTINCT #pr := prop,
(SELECT COUNT(1) FROM tbl WHERE prop = #pr LIMIT 1),
(SELECT COUNT(1) FROM
(SELECT *, COUNT(*) cnt
FROM tbl
GROUP BY usr, prop
HAVING cnt = 2) as tmp
WHERE `tmp`.prop = #pr LIMIT 1)
FROM tbl;
Yes, it is not the best method; but hey, you get the answers as desired.
Also, it'll generate the results for any kind of property in your table.
The fiddle link lies here.
P.S.: 60 tries O_O
I am here since you posted the question. Good one...
Here is a way to do it exactly as you asked for, with just groups and counts.
The trick is that I concatenate the user and property columns to produce a unique "id" for each, if we could call it that. It should work independently of the count of purchases.
SELECT C.`property`, COUNT(C.`property`), D.`pcount` from `purchases` C
LEFT JOIN(
SELECT A.`property`, B.`pcount` FROM `purchases` A
LEFT JOIN (
SELECT `property`,
CONCAT(`user`, `property`) as conc,
COUNT(CONCAT(`user`, `property`)) as pcount
FROM `purchases` GROUP BY CONCAT(`user`, `property`)
) B
ON A.`property` = B.`property`
GROUP BY B.pcount
) D
ON C.`property` = D.`property`
GROUP BY C.`property`
SQL Fiddle
MySQL 5.5.30 Schema Setup:
CREATE TABLE Table1
(`user` varchar(6), `property` varchar(5))
;
INSERT INTO Table1
(`user`, `property`)
VALUES
('john', 'car'),
('john', 'car'),
('john', 'house'),
('peter', 'car'),
('peter', 'car'),
('amanda', 'house'),
('amanda', 'house')
;
Query 1:
select t.property, t.total, c1.cnt as c1, c2.cnt as c2, c3.cnt as c3
from
(select
t.property ,
count(t.property) as total
from Table1 t
group by t.property
) as t
left join (
select property, count(*) as cnt
from (
select
property, user, count(*) as cnt
from table1
group by property, user
having count(*) = 1
) as i1
group by property
) as c1 on t.property = c1.property
left join (
select property, count(*) as cnt
from (
select
property, user, count(*) as cnt
from table1
group by property, user
having count(*) = 2
) as i2
group by property
) as c2 on t.property = c2.property
left join (
select property, count(*) as cnt
from (
select
property, user, count(*) as cnt
from table1
group by property, user
having count(*) = 3
) as i3
group by property
) as c3 on t.property = c3.property
Results:
| PROPERTY | TOTAL | C1 | C2 | C3 |
-------------------------------------------
| car | 4 | (null) | 2 | (null) |
| house | 3 | 1 | 1 | (null) |
You may try following.
SELECT COUNT(TABLE1.PROPERTY) AS COUNT, PROPERTY.USER FROM TABLE1
INNER JOIN (SELECT DISTINCT PROPERTY, USER FROM TABLE1) AS PROPERTY
ON PROPERTY.PROPERTY = TABLE1.PROPERTY
AND PROPERTY.USER = TABLE1.USER
GROUP BY TABLE1.USER, PROPERTY.PROPERTRY
tested similar in MySQL
try this
SELECT property , count(property) as bought_total , count(distinct(user)) bought_per_user
FROM Table1
GROUP BY property
the output will be like that
PROPERTY | BOUGHT_TOTAL | BOUGHT_PER_USER
________________________________________________________
car | 4 | 2
house | 3 | 2
DEMO SQL FIDDLE HERE
You should be able to do this with sub-selects.
SELECT property, user, COUNT(*) FROM purchases GROUP BY property, user;
will return you the full set of grouped data that you want. You then need to look at the different frequencies:
SELECT property, freq, COUNT(*) FROM (SELECT property, user, COUNT(*) freq FROM purchases GROUP BY property, user) AS foo GROUP BY property, freq;
It's not quite in the format that you illustrated but it returns the data
I hope this can help u.....let us create one table first:
create table prop(user varchar(max),property varchar(max))
insert into prop values('john','car'),insert into prop values('john','car'),
insert into prop values('john','house'),insert into prop values('peter','car'),
insert into prop values('peter','car'),insert into prop values('amanda','house'),
insert into prop values('amanda','house')
1)how many times car was bought?
ANS: select count(property) from prop where property = 'car'
(4)
2)How many times a car was bought twice?
ANS: select user,COUNT(property) from prop where property = 'car' group by user
having COUNT(property) = 2
2-john
2-peter
3)How many times a house was bought?
ANS: select COUNT(property) from prop where property = 'house'
(3)
4)How many times a house was bought twice?
ANS: select user,COUNT(property) from prop where property='house' group by user
having COUNT(property)< =2
2-amanda
1-john

Query to Segment Results Based on Equal Sets of Column Value

I'd like to construct a single query (or as few as possible) to group a data set. So given a number of buckets, I'd like to return results based on a specific column.
So given a column called score which is a double which contains:
90.00
91.00
94.00
96.00
98.00
99.00
I'd like to be able to use a GROUP BY clause with a function like:
SELECT MIN(score), MAX(score), SUM(score) FROM table GROUP BY BUCKETS(score, 3)
Ideally this would return 3 rows (grouping the results into 3 buckets with as close to equal count in each group as is possible):
90.00, 91.00, 181.00
94.00, 96.00, 190.00
98.00, 99.00, 197.00
Is there some function that would do this? I'd like to avoid returning all the rows and figuring out the bucket segments myself.
Dave
create table test (
id int not null auto_increment primary key,
val decimal(4,2)
) engine = myisam;
insert into test (val) values
(90.00),
(91.00),
(94.00),
(96.00),
(98.00),
(99.00);
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row
from test,(select #row:=0) as r order by id
) as t
group by ceil(row/2)
+-------+--------+--------+
| lower | higher | total |
+-------+--------+--------+
| 90.00 | 91.00 | 181.00 |
| 94.00 | 96.00 | 190.00 |
| 98.00 | 99.00 | 197.00 |
+-------+--------+--------+
3 rows in set (0.00 sec)
Unluckily mysql doesn't have analytical function like rownum(), so you have to use some variable to emulate it. Once you do it, you can simply use ceil() function in order to group every tot rows as you like. Hope that it helps despite my english.
set #r = (select count(*) from test);
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row
from test,(select #row:=0) as r order by id
) as t
group by ceil(row/ceil(#r/3))
or, with a single query
select min(val) as lower,max(val) as higher,sum(val) as total from (
select id,val,#row:=#row+1 as row,tot
from test,(select count(*) as tot from test) as t2,(select #row:=0) as r order by id
) as t
group by ceil(row/ceil(tot/3))