I have this assets table:
asset_id | mediumint(8)
asset_type_id | mediumint(8)
asset_name | varchar(200)
and a lookup table:
product_id | mediumint(8)
asset_id | mediumint(8)
sequence | smallint(3)
Basically, the lookup table can have multiple entries of assets for an item. And you can get the item description in assets table.
The assets is categorized as an image, video, sound, etc based on its asset_type_id. This asset_type_id is comprises of integers 1 to 16.
Now, I want to count the number of images(1), video(2) and sound(3) ONLY for a particular item. Is there anyway I can do this using one SELECT QUERY?
I can't limit my selection to just asset_type_id = 1 or 2 or 3 because I still need the other assets data.
SELECT
l.product_id, t.asset_type_id, COUNT(*)
FROM
lookup l
CROSS JOIN
(SELECT 1 AS asset_type_id UNION ALL SELECT 2 UNION ALL SELECT 3) t
LEFT JOIN
assets a ON l.asset_id = a.asset_id AND t.asset_type_id = a.asset_type_id
GROUP BY
l.product_id, t.asset_type_id;
Edit: as separate columns
SELECT
l.product_id,
COUNT(CASE WHEN a.asset_type_id = 1 THEN 1 END) AS asset_count_image,
COUNT(CASE WHEN a.asset_type_id = 2 THEN 1 END) AS asset_count_video,
COUNT(CASE WHEN a.asset_type_id = 3 THEN 1 END) AS asset_count_sound
FROM
lookup l
LEFT JOIN
assets a1 ON l.asset_id = a.asset_id AND a.asset_type_id IN (1, 2, 3)
GROUP BY
l.product_id;
Related
I am having trouble combining data from multiple tables. I have tried joins and subqueries but to no avail. I basically need to combine 2 queries into one. My tables (simplified):
Stock:
id int(9) PrimaryIndex
lot_number int(4)
description text
reserve int(9)
current_bid int(9)
current_bidder int(6)
Members:
member_id int(11) PrimaryIndex
name varchar(255)
Bids:
id int(9)
lot_id int(9)
bidder_id int(5)
max_bid int(9)
time_of_bid datetime
I'm currently using 2 separate queries which with 1000's of lots, makes it very inefficient. 1st query:
SELECT S.id, S.lot_number, S.description, S.reserve FROM stock S ORDER BY
S.lot_number ASC
The 2nd query within a while loop then gets the bidding info:
SELECT DISTINCT B.bidder_id, B.lot_id, B.max_bid, B.time_of_bid,
M.fname, M.lname FROM bids B, members M WHERE B.lot_id=? AND
B.bidder_id=M.member_id ORDER BY B.max_bid DESC LIMIT 2
Below is what i would like as output from a single query, if possible:
Lot No. | Reserve | Current Bid | 1st Max Bid | 1st Bidder | 2nd Max Bid | 2nd Max Bidder
1 | $100 | $120 | $150 | Steve | $110 | John
2 | $500 | $650 | $900 | Tom | $600 | Paul
I have had partial success with just getting the MAX(B.bid) and then its related details (WHERE S.id=B.id), but i cant get the top 2 bids for each lot.
First assign a row number rn to rows within each group of lot_id in table bids (highest bid gets 1, 2nd highest bid gets 2 and so on). The highest bid and second highest bid will be on two different rows after the LEFT JOIN. Use GROUP BY to merge the two rows into one.
select s.lot_number, s.reserve, s.current_bid,
max( case when rn = 1 then b.max_bid end) as first_max_bid,
max( case when rn = 1 then m.name end) as first_bidder,
max( case when rn = 2 then b.max_bid end) as second_max_bid,
max( case when rn = 2 then m.name end ) as second_bidder
from
stock s
left join
(select * from
(select *,
(#rn := if(#lot_id = lot_id, #rn+1,
if( #lot_id := lot_id, 1, 1))) as rn
from bids cross join
(select #rn := 0, #lot_id := -1) param
order by lot_id, max_bid desc
) t
where rn <= 2) b
on s.lot_number = b.lot_id
left join members m
on b.bidder_id = m.member_id
group by s.lot_number, s.reserve, s.current_bid
order by s.lot_number
I have a simple configuration :
2 tables linked in a many-to-many relation, so it gave me 3 tables.
Table author:
idAuthor INT
name VARCHAR
Table publication:
idPublication INT,
title VARCHAR,
date YEAR,
type VARCHAR,
conference VARCHAR,
journal VARCHAR
Table author_has_publication:
Author_idAuthor,
Publication_idPublication
I am trying to get all the authors name that have published at least 2 papers in conference SIGMOD and conference PVLDB.
Right now I achieved this but I still have a double result. My query :
SELECT author.name, publication.journal, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication = publication.idPublication
GROUP BY publication.journal, author.name
HAVING COUNT(*) >= 2
AND (publication.journal = 'PVLDB' OR publication.journal = 'SIGMOD');
returns
+-------+---------+----------+
| name | journal | COUNT(*) |
+-------+---------+----------+
| Renee | PVLDB | 2 |
| Renee | SIGMOD | 2 |
+-------+---------+----------+
As you can see the result is correct but doubled, as I just want 1 time the name.
Other question, how to modify the number parameter for only one conference, for example get all the author that published at least 3 SIGMOD and at least 1 PVLDB ?
If you don't care about the journal , don't select it, it is splitting your results. Also, normal filters need to be placed in the WHERE clause, not the HAVING clause :
SELECT author.name, COUNT(*)
FROM author
INNER JOIN author_has_publication
ON author.idAuthor = author_has_publication.Author_idAuthor
INNER JOIN publication
ON author_has_publication.Publication_idPublication =
publication.idPublication
WHERE publication.journal IN('PVLDB','SIGMOD')
GROUP BY author.name
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 2
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 2;
For the second question, use this HAVING() clause :
HAVING COUNT(CASE WHEN publication.journal = 'SIGMOD' THEN 1 END) >= 3
AND COUNT(CASE WHEN publication.journal = 'PVLDB' THEN 1 END) >= 1;
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 called user_scores as below:
id | af_id | uid | level | record_date
----------------------------------------
1 | 1.1 | 1 | 3 | 2012-01-01
2 | 1.1 | 1 | 4 | 2012-02-01
3 | 1.2 | 1 | 3 | 2012-01-01
4 | 1.2 | 1 | 5 | 2012-03-01
...
I have another table call user_info as below:
uid | forename | surname | gender
-----------------------------------
1 | Homer | Simpson | M
2 | Marge | Simpson | F
3 | Bart | Simpson | M
4 | Lisa | Simpson | F
...
In user scores uid is the user id of a registered user on the system, af_id identifies a particular test a user submits. A user scores a level between 1 - 5 for each test, which can be submitted every month.
My problem is I need to produce an analysis at the end of the year to COUNT the number of users that have achieved each level for a particular test. The analysis is to show a gender split for male and female.
So for example an administrator would select test 1.1 and the system would generate stats based that would COUNT of the total MAX level achieved by each user in the year, with a gender split.
Any help is much appreciated. Thank you in advance.
-
I think I need to clarify myself a bit. Because a user can complete the test multiple times throughout the year, there will be multiple scores for the same test. The query should take the highest level achieved and include this in the count. An example result would be:
Male Results:
level1 | level2 | level3 | level4 | level5
------------------------------------------
2 | 5 | 10 | 8 | 1
I am not certain I get exactly what you mean, but as always I'll have a go. As I understand it you want to know how many people from each gender reached each level in a certain year.
SELECT MaxLevel,
COUNT(CASE WHEN ui.Gender = 'M' THEN 1 END) AS Males,
COUNT(CASE WHEN ui.Gender = 'F' THEN 1 END) AS Females
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY MaxLevel
I've put some sample data on SQL Fiddle so you see if it is what you were after.
EDIT
To transpose the data so levels are along the top and Gender in the rows the following will work:
SELECT Gender,
COUNT(CASE WHEN MaxLevel = 1 THEN 1 END) AS Level1,
COUNT(CASE WHEN MaxLevel = 2 THEN 1 END) AS Level2,
COUNT(CASE WHEN MaxLevel = 3 THEN 1 END) AS Level3,
COUNT(CASE WHEN MaxLevel = 4 THEN 1 END) AS Level4,
COUNT(CASE WHEN MaxLevel = 5 THEN 1 END) AS Level5
FROM User_Info ui
INNER JOIN
( SELECT MAX(Level) AS MaxLevel,
UID
FROM User_Scores us
WHERE af_ID = '1.1'
AND YEAR(Record_Date) = 2012
GROUP BY UID
) AS MaxUs
ON MaxUs.uid = ui.UID
GROUP BY Gender
Note, that if there are ever more than 5 levels you will need to add more to the select statement, or start building dynamic SQL.
Assuming record_date holds only dates (without time parts):
SELECT
s.maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid,
MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
GROUP BY
s.maxlevel
That will show you only the maximum levels found in the user_scores table. If you have a Levels table where all possible levels (1 to 5) are listed, you could use that table to get a complete list of levels. If some levels are not present in the requested subset of data, the corresponding rows will show 0s in both columns.
Here's the above script with minor changes to show the complete chart of levels:
SELECT
l.level AS maxlevel,
COUNT(NULLIF(gender, 'F')) AS M,
COUNT(NULLIF(gender, 'M')) AS F
FROM user_info u
INNER JOIN (
SELECT
uid, MAX(level) AS maxlevel
FROM user_scores
WHERE record_date > DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY)
AND af_id = '1.1'
GROUP BY
uid
) s ON s.uid = u.uid
RIGHT JOIN Levels l ON s.maxlevel = l.level
GROUP BY
l.level
Hope this is what your looking for!
Show number of records group by userid and gender of the max score for af_id '1.1'.
select count(*), info.uid, info.gender, max(score.level)
from user_info as info
join user_scores as score
on info.uid = score.uid
where score.af_id = '1.1'
group by info.uid, info.gender;
EDITED based on your edit.
select sum(if(a.gender="M",1,0)) Male_users, sum(if(a.gender="F",1,0)) Female_users
from myTable a where
a.level = (select max(b.level) from myTable b where a.uid=b.uid)
group by af_id.
I typed this in a rush. But it should work or at least get you where you need to go. E.G. if you need to specify time frame, add that.
You need something like
SELECT
uid,
MAX(level)
WHERE
record_date BETWEEN '2012-01-01' AND '2012-12-31'
AND af_id='1.1'
GROUP BY uid
If you need the gender splits then depending on what stat you need per gender you can either add a JOIN on the user_info table into this query (to get the MAX per gender) to wrap this as a sub-query and JOIN on the whole thing.
Consider following tables in MySQL database:
entries:
creator_id INT
entry TEXT
is_expired BOOL
other:
creator_id INT
entry TEXT
userdata:
creator_id INT
name VARCHAR
etc...
In entries and other, there can be multiple entries by 1 creator. userdata table is read only for me (placed in other database).
I'd like to achieve a following SELECT result:
+------------+---------+---------+-------+
| creator_id | entries | expired | other |
+------------+---------+---------+-------+
| 10951 | 59 | 55 | 39 |
| 70887 | 41 | 34 | 108 |
| 88309 | 38 | 20 | 102 |
| 94732 | 0 | 0 | 86 |
... where entries is equal to SELECT COUNT(entry) FROM entries GROUP BY creator_id,
expired is equal to SELECT COUNT(entry) FROM entries WHERE is_expired = 0 GROUP BY creator_id and
other is equal to SELECT COUNT(entry) FROM other GROUP BY creator_id.
I need this structure because after doing this SELECT, I need to look for user data in the "userdata" table, which I planned to do with INNER JOIN and select desired columns.
I solved this problem with selecting "NULL" into column which does not apply for given SELECT:
SELECT
creator_id,
COUNT(any_entry) as entries,
COUNT(expired_entry) as expired,
COUNT(other_entry) as other
FROM (
SELECT
creator_id,
entry AS any_entry,
NULL AS expired_entry,
NULL AS other_enry
FROM entries
UNION
SELECT
creator_id,
NULL AS any_entry,
entry AS expired_entry,
NULL AS other_enry
FROM entries
WHERE is_expired = 1
UNION
SELECT
creator_id,
NULL AS any_entry,
NULL AS expired_entry,
entry AS other_enry
FROM other
) AS tTemp
GROUP BY creator_id
ORDER BY
entries DESC,
expired DESC,
other DESC
;
I've left out the INNER JOIN and selecting other columns from userdata table on purpose (my question being about combining 3 SELECTs into 1).
Is my idea valid? = Am I trying to use the right "construction" for this?
Are these kind of SELECTs possible without creating an "empty" column? (some kind of JOIN)
Should I do it "outside the DB": make 3 SELECTs, make some order in it (let's say python lists/dicts) and then do the additional SELECTs for userdata?
Solution for a similar question does not return rows where entries and expired are 0.
Thank you for your time.
This should work (assuming all creator_ids appear in the userdata table.
SELECT userdata.creator_id, COALESCE(entries_count_,0) AS entries_count, COALESCE(expired_count_,0) AS expired_count, COALESCE(other_count_,0) AS other_count
FROM userdata
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS entries_count_
FROM entries
GROUP BY creator_id) AS entries_q
ON userdata.creator_id=entries_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS expired_count_
FROM entries
WHERE is_expired=0
GROUP BY creator_id) AS expired_q
ON userdata.creator_id=expired_q.creator_id
LEFT OUTER JOIN
(SELECT creator_id, COUNT(entry) AS other_count_
FROM other
GROUP BY creator_id) AS other_q
ON userdata.creator_id=other_q.creator_id;
Basicly, what you are doing looks correct to me.
I would rewrite it as follows though
SELECT entries.creator_id
, any_entry
, expired_entry
, other_entry
FROM (
SELECT creator_id, COUNT(entry) AS any_entry,
FROM entries
GROUP BY creator_id
) entries
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS expired_entry,
FROM entries
WHERE is_expired = 1
GROUP BY creator_id
) expired ON expired.creator_id = entries.creator_id
LEFT OUTER JOIN (
SELECT creator_id, COUNT(entry) AS other_entry
FROM other
GROUP BY creator_id
) other ON other.creator_id = entries.creator_id
How about
SELECT creator_id,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 0) AS entries,
(SELECT COUNT(*)
FROM entries e
WHERE e.creator_id = main.creator_id AND
e.is_expired = 1) as expired,
(SELECT COUNT(*)
FROM other
WHERE other.creator_id = main.creator_id) AS other,
FROM entries main
GROUP BY main.creator_id;