I have two tables that I need to join. These are:
art
id | art
--------
1 | A
2 | B
3 | C
4 | D
5 | E
6 | F
7 | G
8 | H
9 | I
and
Sess
artid | sessid
--------------
1 | 1
2 | 1
3 | 1
4 | 1
1 | 2
4 | 2
5 | 2
6 | 2
1 | 3
2 | 3
7 | 3
4 | 3
where Sess.artid is a foregin key to art.id.
From the tables above we can see that there are 3 sessions: A,B,C,D, A,D,E,F and A,B,G,D.
I want to get a ranking of the arts that occur along with art A. Something like:
D=3
B=2
How could I form such a query in mysql or postgres?
You need to join twice the session table to get the article sharing the same session.
Then join one time with article for the filter clause, and another time to get the name of the other article in the other session.
SELECT aSameSession.art, count(*)
FROM art a
INNER JOIN Sess s
ON a.id = s.artid
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
INNER JOIN art aSameSession
ON sSameArticle.artid = aSameSession.id
WHERE A.art = 'A'
AND aSameSession.art <> 'A'
GROUP BY aSameSession.art
Output :
B 2
C 1
D 3
E 1
F 1
G 1
This version could be a little difficult to understand, so here a version just with the ID of the article, which is much more simple :
SELECT sSameArticle.artid, count(*)
FROM Sess s
INNER JOIN Sess sSameArticle
ON sSameArticle.sessid = s.sessid
WHERE s.artid = 1
AND sSameArticle.artid != 1
GROUP BY sSameArticle.artid
Output :
2 2
3 1
4 3
5 1
6 1
7 1
Adding the name of the article is just cosmetic.
Something like this, perhaps:
select art,count(*)
from sessid
left join art on art.id=artid
where sessid in (select sessid from sess where artid=1)
group by artid;
?
Example of table structure and join queries on PostgreSQL
CREATE TABLE arts (
arts_id serial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE sessions (
sessions_id integer NOT NULL,
arts_id integer NOT NULL REFERENCES arts
);
SELECT arts.name, count(sessions_id)
FROM arts
JOIN sessions USING (arts_id)
GROUP BY arts.name
ORDER BY count(sessions_id) DESC;
SELECT a.art, count(*) as ranking
FROM art a, sess s
WHERE a.id = s.artid
group by a.art
order by count(*) DESC;
For a statement in ANSI-92 syntax have a look at Konerak's answer.
Related
I have 3 tables:
Question (id, questionText)
QuestionCategory (id, categoryName)
Question_QuestionCategory (questionId, categoryId)
Sample Data:
Table Question:
id | questionText
1 | 2 + 2 = ?
2 | 10 x 5 / 3 + 5 = ?
3 | USA is located in which continent?
Table QuestionCategory:
id | categoryName
1 | Easy
2 | Hard
3 | Math
4 | Geography
Table Question_QuestionCategory:
questionId | categoryId
1 | 1
1 | 3
2 | 2
2 | 3
3 | 1
3 | 4
The Question_QuestionCategory table is a relation table that stores the foreign keys from the question and questionCategory tables.
My problem is: I need a select that returns to me a question that has the Hard and Math categories at the same time (the question with id 2 in this case). How can I do that?
You can do that by using aggregation an checking if the distinct count of categories is equal to the number of categories you asked for. To only get one row as a result you can use LIMIT.
SELECT q.id,
q.text
FROM question q
INNER JOIN question_questioncategory qc
ON qc.question = q.id
INNER JOIN questioncategory c
ON c.id = qc.categoryid
WHERE c.categoryname IN ('Hard',
'Math')
GROUP BY q.id,
q.text
HAVING count(DISTINCT c.categoryname) = 2
LIMIT 1;
Let's say i've got this database:
book
| idBook | name |
|--------|----------|
| 1 |Book#1 |
category
| idCateg| category |
|--------|----------|
| 1 |Adventures|
| 2 |Science F.|
book_categ
| id | idBook | idCateg | DATA |
|--------|--------|----------|--------|
| 1 | 1 | 1 | (null) |
| 2 | 1 | 2 | (null) |
I'm trying to select only the books which are in category 1 AND category 2
This is what I've got so far:
SELECT book.* FROM book,book_categ
WHERE book_categ.idCateg = 1 AND book_categ.idCateg = 2
Obviously, this giving 0 results becouse each row has only one idCateg it does work width OR but the results are not what I need. I've also tried to use a join, but I just can't get the results I expect.
Here it's the SQLFiddle of my current project, the data at the begining is just a sample.
SQLFiddle
Any help will be really appreciated.
You could double join with a constraint on the category id:
SELECT a.* FROM book AS a
INNER JOIN book_categ AS b ON a.idBook = b.idBook AND b.idCateg = 1
INNER JOIN book_categ AS c ON a.idBook = c.idBook AND c.idCateg = 2
You could use a subquery:
SELECT a.* FROM book AS a
WHERE
(SELECT COUNT(DISTINCT idCateg) FROM book_categ AS b
WHERE b.idBook = a.idBook AND b.idCateg IN (1,2)) = 2
If you are on MySQL as your fiddle implies, you should prefer the join variant, since most joins are much faster in MySQL than subqueries.
edit
This one should also work:
SELECT a.* FROM book a
INNER JOIN book_categ AS b ON a.idBook = b.idCateg
WHERE b.idCateg IN (5, 6)
GROUP BY idBook
HAVING COUNT(DISTINCT b.idCateg) = 2
and should be faster than the two above, although you have to change the last number according to the number of category ids you are requesting.
I have a weak relation table, called header, it is basically just three ID's: id is an autoincrement primary key, did points to the id of table D and hid points to the id of table H. D and H are irrelevant here.
I want to find for any value of hid, the other values of hid that shares did with the original hid. An example:
id | did | hid
===============
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 4
6 | 2 | 5
7 | 3 | 2
8 | 3 | 6
For hid = 1 I would thus like to find id = {2,3,5,6} as those are the rows that have did in common with hid = 1.
I can do this by creating some arrays in PHP and running through all possible values of hid and respective did, but this is a quite slow process for large tables. I was wondering if there is a clever kind of JOIN or similar statement that could be used to find the cooccuring values of hid.
If I have understood you correctly:-
SELECT a.hid, GROUP_CONCAT(b.id)
FROM header a
INNER JOIN header b
ON a.did = b.did
AND b.hid != 1
WHERE a.hid = 1
GROUP BY a.hid
SQL fiddle:-
http://www.sqlfiddle.com/#!2/9aa26/1
Maybe this:
SELECT d.id
FROM (
SELECT *
FROM header
WHERE header.hid =1
) AS h
JOIN header AS d ON d.did = h.did
WHERE d.hid !=1
I have 3 tables players positions players_national
I need to SELECT DISTINCT positions for players_national where player_positon is associated with players table.
My Tables are like this:
Players Table
-----------------------------------------------------------------------
| player_id player_name player_team player_position |
-----------------------------------------------------------------------
1 KAKA 12 1
2 Ronaldo 7 2
3 Adriano 10 2
Positions Table
-------------------------------------------------------
| position_id position_name |
-------------------------------------------------------
1 Midfield
2 Forward
Players_national Table
-------------------------------------------------------------------
| player_id player_team player_national_team |
-------------------------------------------------------------------
1 12 4
2 7 4
3 19 4
My Dream output is this
---------------------------------------------------
| player_national_team position |
---------------------------------------------------
4 1
4 2
My Query is like this:
SELECT DISTINCT players. * , positions. * , players_national. *
FROM players
LEFT JOIN positions ON positions.position_id = players.player_position
LEFT JOIN players_national ON players_national.player_id = players.player_id
WHERE players_national.player_id = players.player_id AND players_national.player_national_team = 4
If you select everything from every table then every row will be DISTINCT; you need to limit your SELECT clause to just the fields you want to be DISTINCT
SELECT DISTINCT positions.position_id, players_national.player_national_team
FROM players
LEFT JOIN positions ON positions.position_id = players.player_position
LEFT JOIN players_national ON players_national.player_id = players.player_id
WHERE players_national.player_id = players.player_id AND players_national.player_national_team = 4
Distinct player_position on table players, where is join to table players_national
SELECT
DISTINCT(players.player_position),
players_national.player_national_team
FROM players INNER JOIN players_national;
result:
+-----------------+----------------------+
| player_position | player_national_team |
+-----------------+----------------------+
| 1 | 4 |
| 2 | 4 |
+-----------------+----------------------+
2 rows in set
Given the following (simplified) tables:
People p
id name registered
-----------------------------------
1 Geoff 2011-03-29 12:09:08
2 Phil 2011-04-29 09:03:54
3 Tony 2011-05-29 21:22:23
4 Gary 2011-06-21 22:56:08
...
Items i
date p1id p2id
----------------------------------------
2011-06-29 20:09:44 1 2
2011-06-26 10:45:00 1 3
2011-06-23 12:22:43 2 3
2011-06-22 13:07:12 2 4
...
I'd like:
The earliest single i.date that each p.id appears in either column p1id or p2id; or p.registered if they feature in neither.
So far, I've tried:
CREATE TEMPORARY TABLE temp (id INT);
INSERT INTO temp (id)
SELECT DISTINCT u FROM (
SELECT p1id AS u FROM Items UNION ALL
SELECT p2id AS u FROM Items
)tt;
SELECT registered,id FROM People
WHERE id NOT IN (SELECT id FROM temp);
Which gets me as far as the second part, albeit in a fairly clumsy way; and I'm stuck on the first part beyond some sort of external, scripted iteration through all the values of p.id (ugh).
Can anyone help?
I'm on MySQL 5.1 and there's ~20k people and ~100k items.
One more solution:
SELECT id, name, IF(min_date1 IS NULL AND min_date2 IS NULL, registered, LEAST(COALESCE(min_date1, min_date2), COALESCE(min_date2, min_date1))) date FROM (
SELECT p.id, p.name, p.registered, MIN(i1.date) min_date1, MIN(i2.date) min_date2 FROM people p
LEFT JOIN items i1
ON p.id = i1.p1id
LEFT JOIN items i2
ON p.id = i2.p2id
GROUP BY id
) t;
OR this:
SELECT p.id, p.name, COALESCE(MIN(i.date), p.registered) FROM people p
LEFT JOIN (
SELECT p1id id, date FROM items
UNION ALL
SELECT p2id id, date FROM items
) i
ON p.id = i.id
GROUP BY id;
Result:
+------+-------+---------------------+
| id | name | date |
+------+-------+---------------------+
| 1 | Geoff | 2011-06-26 10:45:00 |
| 2 | Phil | 2011-06-22 13:07:12 |
| 3 | Tony | 2011-06-23 12:22:43 |
| 4 | Gary | 2011-06-22 13:07:12 |
+------+-------+---------------------+
This is tested in Postgres, but I think it ought to work in MySQL with few or no changes:
SELECT p.id,COALESCE(MIN(x.date),p.registered) AS date
FROM p
JOIN (
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p1id)
GROUP BY p.id
UNION
SELECT p.id,MIN(i.date) AS date
FROM p
JOIN i ON (p.id=i.p2id)
GROUP BY p.id
) AS x ON x.id = p.id
GROUP BY p.id,p.registered;
Output (given your sample data):
id | date
----+---------------------
3 | 2011-06-23 12:22:43
1 | 2011-06-26 10:45:00
2 | 2011-06-22 13:07:12
4 | 2011-06-22 13:07:12
(4 rows)