MySQL Group results - mysql

the following query:
SELECT COUNT(*) AS count, items.category, customers.sector
FROM customers
LEFT JOIN items ON items.customer_id = customers.id
GROUP BY items.category, customers.sector
ORDER BY customers.sector ASC
Gives me this result:
| count | category | sector |
|-------|-----------------------|--------------|
| 3 | A-Frames & Trolleys | Automotive |
| 4 | Suction Mounts | Automotive |
| 1 | Hand Cups | Automotive |
| 103 | Glazing Tools | Construction |
| 2 | A-Frames & Trolleys | Construction |
| 2 | Suction Mounts | Construction |
|_______|_______________________|______________|
I want the sector column to be unique and to show the category with the biggest count
eg:
| count | category | sector |
|-------|-----------------------|--------------|
| 4 | Suction Mounts | Automotive |
| 103 | Glazing Tools | Construction |
|_______|_______________________|______________|
Thanks

I think the easiest way to do this is the substring_index()/group_concat() trick:
select max(count) as `count`,
substring_index(group_concat(category order by count desc), ',', 1) as category,
sector
from (SELECT COUNT(*) AS count, i.category, c.sector
FROM customers c LEFT JOIN
items i
ON i.customer_id = c.id
GROUP BY i.category, c.sector
) t
group by c.sector;

Related

Optimizing MySQL SELECT query with subqueries

Hi is it possible to optimize the SELECT query below? The query itself is working but when we are querying large data we are encountering a message in php which is "Maximum execution time of 30 seconds exceeded". I reduced the columns in the query up to Topping3 but I am querying up to Topping15 column.
SELECT
itemID,
itemName,
Topping1,
(SELECT DISTINCT Description FROM items WHERE PLU = a.Topping1 AND ClientID = 1679) AS Top1_desc,
Topping2,
(SELECT DISTINCT Description FROM items WHERE PLU = a.Topping2 AND ClientID = 1679) AS Top2_desc,
Topping3,
(SELECT DISTINCT Description FROM items WHERE PLU = a.Topping2 AND ClientID = 1679) AS Top3_desc,
FROM
items a
WHERE
...
Current data on items table
--------------------------------------------------------------------
| itemID | itemName | Description | Topping1 | Topping2 | Topping3 |
--------------------------------------------------------------------
| 1 | HAM1 | Hamburger | ONI1 | TOMO1 | |
--------------------------------------------------------------------
| 2 | ONI1 | Onion | | | |
--------------------------------------------------------------------
| 3 | TOMO1 | Tomato | | | |
--------------------------------------------------------------------
and this is the expected result
--------------------------------------------------------------------------------------------------------
| itemID | itemName | Description | Topping1 | Top1_desc | Topping2 | Top2_desc | Topping3 | Top3_desc |
--------------------------------------------------------------------------------------------------------
| 1 | HAM1 | Hamburger | ONI1 | Onion | TOMO1 | Tomato | | |
--------------------------------------------------------------------------------------------------------
| 2 | ONI1 | Onion | | | | | | |
--------------------------------------------------------------------------------------------------------
| 3 | TOMO1 | Tomato | | | | | | |
--------------------------------------------------------------------------------------------------------
This should be fast, unless there are a lot of entries with the same client ID. You could add LIMIT 1 after all subqueries, i.e.: (SELECT DISTINCT Description FROM items WHERE PLU = a.Topping1 AND ClientID = 1679 LIMIT 1) etc.
I suspect however that it is an index problem. Are the fields ClientID and PLU indexed?
EDIT: Alternative for your query:
SELECT
itemID,
itemName,
Topping1,
t1.Description AS Top1_desc,
Topping2,
t2.Description AS Top2_desc,
Topping3,
t3.Description AS Top3_desc,
FROM
items a
LEFT JOIN
items t1 ON t1.PLU=a.Topping1 AND t1.ClientID = 1679
LEFT JOIN
items t2 ON t2.PLU=a.Topping2 AND t2.ClientID = 1679
LEFT JOIN
items t3 ON t3.PLU=a.Topping3 AND t3.ClientID = 1679
WHERE
...
GROUP BY
a.itemID
Fields itemID, PLU and ClientID need indexes.

Retrieving a variable number of rows using a table join

This is an addition layer of complexity on another question I asked here: Using GROUP BY and ORDER BY in same MySQL query
Same table structure and problem, except this time imagine that the past_election table is now set up as...
| election_ID | Date | jurisdiction | Race | Seats |
|-------------|------------|----------------|---------------|-------|
| 1 | 2016-11-08 | federal | president | 1 |
| 2 | 2016-11-08 | state_district | state senator | 2 |
(last record has seats set as 2 instead of 1.)
I want to use the Seats number to grab different numbers of records, ordered by the number of votes, for each group. So in this case with the following additional tables...
candidates
| Candidate_ID | FirstName | LastName | MiddleName |
|--------------|-----------|----------|------------|
| 1 | Aladdin | Arabia | A. |
| 2 | Long | Silver | John |
| 3 | Thor | Odinson | NULL |
| 4 | Baba | Yaga | NULL |
| 5 | Robin | Hood | Locksley |
| 6 | Sherlock | Holmes | J. |
| 7 | King | Kong | Null |
past_elections-candidates
| ID | PastElection | Candidate | Votes |
|----|--------------|-----------|-------|
| 1 | 1 | 1 | 200 |
| 2 | 1 | 2 | 100 |
| 3 | 1 | 6 | 50 |
| 4 | 2 | 3 | 75 |
| 5 | 2 | 4 | 25 |
| 6 | 2 | 5 | 150 |
| 7 | 2 | 7 | 100 |
I would expect the following output:
| election_ID | FirstName | LastName | votes | percent |
|-------------|-----------|----------|-------|---------|
| 1 | Aladdin | Arabia | 200 | 0.5714 |
| 2 | Robin | Hood | 150 | 0.4286 |
| 2 | King | Kong | 100 | 0.2857 |
I've tried setting a variable and using that with a LIMIT statement but variables don't work in limits. I've also tried using ROW_NUMBER() (I'm not using MySQL 8.0 so this won't work but I'd be willing to upgrade if it did) or a related workaround like #row_number := IF ... and then filtering based on the row number but nothing has worked.
Last tried query:
SELECT pe.election_ID as elec,
pe.Seats as s,
pecs.row_num,
c.FirstName,
c.LastName,
pecs.max_votes AS votes,
pecs.max_votes / pecs.total_votes AS percent
FROM past_elections pe
JOIN `past_elections-candidates` pec ON pec.PastElection = pe.election_ID
JOIN (SELECT PastElection,
Candidate,
#row_num := IF(PastElection = #current_election, #current_election + 1, 1) as row_num,
MAX(Votes) AS max_votes,
SUM(Votes) AS total_votes,
#current_election := PastElection
FROM `past_elections-candidates`
GROUP BY PastElection) pecs ON pecs.PastElection = pec.PastElection AND pecs.row_num <= pe.Seats
JOIN candidates c ON c.Candidate_ID = pec.Candidate
Use MySQL 8 regardless ;)
Use ROW_NUMBER to order the past elections:
SELECT *, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
Join this to past_elections as a subquery (this is just the bit you're stuck on with the "using pe.seats to vary the number of rows returned per election" and doesn't include the percent bits:
SELECT *
FROM
past_elections pe
INNER JOIN
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
) pecr
ON pecr.pastelection = pe.electionid AND
pecr.rown <= pe.seats
If you want to test things out on 8 before you upgrade, loads of the db fiddle sites support v8
ps; percent-y stuff can be done at the same time as the ROW_NUMBER with eg:
votes/SUM(votes) OVER(PARTITION BY past_election)
eg for election ID 1 that sum will be 200+100+50, giving 200/350 = ~57%
SELECT *, votes/SUM(votes) OVER(PARTITION BY past_election) as pcnt, ROW_NUMBER() OVER(PARTITION BY pastelection ORDER BY votes DESC) as rown
FROM `past_elections-candidates`
You need to calc it before filtering
I don't have the right fields listed but this is as close as I'll probably get tonight... I've gotten the rows I need but need to join the candidate table to get the name out...
Using Dense_Rank seems to work for this...
SELECT * FROM (
SELECT pec.PastElection,
c.FirstName,
c.LastName,
pec.Votes,
pecs.totalVotes,
pe.Seats as s,
DENSE_RANK() OVER(PARTITION BY PastElection ORDER BY Votes DESC) as rank_votes
FROM `past_elections-candidates` pec
JOIN (SELECT PastElection,
Max(Votes) as maxVotes,
Sum(Votes) as totalVotes
FROM `past_elections-candidates`
GROUP BY PastElection) pecs ON pecs.PastElection = pec.PastElection
JOIN `past_elections` pe ON pec.PastElection = pe.election_ID
JOIN candidates c ON c.Candidate_ID = pec.Candidate
) t WHERE rank_votes <= s;
This results in
| PastElection | FirstName | LastName | Votes | totalVotes | s | rank_votes |
|--------------|-----------|----------|-------|------------|---|------------|
| 1 | Aladdin | Arabia | 200 | 350 | 1 | 1 |
| 2 | Robin | Hood | 150 | 350 | 2 | 1 |
| 2 | King | Kong | 100 | 350 | 2 | 2 |
I guess it's just kind of messy having the rank_votes and s columns in the data, but that's honestly fine with me if it gets the results I need.

MySQL group_concat with select inside select

I have a glitch which i cannot solve,let me elaborate...
These are my MySQL tables...
Therapists table
id therapist_name
1 Therapist 1
2 Therapist 2
Location table
+-----+------------+--+
| id | name | |
+-----+------------+--+
| 1 | Location 1 | |
| 2 | Location 2 | |
| 3 | Location 3 | |
+-----+------------+--+
Days_location table
+-----+-----------+--------------+-------------+--+
| id | day | therapist_id | location_id | |
+-----+-----------+--------------+-------------+--+
| 1 | monday | 1 | 1 | |
| 2 | monday | 1 | 2 | |
| 3 | wednesday | 1 | 3 | |
| 4 | wednesday | 2 | 1 | |
| 5 | tuesday | 2 | 2 | |
| 6 | friday | 2 | 1 | |
| 7 | friday | 2 | 2 | |
| 8 | friday | 1 | 1 | |
+-----+-----------+--------------+-------------+--+
Now i want to get every therapist with locations for every day,for example something like this:
therapist_name=>Therapist 1,day_locations=>monday(Location1,Location2),friday(Location1)
I need it to be as a select variable,this was my query but i got stuck there:
SELECT t.*,GROUP_CONCAT(
SELECT CONCAT(dl2.day,GROUP_CONCAT(dl2.location_id)) as concated
FROM days_location dl2
WHERE therapist_id=85
GROUP BY dl2.day
) as day_location
FROM therapists t
LEFT JOIN days_location dl
ON dl.therapist_id=t.id
This of course doesn't work,what am i doing wrong...should i try a different approach or make my tables different?
I believe this is what you're looking for, or could get you started:
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
For therapists.id = 1 this should give you results:
+----------------+-----------+-----------------------+
| therapist_name | day | locations |
+----------------+-----------+-----------------------+
| Therapist 1 | monday | Location 1,Location 2 |
| Therapist 1 | wednesday | Location 3 |
| Therapist 1 | friday | Location 1 |
+----------------+-----------+-----------------------+
If you need to concatenate day with locations column then use a simple CONCAT():
SELECT
therapist_name,
CONCAT(day, '(', locations, ')') AS locations
FROM (
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
) t
GROUP BY therapist_name, locations
Output should look like:
+----------------+-------------------------------+
| therapist_name | locations |
+----------------+-------------------------------+
| Therapist 1 | monday(Location 1,Location 2) |
| Therapist 1 | wednesday(Location 3) |
| Therapist 1 | friday(Location 1) |
+----------------+-------------------------------+
If you need to group it all into one row for each therapist, then you could GROUP_CONCAT() again.
Edit after comments:
SELECT
therapist_name,
GROUP_CONCAT( CONCAT(day, '(', locations, ')') SEPARATOR ',' ) AS locations
FROM (
SELECT
t.therapist_name,
dl.day,
GROUP_CONCAT(DISTINCT dl.name SEPARATOR ',') AS locations
FROM
therapists t
LEFT JOIN days_location dl ON dl.therapist_id = t.id
LEFT JOIN location l ON dl.location_id = l.id
GROUP BY t.therapist_name, dl.day
) t
GROUP BY therapist_name
I haven't tested the code so there may be some minor mistakes to tweak. No way of testing it atm.

mysql - add header to query across join

I have the following so far:
SELECT D.department AS dept, C.name AS subdept
FROM (SELECT DISTINCT department FROM classes WHERE web != 0 ORDER BY department,name LIMIT 5) D
LEFT JOIN classes C ON (C.department = D.department)
ORDER BY D.department,C.name
Which results in something like:
+------+-------------------------------+
| dept | subdept |
+------+-------------------------------+
| BOOK | CHILDRENS BOOKS |
| BOOK | DVD'S |
| CLOT | ACCESSORIES |
| CLOT | APRONS |
| FEED | BIBS & BURP CLOTHS |
| FEED | BOTTLE & FOOD WARMERS |
+------+-------------------------------+
What Im trying to get is a 'header' for the each department with a null subdept value such as:
+------+-------------------------------+
| dept | subdept |
+------+-------------------------------+
| BOOK | null |
| BOOK | CHILDRENS BOOKS |
| BOOK | DVD'S |
| CLOT | null |
| CLOT | ACCESSORIES |
| CLOT | APRONS |
| FEED | null |
| FEED | BIBS & BURP CLOTHS |
| FEED | BOTTLE & FOOD WARMERS |
+------+-------------------------------+
The structure of the tables: departments table has id primary key to classes department field as foreign key.
Departmens: id | name (of department)
Classes: department | name (of class)
Based on what DanfromGermany has shown me I have:
SELECT D.department AS dept, C.name AS subdept
FROM
(SELECT DISTINCT department FROM classes WHERE web != 0 ORDER BY department,name LIMIT 5) D
LEFT JOIN classes C ON (C.department = D.department)
GROUP BY D.department, C.name WITH ROLLUP
Which now gives:
+--------+-------------------------------+
| dept | subdept |
+--------+-------------------------------+
| BOOK | CHILDRENS BOOKS |
| BOOK | DVD'S |
| BOOK | [NULL] |
| CLOT | ACCESSORIES |
| CLOT | APRONS |
| CLOT | [NULL] |
| FEED | BIBS & BURP CLOTHS |
| FEED | BOTTLE & FOOD WARMERS |
| FEED | [NULL] |
| GEAR | BOOSTER CAR SEATS |
| GEAR | CAR SEAT ACCESSORIES |
| GEAR | [NULL] |
| GIFT | BABY BASKETS & DIAPER CAKES |
| GIFT | BANKS |
| GIFT | [NULL] |
| [NULL] | [NULL] |
+--------+-------------------------------+
OK last edit:
It works by subquery to use order by:
SELECT * FROM
(SELECT D.department AS dept, C.name AS subdept
FROM
(SELECT DISTINCT department FROM classes WHERE web != 0 ORDER BY department,name LIMIT 5) D
LEFT JOIN classes C ON (C.department = D.department)
GROUP BY D.department, C.name WITH ROLLUP
) T
ORDER BY dept,subdept
Change your query to have a GROUP BY clause,
then use WITH ROLLUP.
See in the middle of that page:
http://dev.mysql.com/doc/refman/5.1/en/group-by-modifiers.html
Or google for "GROUP BY WITH ROLLUP mysql"
Something like this (untested):
SELECT D.department AS dept, C.name AS subdept
FROM
(SELECT DISTINCT department FROM classes WHERE web != 0 ORDER BY department,name LIMIT 5) D
LEFT JOIN classes C ON (C.department = D.department)
GROUP BY dept, subdept WITH ROLLUP
ORDER BY D.department,C.name

Categorize the identical rows without repeating one-to-many relationship using LEFT JOIN

pardon my question title, I'm not sure what should I put it, I have these two tables as below.
products orders
+------+----------+ +--------+------+-------+
| id | name | | id | qty | pid |
+------+----------+ +--------+------+-------+
| 1 | mouse | | 10001 | 20 | 1 |
| 2 | keyboard | | 10002 | 15 | 3 |
| 3 | headset | | 10004 | 5 | 3 |
+------+----------+ | 10005 | 12 | 2 |
| 10006 | 18 | 1 |
+--------+------+-------+
This is the LEFT JOIN query I am using and the output
SELECT p.id AS No, p.name AS ProductName, o.qty AS Quantity
FROM products AS p
LEFT JOIN orders AS o ON p.id = o.pid
+------+-------------+----------+
| No | ProductName | Quantity |
+------+-------------+----------+
| 1 | mouse | 20 |
| 1 | mouse | 18 |
| 2 | keyboard | 12 |
| 3 | headset | 15 |
| 3 | headset | 5 |
+------+-------------+----------+
What I am trying to achieve is an output as below:
+------+-------------+----------+
| No | ProductName | Quantity |
+------+-------------+----------+
| 1 | mouse | 20 |
| | | 18 |
| 2 | keyboard | 12 |
| 3 | headset | 15 |
| | | 5 |
+------+-------------+----------+
My question is it possible to do so? Any reply and suggestions is greatly appreciate. Thanks.
P/S: I also have tried using the GROUP_CONCAT(qty SEPARATOR ",") but it returns the result in one row as I may have more additional column to add in the Orders table in the future and it will be difficult to read.
Sure, it's possible — and without needing to use variables:
SELECT IF(c.min_oid IS NOT NULL, a.id, NULL) AS No,
IF(c.min_oid IS NOT NULL, a.name, NULL) AS ProductName,
b.qty AS Quantity
FROM products a
JOIN orders b ON a.id = b.pid
LEFT JOIN (
SELECT MIN(id) AS min_oid
FROM orders
GROUP BY pid
) c ON b.id = c.min_oid
ORDER BY a.id,
b.id
Basically what it's doing is if the row is not the minimum order id of a particular product, display blank (NULL), otherwise display the information.
SQLFiddle Demo
In this case you can use MySQL variables. I store the previous product id in the variable #prev, and only if it changes we output the product name.
http://www.sqlfiddle.com/#!2/d5fd6/9
SET #prev := NULL;
SELECT
IF( #prev = p.id, NULL, p.id) AS No,
IF( #prev = p.id, NULL, p.name) AS ProductName,
o.qty AS Quantity
,#prev := p.id
FROM products AS p
LEFT JOIN orders AS o
ON p.id = o.pid