I have this table named prizes with the following structure
`id` (PRIMARY)
`id_multiple`
`desc`
`winner`
I want to select those who don't have a winner (NULL) and display them together if they have the same id_multiple showing the count of how many left to win of that id_multiple.
So for example, there's this values:
id_multiple | winner | desc
1 | NULL | voucher
1 | jonh | voucher
2 | NULL | car
2 | NULL | car
And I want to display:
Left to win:
1 Voucher
2 Car
(The desc will be the same for all id_multiple so it might be ambiguous to use id_multiple?)
Something like:
SELECT id_multiple,count(id_multiple),`desc`
FROM `yourtable`
WHERE `winner` IS NULL
GROUP BY `id_multiple`
You could count a case expression:
SELECT id_multiple, COUNT(CASE WHEN winner IS NULL THEN 1 END) AS left_to_win, `desc`
FROM mytable
GROUP BY id_multiple, `desc`
Or, even simpler, with a sum expression that takes advantage of the fact that true is interpreted as 1 and false as 0 in numerical contexts:
SELECT id_multiple, SUM(winner IS NULL) AS left_to_win, `desc`
FROM mytable
GROUP BY id_multiple, `desc`
Related
I have the following tables (minified for the sake of simplicity):
CREATE TABLE IF NOT EXISTS `product_bundles` (
bundle_id int AUTO_INCREMENT PRIMARY KEY,
-- More columns here for bundle attributes
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `product_bundle_parts` (
`part_id` int AUTO_INCREMENT PRIMARY KEY,
`bundle_id` int NOT NULL,
`sku` varchar(255) NOT NULL,
-- More columns here for product attributes
KEY `bundle_id` (`bundle_id`),
KEY `sku` (`sku`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `products` (
`product_id` mediumint(8) AUTO_INCREMENT PRIMARY KEY,
`sku` varchar(64) NOT NULL DEFAULT '',
`status` char(1) NOT NULL default 'A',
-- More columns here for product attributes
KEY (`sku`),
) ENGINE=InnoDB;
And I want to show only the 'product bundles' that are currently completely in stock and defined in the database (since these get retrieved from a third party vendor, there is no guarantee the SKU is defined). So I figured I'd need an anti-join to retrieve it accordingly:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
WHERE parts.bundle_id = bundles.bundle_id
AND products.status = 'A'
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Now, I sincerely thought this would filter out the products by status, however, that seems not to be the case. I then changed one thing up a bit, and the query never finished (although I believe it to be correct):
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
AND products.status = 'A'
WHERE parts.bundle_id = bundles.bundle_id
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Example data:
product_bundles
bundle_id | etc.
1 |
2 |
3 |
product_bundle_parts
part_id | bundle_id | sku
1 | 1 | 'sku11'
2 | 1 | 'sku22'
3 | 1 | 'sku33'
4 | 1 | 'sku44'
5 | 2 | 'sku55'
6 | 2 | 'sku66'
7 | 3 | 'sku77'
8 | 3 | 'sku88'
products
product_id | sku | status
101 | 'sku11' | 'A'
102 | 'sku22' | 'A'
103 | 'sku33' | 'A'
104 | 'sku44' | 'A'
105 | 'sku55' | 'D'
106 | 'sku66' | 'A'
107 | 'sku77' | 'A'
108 | 'sku99' | 'A'
Example result: Since the product status of product #105 is 'D' and 'sku88' from part #8 was not found:
bundle_id | etc.
1 |
I am running Server version: 10.3.25-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04
So there are a few questions I have.
Why does the first query not filter out products that do not have the status A.
Why does the second query not finish?
Are there alternative ways of achieving the same thing in a more efficient matter, as this looks rather cumbersome.
First of all, I've read that SQL_CALC_FOUND_ROWS * is much slower than running two separate query (COUNT(*) and then SELECT * or, if you make your query inside another programming language, like PHP, executing the SELECT * and then count the number of rows of the result set)
Second: your first query returns all the boundles that doesn't have ANY active products, while you need the boundles with ALL products active.
I'd change it in the following:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE NOT EXISTS (
SELECT 'x'
FROM product_bundle_parts AS parts
LEFT JOIN products ON (parts.sku = products.sku)
WHERE parts.bundle_id = bundles.bundle_id
AND COALESCE(products.status, 'X') != 'A'
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
I changed the products.status = 'A' in products.status != 'A': in this way the query will return all the boundles that DOESN'T have inactive products (I also removed the condition AND products.product_id IS NULL because it should have been in OR, but with a loss in performance).
You can see my solution in SQLFiddle.
Finally, to know why your second query doesn't end, you should check the structure of your tables and how they are indexed. Executing an Explain on the query could help you to find eventual issues on the structure. Just put the keyword EXPLAIN before the SELECT and you'll have your "report" (EXPLAIN SELECT * ....).
I need your help.
I have a database with a schema like this:
teams:
id
name
fundation_date
matchs:
id
date
id_local_team (foreign key to teams)
id_visit_team (foreign key to teams)
winner ('local', 'visit', 'draw')
players:
id
name
born
position ('arq','def','med','del')
id_team
goals:
id
id_match
id_player
time
and I need to do (among other things) this:
Show by team: Played matchs, winned matchs and drawn matchs (in different columns)
I have something like this:
SELECT t.name,
SUM(CASE t.id WHEN m.id_local_team THEN 1 WHEN m.id_visit_team THEN 1 ELSE 0 END) AS played,
SUM(CASE (CASE m.winner
WHEN 'local' THEN m.id_local_team
WHEN 'visit' THEN m.id_visit_team
ELSE NULL END)
WHEN t.id THEN 1
ELSE 0 END) AS winned,
SUM(CASE m.winner WHEN 'draw' THEN 1 ELSE 0 END) AS drawn
FROM teams AS t
INNER JOIN matchs AS m
ON (t.id = m.id_local_team OR t.id = m.id_visit_team)
GROUP BY t.name;
But that is giving me wrong results. Like, there are 8 matchs total, and the (4) teams are returning 12, 9, or 10 matchs winned (total of 43 matchs), a total of 16 winned matchs and a total of 10 drawn matchs. All above of 8.
What is happening??
In the full query I also have two more inner joins:
INNER JOIN players AS p
ON (p.id_team = t.id)
INNER JOIN goals AS g
ON (p.id = g.id_jugador)
I don't think it has nothing to do with these last ones. I know (think?) that i didn't do the matchs join correctly.
I appreciate if you have made it this far into the post!
The real schema is in spanish actually in Spanish (sorry for that guys) but here is all the magic:
SCHEMA
| equipos | CREATE TABLE `equipos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nombre` varchar(180) NOT NULL,
`f_fundacion` date DEFAULT NULL,
PRIMARY KEY (`id`)
)
| partidos | CREATE TABLE `partidos` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fecha` datetime DEFAULT NULL,
`id_equipo_local` int(11) DEFAULT NULL,
`id_equipo_visitante` int(11) DEFAULT NULL,
`ganador` enum('local','visitante','empate') DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_partidos_equipos_1` (`id_equipo_local`),
KEY `fk_partidos_equipos_2` (`id_equipo_visitante`),
CONSTRAINT `fk_partidos_equipos_1` FOREIGN KEY (`id_equipo_local`) REFERENCES `equipos` (`id`),
CONSTRAINT `fk_partidos_equipos_2` FOREIGN KEY (`id_equipo_visitante`) REFERENCES `equipos` (`id`)
)
QUERY
SELECT e.nombre,
SUM(CASE e.id WHEN p.id_equipo_visitante THEN 1 WHEN p.id_equipo_local THEN 1 ELSE 0 END) AS jugados,
SUM(CASE (CASE ganador
WHEN 'local' THEN p.id_equipo_local
WHEN 'visitante' THEN p.id_equipo_visitante
ELSE NULL END)
WHEN e.id THEN 1
ELSE 0 END) AS ganados,
SUM(CASE ganador WHEN 'empate' THEN 1 ELSE 0 END) AS empatados,
SUM(CASE (CASE ganador
WHEN 'local' THEN p.id_equipo_local
WHEN 'visitante' THEN p.id_equipo_visitante
ELSE NULL END)
WHEN e.id THEN 1
ELSE 0 END) * 3 + SUM(CASE ganador WHEN 'empate' THEN 1 ELSE 0 END) AS puntos,
COUNT(DISTINCT g.id) AS goles_a_favor
FROM equipos AS e
INNER JOIN partidos AS p
ON (e.id = p.id_equipo_visitante OR e.id = p.id_equipo_local)
INNER JOIN jugadores AS j
ON (j.id_equipo = e.id)
INNER JOIN goles AS g
ON (j.id = g.id_jugador)
GROUP BY e.nombre;
RESULTS
+----------------------------------+---------+---------+-----------+--------+---------------+
| nombre | jugados | ganados | empatados | puntos | goles_a_favor |
+----------------------------------+---------+---------+-----------+--------+---------------+
| Club Atlético All Boys | 12 | 6 | 3 | 21 | 3 |
| Club Atlético Chacarita Juniors | 12 | 3 | 0 | 9 | 3 |
| Club Atlético Ferrocarril Oeste | 9 | 3 | 3 | 12 | 3 |
| Club Atlético Tucumán | 10 | 4 | 4 | 16 | 2 |
+----------------------------------+---------+---------+-----------+--------+---------------+
You say that the full query contains joins to each goal made in a given match. This would lead to a situation where the each match is counted N times where N is the number of goals in the match. So for a 0-0 draw the match won't be counted at all, for a 1-0 match the match is counted once for the home team and zero times for the visiting team and 1-2 once for the home team and twice for the visiting team.
To check the number of goals in favor you should first calculate the the goal balance per match using a subquery or a view and then join with that. Then you won't have to problem caused by joining with the player-table.
It does look like the Matchs JOIN is a problem. So you are matching every match at least twice, once for the home team and once for the visiting team, but that doesn't quite explain 43 matches being displayed. Would it be possible to maybe see the full set of results? Sometimes SQL stuff can get touch to debug without access to the tables themselves, but at least seeing the results and what are duplicated might help.
You may want to join only on the winning teams - that should cut half of it out. Actually, since you seem to be trying to get match information, I would SELECT data FROM matches rather than teams. Selecting FROM the table that will limit your total selected rows is always your best bet, then JOIN from there.
I have a table where there are columns students and grade obtained(A-F). A student can appear for test more than once. Sometimes students register but do not appear for test so the grade is not entered but student record entry is made.
I want to get best grade of each student. When I do min(grade) if there is any record with null, null gets selected instead of 'A-F' which indicate proper results. I want to get min of grade if grade exists or null if there are no grades.
SELECT `name`,min(grade) FROM `scores` group by `name`
Id | Name | Grade
1 | 1 | B
2 | 1 |
3 | 1 | A
4 | 2 | C
5 | 2 | D
For name 1 it is fetching second record not the third one having 'A'.
As per the conversations in the comments, the easiest solution may be to convert your empty strings to null, and let the builtin min function do the heavy lifting:
ALTER TABLE scores MODIFY grade VARCHAR(1) NULL;
UPDATE scores
SET grade = null
WHERE grade = '';
SELECT name, MIN(grade)
FROM scores
GROUP BY name
If this is not possible, a dirty trick you could use is to have a case expression convert the empty string to a something you know will come after F:
SELECT name,
MIN(CASE grade WHEN '' THEN 'DID NOT PARTICIPATE' ELSE grade END)
FROM scores
GROUP BY name
And if you really need the empty string back, you can have another case expression around the min:
SELECT name, CASE best_grade WHEN 'HHH' THEN '' ELSE best_grade END
FROM (SELECT name,
MIN(CASE grade WHEN '' THEN 'HHH' ELSE grade END) AS
best_grade
FROM scores
GROUP BY name) t
Change your query slightly to -
SELECT `name`,min(grade) FROM `scores` WHERE grade <> "" group by `name`
If the name has a grade/s assigned to it then the lowest will be returned else the resultset will be null
I have an SQL table that contains the names of people and respective country codes.
----------------
name | code
----------------
saket | IN
rohan | US
samules | AR
Geeth | CH
Vikash | IN
Rahul | IN
Ganesh | US
Zorro | US
What I wanted was that, I should able to get rows group by country code having names starting with sa first, if not then Vi even if not then last row of the group.
When I tried this
SELECT * FROM MyTable GROUP BY code HAVING name like 'sa%' or name like 'vi%';
But its give me rows who matched with the above condition in having clause.
I want that if condition fails then give me the last row of that group, Is it possible?.
If possible, then how?
Maybe not very efficient, but try:
SELECT FIRST(`name`) AS `name`, `code` FROM (
SELECT `name`, `code` FROM `MyTable`
WHERE `name` LIKE 'sa%'
UNION ALL
SELECT `name`, `code` FROM `MyTable`
WHERE `name` LIKE 'vi%'
UNION ALL
SELECT LAST(`name`) AS `name`, `code` FROM `MyTable` GROUP BY `code`
HAVING `name` NOT LIKE 'sa%' AND `name` NOT LIKE `vi%'
) AS `a` GROUP BY `code`
You can try this query. It returns what you need, but be aware - this query has two pitfalls:
Subquery is a pain on 10^6 rows
Field name in outer query is nonaggregated. MySQL documentation says that is is impossible to say what value will be selected for nonaggregated.
http://dev.mysql.com/doc/refman/5.7/en/group-by-extensions.html
select name, country
from
(
select *, if(name like 'sa%', 0, if(name like 'vi%', 2, 3) ) as name_order
from tmp_names
order by country, name_order, name desc
) as tmp_names
group by country
order by name;
It returns
+---------+---------+
| name | country |
+---------+---------+
| Geeth | CH |
| saket | IN |
| samules | AR |
| Zorro | US |
+---------+---------+
I have a table with name-value pairs and additional attribute. The same name can have more than one value. If that happens I want to return the row which has a higher attribute value.
Table:
ID | name | value | attribute
1 | set1 | 1 | 0
2 | set2 | 2 | 0
3 | set3 | 3 | 0
4 | set1 | 4 | 1
Desired results of query:
name | value
set2 | 2
set3 | 3
set1 | 4
What is the best performing sql query to get the desired results?
the best performing query would be as follows:
select
s.set_id,
s.name as set_name,
a.attrib_id,
a.name as attrib_name,
sav.value
from
sets s
inner join set_attribute_values sav on
sav.set_id = s.set_id and sav.attrib_id = s.max_attrib_id
inner join attributes a on sav.attrib_id = a.attrib_id
order by
s.set_id;
+--------+----------+-----------+-------------+-------+
| set_id | set_name | attrib_id | attrib_name | value |
+--------+----------+-----------+-------------+-------+
| 1 | set1 | 3 | attrib3 | 20 |
| 2 | set2 | 0 | attrib0 | 10 |
| 3 | set3 | 0 | attrib0 | 10 |
| 4 | set4 | 4 | attrib4 | 10 |
| 5 | set5 | 2 | attrib2 | 10 |
+--------+----------+-----------+-------------+-------+
obviously for this to work you're gonna also have to normalise your design and implement a simple trigger:
drop table if exists attributes;
create table attributes
(
attrib_id smallint unsigned not null primary key,
name varchar(255) unique not null
)
engine=innodb;
drop table if exists sets;
create table sets
(
set_id smallint unsigned not null auto_increment primary key,
name varchar(255) unique not null,
max_attrib_id smallint unsigned not null default 0,
key (max_attrib_id)
)
engine=innodb;
drop table if exists set_attribute_values;
create table set_attribute_values
(
set_id smallint unsigned not null,
attrib_id smallint unsigned not null,
value int unsigned not null default 0,
primary key (set_id, attrib_id)
)
engine=innodb;
delimiter #
create trigger set_attribute_values_before_ins_trig
before insert on set_attribute_values
for each row
begin
update sets set max_attrib_id = new.attrib_id
where set_id = new.set_id and max_attrib_id < new.attrib_id;
end#
delimiter ;
insert into attributes values (0,'attrib0'),(1,'attrib1'),(2,'attrib2'),(3,'attrib3'),(4,'attrib4');
insert into sets (name) values ('set1'),('set2'),('set3'),('set4'),('set5');
insert into set_attribute_values values
(1,0,10),(1,3,20),(1,1,30),
(2,0,10),
(3,0,10),
(4,4,10),(4,2,20),
(5,2,10);
This solution will probably perform the best:
Select ...
From Table As T
Left Join Table As T2
On T2.name = T.name
And T2.attribute > T1.attribute
Where T2.ID Is Null
Another solution which may not perform as well (you would need to evaluate against your data):
Select ...
From Table As T
Where Not Exists (
Select 1
From Table As T2
Where T2.name = T.name
And T2.attribute > T.attribute
)
select name,max(value)
from table
group by name
SELECT name, value
FROM (SELECT name, value, attribute
FROM table_name
ORDER BY attribute DESC) AS t
GROUP BY name;
There is no easy way to do this.
A similar question was asked here.
Edit: Here's a suggestion:
SELECT `name`,`value` FROM `mytable` ORDER BY `name`,`attribute` DESC
This isn't quite what you asked for, but it'll at least give you the higher attribute values first, and you can ignore the rest.
Edit again: Another suggestion:
If you know that value is a positive integer, you can do this. It's yucky, but it'll work.
SELECT `name`,CAST (GROUP_CONCAT(`value` ORDER by `attribute` DESC) as UNSIGNED) FROM `mytable` GROUP BY `name`
To include negative integers you could change UNSIGNED to SIGNED.
Might want to benchmark all these options, here's another one.
SELECT t1.name, t1.value
FROM temp t1
WHERE t1.attribute IN (
SELECT MAX(t2.attribute)
FROM temp t2
WHERE t2.name = t1.name);
How about:
SELECT ID, name, value, attribute
FROM table A
WHERE A.attribute = (SELECT MAX(B.attribute) FROM table B WHERE B.NAME = A.NAME);
Edit: Seems like someones said the same already.
Did not benchmark them, but here is how it is doable:
TableName = temm
1) Row with maximum value of attribute :
select t.name, t.value
from (
select name, max(attribute) as maxattr
from temm group by name
) as x inner join temm as t on t.name = x.name and t.attribute = x.maxattr;
2) Top N rows with maximum attribute value :
select name, value
from temm
where (
select count(*) from temm as n
where n.name = temm.name and n.attribute > temm.attribute
) < 1 ; /* 1 can be changed to 2,3,4 ..N to get N rows */