PHP - Get only single image from many inside while loop [duplicate] - php

I read many threads about getting only the first row of a left join, but, for some reason, this does not work for me.
Here is my structure (simplified of course)
Feeds
id | title | content
----------------------
1 | Feed 1 | ...
Artists
artist_id | artist_name
-----------------------
1 | Artist 1
2 | Artist 2
feeds_artists
rel_id | artist_id | feed_id
----------------------------
1 | 1 | 1
2 | 2 | 1
...
Now i want to get the articles and join only the first Artist and I thought of something like this:
SELECT *
FROM feeds
LEFT JOIN feeds_artists ON wp_feeds.id = (
SELECT feeds_artists.feed_id FROM feeds_artists
WHERE feeds_artists.feed_id = feeds.id
LIMIT 1
)
WHERE feeds.id = '13815'
just to get only the first row of the feeds_artists, but already this does not work.
I can not use TOP because of my database and I can't group the results by feeds_artists.artist_id as i need to sort them by date (I got results by grouping them this way, but the results where not the newest)
Tried something with OUTER APPLY as well - no success as well.
To be honest i can not really imagine whats going on in those rows - probably the biggest reason why i cant get this to work.
SOLUTION:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'

If you can assume that artist IDs increment over time, then the MIN(artist_id) will be the earliest.
So try something like this (untested...)
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT
MIN(fa.artist_id) a_id
FROM feeds_artists fa
WHERE fa.feed_id = f.feed_id
) a

Version without subselect:
SELECT f.title,
f.content,
MIN(a.artist_name) artist_name
FROM feeds f
LEFT JOIN feeds_artists fa ON fa.feed_id = f.id
LEFT JOIN artists a ON fa.artist_id = a.artist_id
GROUP BY f.id

#Matt Dodges answer put me on the right track. Thanks again for all the answers, which helped a lot of guys in the mean time. Got it working like this:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'

based on several answers here, i found something that worked for me and i wanted to generalize and explain what's going on.
convert:
LEFT JOIN table2 t2 ON (t2.thing = t1.thing)
to:
LEFT JOIN table2 t2 ON (t2.p_key = (SELECT MIN(t2_.p_key)
FROM table2 t2_ WHERE (t2_.thing = t1.thing) LIMIT 1))
the condition that connects t1 and t2 is moved from the ON and into the inner query WHERE. the MIN(primary key) or LIMIT 1 makes sure that only 1 row is returned by the inner query.
after selecting one specific row we need to tell the ON which row it is. that's why the ON is comparing the primary key of the joined tabled.
you can play with the inner query (i.e. order+limit) but it must return one primary key of the desired row that will tell the ON the exact row to join.
Update - for MySQL 5.7+
another option relevant to MySQL 5.7+ is to use ANY_VALUE+GROUP BY. it will select an artist name that is not necessarily the first one.
SELECT feeds.*,ANY_VALUE(feeds_artists.name) artist_name
FROM feeds
LEFT JOIN feeds_artists ON feeds.id = feeds_artists.feed_id
GROUP BY feeds.id
more info about ANY_VALUE: https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

I've used something else (I think better...) and want to share it:
I created a VIEW that has a "group" clause
CREATE VIEW vCountries AS SELECT * PROVINCES GROUP BY country_code
SELECT * FROM client INNER JOIN vCountries on client_province = province_id
I want to say yet, that I think that we need to do this solution BECAUSE WE DID SOMETHING WRONG IN THE ANALYSIS... at least in my case... but sometimes it's cheaper to do this that to redesign everything...
I hope it helps!

Here is my answer using the group by clause.
SELECT *
FROM feeds f
LEFT JOIN
(
SELECT artist_id, feed_id
FROM feeds_artists
GROUP BY artist_id, feed_id
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id

I want to give a more generalized answer. One that will handle any case when you want to select only the first item in a LEFT JOIN.
You can use a subquery that GROUP_CONCATS what you want (sorted, too!), then just split the GROUP_CONCAT'd result and take only its first item, like so...
LEFT JOIN Person ON Person.id = (
SELECT SUBSTRING_INDEX(
GROUP_CONCAT(FirstName ORDER BY FirstName DESC SEPARATOR "_" ), '_', 1)
) FROM Person
);
Since we have DESC as our ORDER BY option, this will return a Person id for someone like "Zack". If we wanted someone with the name like "Andy", we would change ORDER BY FirstName DESC to ORDER BY FirstName ASC.
This is nimble, as this places the power of ordering totally within your hands. But, after much testing, it will not scale well in a situation with lots of users and lots of data.
It is, however, useful in running data-intensive reports for admin.

For some database like DB2 and PostgreSQL, you have to use the key word LATERAL for specifying a sub query in the LEFT JOIN : (here, it's for DB2)
SELECT f.*, a.*
FROM feeds f
LEFT JOIN LATERAL
(
SELECT artist_id, feed_id
FROM feeds_artists sfa
WHERE sfa.feed_id = f.id
fetch first 1 rows only
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id

I know this is not a direct solution but as I've faced this and it's always a huge problem for me, and also using left join select etc. sometimes lead to a heavy process cost in database and server, I prefer doing this kind of left joins using array in php like this:
First get the data in range from second table and while you need just one row from second table, just save them with left join in-common column as key in result array.
SQL1:
$sql = SELECT artist_id FROM feeds_artists fa WHERE fa.feed_id {...RANGE...}
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$join_data[...$KEY...] = $row['artist_id'];
}
Then, get the base data and add detail of left join table from previous array while fetch them like this:
SQL2:
$sql = SELECT * FROM feeds f WHERE f.id {...RANGE...};
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$key = $row[in_common_col_value];
$row['EXTRA_DATA'] = $join_data[$key];
$final_data[] = $row;
}
Now, you'll have a $final_data array with desire extra data from $join_data array. this usually works good for date range data and like this.

Related

Need the highest ID using left join

I have two tables,
TABLE 1 has many of each client and campaign and is very large
TABLE 2 has only one of each client and campaign and is small.
So I want to get the lastest(highest ID) from TABLE 1 where it matches the client and campaign in TABLE 2 and only one of each.
I have tried MAX, and playing with the order by etc, but cant get it working....
The results I get are choosing the lowest ID from TABLE 1 (I want highest)
$result2 = mysql_query("SELECT table1.client,table1.campaign,table1.id
FROM table1
LEFT OUTER JOIN
table2
ON (table2.client = table1.client)
AND (table2.campaign = table1.campaign )
WHERE (table2.enabled != 'disabled')
group by campaign asc
order by client,campaign,id asc
");
Help needed....
SELECT * FROM table1
INNER JOIN
(
SELECT MAX(table1.id) AS id FROM table1
INNER JOIN table2 ON table2.client = table1.client AND table2.campaign=table1.campaign and table2.enabled != 'disabled'
GROUP BY table1.client, table1.campaign
) AS m ON m.id = table1.id
I think that's what you're asking for. For each combination of client and campaign that exists in each table, it will give you the highest ID in table 1.

Limiting a left join to returning one result?

I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'

MySql find data in one table based on conditions of two other tables

Help me out with this query:
I have 3 tables with this structure.
items_to_groups (item_id | group_id)
item_to_regions (item_id | region_id)
items [a bunch of columns]
I need to select every row on the item table that has an item_id match on item_to_groups table WHERE group = x AND has an item_id match on item_to_regions table WHERE region = y
Currently the code I have is a horrible subquery with loops and all.
What would be a better way of doing this?
I've thought about JOIN and such, but can't really get my head around on how to do it.
SELECT bunch_of_columns
FROM items i
INNER JOIN items_to_groups ig ON i.id=ig.item_id
INNER JOIN items_to_regions ir on i.id=ir.item_d
WHERE ir.region_id=y
AND ig.group_id=x
Have a look at the JOIN documentation on MySQL. Joins are important for relational databases.
As you said you have a hard time grasping joins, have a look at A Visual Explanation of SQL Joins by Jeff Atwood. Maybe it helps.
SELECT colums
FROM items
INNER JOIN items_to_groups ON items.item_id = items_to_groups.item_id AND group_id = x
INNER JOIN items_to_regions ON items.item_id = items_to_regions.item_id AND region_id = y
SELECT * FROM items
JOIN items_to_groups ON (items.item_id = items_to_groups.item_id AND group_id = ?)
JOIN items_to_regions ON (items.item_id = items_to_regions.item_id AND region_id = ?)
GROUP BY items.item_id

PHP / MySQL - Confusing Query

Im trying to construct a query that goes over 3 tables and im COMPLETELY stumped ... my knowledge limit is basic 1 table query and i need some help before i stick my head in a blender.
I have the following query
SELECT * FROM internalrole WHERE introle = $imarole
Im fine with that part .. its the next thats getting me all stressed.
That query returns the following columns ( id, user_id, introle, proven, used )
What i then need to do is take the user_id from the results returned and use it to get the following
SELECT * FROM users WHERE id = user_id(from previous query) AND archive = 0 and status = 8
I need to put that into 1 query, but wait, theres more .... from the results there, i need to check if that user's 'id' is in the availability table, if it is, check the date ( column name is date ) and if it matches todays date, dont return that one user.
I need to put all that in one query :S ... i have NO IDEA how to do it, thinking about it makes my head shake ... If someone could help me out, i would be eternaly grateful.
Cheers,
Use INNER JOIN, which links tables to each other based on a common attribute (typically a primary - foreign key relationship)
say an attribute, 'id', links table1 and table2
SELECT t1.att1, t2.att2
FROM table1 t1
INNER JOIN table2 t2
ON t1.id = t2.id --essentially, this links ids that are equal with each other together to make one large table row
To add more tables, just add more join clauses.
SELECT u.*
FROM internalrole ir
INNER JOIN users u
ON ir.user_id = u.id
AND u.archive = 0
AND u.status = 8
LEFT JOIN availability a
ON ir.user_id = a.user_id
AND a.date = CURDATE()
WHERE ir.introle = $imarole
AND a.user_id IS NULL /* User does NOT exist in availability table w/ today's date */
EDIT: This second query is based on the comments below, asking to show only users who do exist in the availability table.
SELECT u.*
FROM internalrole ir
INNER JOIN users u
ON ir.user_id = u.id
AND u.archive = 0
AND u.status = 8
INNER JOIN availability a
ON ir.user_id = a.user_id
WHERE ir.introle = $imarole
Hmm, maybe something like this
SELECT * FROM users WHERE id IN (SELECT user_id FROM internalrole WHERE introle = $imarole) AND archive = 0 and status = 8;
A handy thing for me to remember is that tables are essentially arrays in SQL.
HTH!
Nested queries are your friend.
SELECT * FROM users WHERE id in (SELECT user_id FROM internalrole WHERE introle = $imarole) AND archive = 0 and status = 8
Alternatively joins:
SELECT * FROM users INNER JOIN internalrole ON users.id = internalrole.user_id WHERE internalrole.user_id = $imarole AND users.archive = 0 and users.status = 8

"Triple Join"?? Can an INNER-JOIN have have an add'l query after 'WHERE' to exclude rows from a 3rd table?

Ok this (truncated query) runs fine. Got some expert advice a month ago to fix it.
SELECT * FROM artWork WHERE art_id in (
SELECT art_id FROM artWork AS a INNER JOIN userPrefs AS u ON ((
((u.media_oil='1' AND a.media_oil='1') OR
(u.media_acrylic='1' AND a.media_acrylic='1') OR
(u.media_wc='1' AND a.media_wc='1') OR
(u.media_pastel='1' AND a.media_pastel='1'))
etc, etc........................................
WHERE a.artist_id NOT EXISTS (
SELECT * FROM removeList AS r
WHERE r.artist_id = a.artist_id
AND r.user_id ='$user_id')
AND a.make_avail='1'
AND a.cur_select='1'
AND u.user_id='$user_id'
AND ((u.pref_painting='1' AND a.pref_painting='1') OR
(u.pref_photo='1' AND a.pref_photo='1') OR
(u.pref_paper='1' AND a.pref_paper='1') OR
(u.pref_print='1' AND a.pref_print='1') OR
(u.pref_draw='1' AND a.pref_draw='1') OR
(u.pref_sculp='1' AND a.pref_sculp='1') OR
(u.pref_install='1' AND a.pref_install='1') OR
(u.pref_vid='1' AND a.pref_vid='1') OR
(u.pref_public='1' AND a.pref_public='1') OR
(u.pref_indef='1' AND a.pref_indef='1'))
) ORDER BY date_submit DESC
But now I need to exclude certain rows that may be in another 2 column (user_id & artist_id) child table: 'removeList'. So I am trying without success to what amounts to a "triple join" (look for the code around 'NOT EXISTS'):
SELECT * FROM artWork WHERE art_id in (
SELECT art_id FROM artWork AS a INNER JOIN userPrefs AS u ON (
(((u.media_oil='1' AND a.media_oil='1') OR
(u.media_acrylic='1' AND a.media_acrylic='1') OR
(u.media_wc='1' AND a.media_wc='1') OR
(u.media_pastel='1' AND a.media_pastel='1'))
etc, etc........................................
WHERE a.artist_id NOT EXISTS (
SELECT * FROM removeList AS r
WHERE r.artist_id = a.artist_id AND r.user_id ='$user_id'
)
AND a.make_avail='1'
AND a.cur_select='1'
AND u.user_id='$user_id'
AND (( u.pref_painting='1' AND a.pref_painting='1') OR
( u.pref_photo='1' AND a.pref_photo='1') OR
( u.pref_paper='1' AND a.pref_paper='1') OR
( u.pref_print='1' AND a.pref_print='1') OR
( u.pref_draw='1' AND a.pref_draw='1') OR
( u.pref_sculp='1' AND a.pref_sculp='1') OR
( u.pref_install='1' AND a.pref_install='1') OR
( u.pref_vid='1' AND a.pref_vid='1') OR
( u.pref_public='1' AND a.pref_public='1') OR
( u.pref_indef='1' AND a.pref_indef='1') )
) ORDER BY date_submit DESC
Am I reaching too far here? Is there a better approach I am overlooking. Thanks to all.
NOT EXISTS shouldn't have a column name before it. remove the a.artist_id from before the not exists.
So the relevant line would say
WHERE NOT EXISTS (
SELECT * FROM removeList AS r
WHERE r.artist_id = a.artist_id AND r.user_id ='$user_id'
)
Not sure where you're using this query, but its also recommended to parameterize your queries to prevent sql injections.
Hope this helps!
i know this is not an answer, but i could not place a table within the comments so i am putting it here.
i feel like extra tables to hold your media and prefs is needed here to greatly simplify things in your database.
media_table
id, user_id, media_type
prefs_table
id, user_id, pref_type
Then fields in your art table that correlate to a media type, and pref type.
art_table
media_type, art_type
This way you could easily use a simple JOIN to find correlating records instead of those hundreds of comparisons.
SELECT art.id FROM art JOIN prefs_table ON art.pref_type = prefs_table.pref_type JOIN users ON prefs_table.user_id = users.id WHERE users.id = whatever
This is a simplified example that would pull up all peices of art that matched any one of the user's preferences. You could of course easily incorporate media types as well into the same query in the same fashion.
You need to do some serious work on your schema, along the lines suggested by dqhendricks.
You have 3 tables:
ArtWork (Art_ID, Artist_ID, ...characteristics...)
UserPrefs (User_ID, ...characteristics...)
RemoveList (Artist_ID, User_ID)
You want to list art works which match the user preferences of a specific user, but do not want to list any art works where the artist dislikes the user or the user dislikes the artist (or both).
You should, therefore, be able to do something like:
SELECT A.*
FROM ArtWork AS A
JOIN UserPrefs AS U
ON (U.User_ID = ? AND (...ghastly OR'd join conditions...))
WHERE A.Artist_ID NOT IN (SELECT R.Artist_ID
FROM RemoveList AS R
WHERE R.User_ID = ?)
Note that you can move some of the join conditions into the WHERE clause, and various other changes, but the basic structure of the query will be a JOIN of ArtWork and UserPrefs and a WHERE clause with the NOT IN clause (which you could write as a NOT EXISTS clause, but I think the NOT IN formulation is easier to read).

Categories