MySQL - Sort and limit by table string - php

I have two mysql tables. One table with strings, for example abc, def, ghi, jkl. The other table contains info about the strings in the other table, for example 'Three first letters in the alphabet', '3-6 letters in the alphabet', and so on. There can be multiple (around 25 rows) for each string in this table.
I want to return 3 rows from the second table for each string in the first one, for example:
table1.string | table2.info
---------------------------
abc | blahblah
abc | blahblah2
def | blahblah
abc | blahblah3
def | blahblah2
def | blahblah3
I can get the strings from table1 first, the do a foreach and execute even more queries to get limit 3 from the table2. But that does not seem to be good for the performance.
How would a query like this look like?

I don't know whether it works in MySQL, but in MSSQL this would be
SELECT
table1.string,
table2.info
FROM table1 INNER JOIN table2 ON table2.stringID = table1.ID
WHERE ROW_NUMBER() OVER (PARTITION BY table1.string ORDER BY table2.info) <= 3

You can try this (it's not very fast though):
SELECT string,info FROM
(
select t1.string, t2.info,
#n_rec := IF(#tmp!=t1.string,1,#n_rec+1) as nrec,
#tmp := t1.string
FROM
table_1 t1
INNER JOIN table_2 t2 ON t2.table_1_id = t1.id
order by t1.string
)yy
INNER JOIN (SELECT #n_rec := 0) X
INNER JOIN (SELECT #tmp:= null)Y
WHERE nrec <=3;
You might want to use LEFT JOIN table_2 instead of INNER JOIN table_2 if you need to include records from table_1 with no info in table_2.

Related

Mysql php: How to join same table multiple times?

Team table has:
ID | TEAM
--------+----------
1 | A
2 | B
Result table has:
fk_ID1 | fk_ID2 | RESULT
----------+-----------+-----------
1 | 2 | 5:0
2 | 1 | 2:3
How to Inner JOIN table, to get: (A 5:0 B) & (A 2:3 B)?
My code example:
public function getResultList($limit, $offset) {
$query = " SELECT result_id,
t1.name name1,
t2.name name2,
team1_goals,
team2_goals,
date
FROM results
INNER JOIN team t1 ON fk_tm1_id=tm_id
INNER JOIN team t2 ON fk_tm2_id=tm_id";
$data = mysql::select($query);
return $data;
}
It's best to answer this as purely an SQL question, which it is. You need to assign a table alias when joining the same table two or more times.
You seem to only be assigning aliases to the column. To assign an alias to a column or table, you can add the alias directly after the column or table name (AS can also be used but isn't necessary for MySQL)
A common thing is to number the tables as t1, t2, t3, etc.
SELECT t1.name name1, t2.name name2 FROM ...
INNER JOIN team_table t1 ON ...
INNER JOIN team_table t2 ON ...
This aliases the first join as t1 and the second join as t2, which you would use when accessing data from that specific join (SELECT t1.name).

MySQL join query but dont show results from the first table if it's a duplicate

So basically I have 2 tables:
table1
id| name
1 Test
2 Something
3 More
table2
id| table1_id
1 1
2 1
3 1
4 2
5 2
6 3
Now I need the result to be like this:
name | table2.id
Test 1
2
3
Something 4
5
More 6
So basically no duplicate entries from the first table. So the exact same results as joining it but without showing the name more than once. Is this possible in MySQL or should I just filter it in PHP? I know it's possible in PHP but I am wondering if something like this is achievable in MySQL if so, I'd like to know what to look for. I was thinking something with DISTINCT and/or a left or right join.
So, you asked if it is possible with MySQL and I answered in comments that it is.
If your question was how can I accomplish this with only MySQL, here it is:
SELECT
tmp.name,
tbl2.id
FROM
tbl2
LEFT JOIN (
SELECT
tbl2.id AS id,
tbl1.`name` AS name
FROM
tbl2
INNER JOIN tbl1 ON tbl1.id = tbl2.tbl1_id
GROUP BY
tbl2.tbl1_id
) AS tmp ON tbl2.id = tmp.id;
Hope it is what you wanted.
As #roberto06 suggested, this query returns NULL instead of duplicates, but if you don't like NULLs and want an empty string you can change SELECT tmp.name to SELECT IFNULL(tmp.name,'')
What about this?
SELECT
*,
(SELECT GROUP_CONCAT(CONCAT(`id`) separator '|') AS table2_ids
FROM table2 WHERE table1_id = table1.id) AS m
FROM table1
In PHP, you just need to explode('|', $mvar)
Found it !
SELECT (
SELECT
IF(COUNT(t2_2.table1_id) = 1, t1.name, '')
FROM table2 t2_2
WHERE t2_2.id <= t2.id
AND t2_2.table1_id = t1.id
) AS name,
t2.id
FROM table1 t1
LEFT JOIN table2 t2 ON t2.table1_id = t1.id
Results:
name | .id
----------+-----
Test | 1
| 2
| 3
Something | 4
| 5
More | 6
Explanation :
This will count the number of occurences of table1_id IN t2_2 where t2_2.id is lower or equal than the actual t2.id and where t2_2.table1_id is equal to t1.id
So this will return 1, 2, 3, 1, 2, 1 in your table example
Then it'll output t1.name if it is evaluated to 1 and an empty string if not.
I don't know if it's clear enough, and I'm pretty sure the performance could be enhanced but hey, it works.
This is a question of presenting the query result, so I would opt for PHP, which is better at handling this than MySQL.
Something like this:
select distinct t1.name, t2.id
from table1 as t1 outer right join table2 as t2 on t1.id = t2.table1_id
You can try like...
select A.name,B.id from table1 A
right join table2 B on A.id=B.table1_id

Order one table based on column of another using MySQL

I am trying to implement a join so then I can order results of one table based on the column of another table. My SQL works perfectly when the records exists in both tables. The SQL also works when there are more records in table1 than there are in table2, providing I do not use the ORDER BY clause.
SQL:
SELECT * FROM table1
JOIN table2 b ON table1.col1 = b.col1
WHERE col3 != 0 ORDER BY b.col2 ASC;
Table 1
col1 | col2 | col3
__________________
1 foo 1
2 foo 1
5 foo 1
9 foo 0
10 foo 1
17 foo 0
14 foo 1
12 foo 1
Table 2
col1 | col2
___________
1 a
2 b
17 e
14 g
12 l
The part of the query ORDER BY b.col2 ASC is causing it to fail when the records between the two tables are not matching.
I cannot guarantee that a record will be present in both. Is there a way of still implementing this?
I am currently using mysqli but can use pdo if needed.
Like #Maximus2012 mentioned, try a LEFT JOIN. This will give you all of the records from table1 and any records from table2 which match col1 from table1.
SELECT * FROM table1
LEFT JOIN table2 b ON table1.col1 = b.col1
WHERE col3 != 0 ORDER BY b.col2 ASC
If you are looking for all records from table2 and any which match from table 1, use a RIGHT JOIN instead.
SELECT * FROM table1
RIGHT JOIN table2 b ON table1.col1 = b.col1
WHERE col3 != 0 ORDER BY b.col2 ASC
Making use of a LEFT JOIN to get this query:
SELECT table1.*,
table2.col2,
CASE WHEN table2.col2 IS NOT NULL THEN '0'
ELSE '1' END AS derived_column FROM table1
LEFT JOIN table2 ON table1.col1 = table2.col1
WHERE table1.col3 <> 0
ORDER BY derived_column, table2.col2
See if the above query works for your case. It is a bit complicated since the ORDER BY clause in the original query will put the non-matching columns before the matching ones in the result. To bypass this, I have conditionally created a derived column to return a value of 0 (for matching) and 1 (for non-matching). Then you order by this derived column first (rows with value 0 will come before those with value 1) and then order by table2.col2.

How to retrieve row number from specific value in a mysql fetch row/array/assoc result via php?

Supposedly I have this sql result in PHP ($myresults = mysql_fetch_… either assoc/row/array):
SELECT table1.name as name, sum(table2.numbers) as numbers FROM table2 INNER JOIN
table1 ON table2.fk_id = table1.id GROUP BY name ORDER BY numbers DESC
---------------
| John | 800 |
---------------
| Mark | 500 |
---------------
| Bill | 300 |
---------------
So I am logged as Mark ($_SESSION['name'] == "Mark") and I want to know in which row # the value 'Mark' is located (in this case, row number 1, considering the first row is 0).
How to I get that via PHP?
Thanks…
EDIT: think of it as a High Score or Leaderboards table, I don't need the user id, but the row in which the user is located as of right now…
You should use user defined variables this way:
SELECT table1.name as name, sum(table2.numbers) as numbers,
#rank := #rank + 1 rank
FROM table2
CROSS JOIN (SELECT #rank := 0) init
JOIN table1 ON table2.fk_id = table1.id
GROUP BY name
ORDER BY numbers DESC
After a second thought, the group by might give you some trouble with the counting of the UDVs. This is another alternative but will be less performant than the previous approach.
SELECT *, #rank := #rank + 1 rank FROM (
SELECT table1.name as name, sum(table2.numbers) as numbers
FROM table2
JOIN table1 ON table2.fk_id = table1.id
GROUP BY name
) s
CROSS JOIN (SELECT #rank := 0) init
ORDER BY numbers DESC
Anyway, I would recommend counting directly in PHP. That will be more flexible and performant.
Modify your SQL to select primary ID along with the other data:
SELECT
table1.id as id,
table1.name as name,
sum(table2.numbers) as numbers
FROM
table2
INNER JOIN
table1 ON table2.fk_id = table1.id
GROUP BY
name
ORDER BY
numbers DESC

MySQL require multiple values from multiple rows of a join

I have the following table structures.
Table A
id name
1 name1
2 name2
Table B
a_id b_id
1 1
1 2
How can I select all rows of table A that have both a b_id of 1 and 2? Table B is a mapping table between table A and another table, whose contents do not matter for this question.
Thank you for your time and help!
This query uses COUNT(DISTINCT) to ensure the presence of both values. If I did not use DISTINCT it may incorrectly count rows in TableB that look like this as a match when it shouldn't:
a_id b_id
1 1
1 1
select a.id, a.name
from TableA a
inner join (
select a_id
from TableB
where b_id in (1, 2)
group by a_id
having count(distinct b_id) = 2 #this number matches no. of unique values in IN clause
) b on a.id = b.a_id
SQL Fiddle example
Correctness can be tricky on a question like this because your sample data is missing a key cases. Duplicate values for B_ID and the possibility that it can contain one of the ids but not both
e.g.
| A_ID | B_ID |
---------------
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 1 |
The best approach is to use Having (Distinct Count) = # of ids (RedFilter's) since its easy to add more ID's
The two other options are to use or multiple EXISTS or IN clauses (NickB's) or to join and filter multiple times (below) but can become tortuously long if you need to add additional ids.
SELECT DISTINCT a.id,
a.name
FROM TableA a
INNER JOIN TableB b1
ON a.id = b1.a_id
and b1.b_id = 1
INNER JOIN TableB b2
ON a.id = b2.a_id
and b2.b_id = 2
DEMO
SELECT * FROM A JOIN B ON A.id=B.a_id WHERE B.b_id IN(1,2);
Here's what I could come up with, it uses one subquery.
SELECT * FROM table_a a1
JOIN table_b b1
ON a1.id = b1.a_id
WHERE b1.b_id = 1 AND
EXISTS(
SELECT b2.b_id
FROM table_b b2
WHERE a1.id = b2.a_id
AND b2.b_id = 2
)
Didn't know SQL Fiddle exists, but here is one showing it working!
I'll take a stab at this too, with a self join:
SELECT A.* FROM B B1
JOIN B B2 ON B2.a_id = B1.a_id
JOIN A ON A.id = B1.a_id
WHERE B1.b_id = 1 AND B2.b_id = 2
I tested this, and it works. If (B.a_id, B.b_id) isn't unique, then you'll need DISTINCT to avoid duplicates.
SELECT TableA.* FROM TableA WHERE TableA.id IN(
SELECT TableB.a_id FROM TableB WHERE TableB.b_id IN(1,2))

Categories