Self-referencing SQL statement - mysql

Overview: I need to write an custom SQL question in ExpressionEngine.
I have a product table (exp_channel_titles) that is assigned categories (exp_category_posts). In EE categories are grouped (as defined in exp_categories).
Here's what I'm trying to do...
When I'm displaying a particular product (single row), I want to go back to the product table and pull random "suggestions" as based on the categories in a certain category group of the displayed product. Finally, I want to be sure to exclude the displayed product from the suggestions.
My SQL is ok but this is fairly advance in my book. I'm here hoping to get some helps.
Thus far I can get the single row and it's categories from the category group I'm interested in. But now what?
SELECT *
FROM exp_channel_titles
LEFT JOIN exp_category_posts
ON exp_channel_titles.entry_id = exp_category_posts.entry_id
LEFT JOIN exp_categories
ON exp_category_posts.cat_id = exp_categories.cat_id
WHERE exp_channel_titles.entry_id = "17" AND exp_categories.group_id = "3"
Thanks in advance for your help.
-- UPDATE --
I suppose this is brute force but it works, almost.
SELECT *
FROM exp_channel_titles my1
LEFT JOIN exp_category_posts my2
ON my1.entry_id = my2.entry_id
LEFT JOIN exp_category_posts my3
ON my1.entry_id = my3.entry_id
LEFT JOIN exp_category_posts my4
ON my1.entry_id = my4.entry_id
WHERE my3.cat_id = "7" AND my2.cat_id = "2" AND my1.entry_id != "17" AND my4.cat_id IN (
SELECT exp_category_posts.cat_id
FROM exp_category_posts
LEFT JOIN exp_categories ON exp_category_posts.cat_id = exp_categories.cat_id
WHERE exp_category_posts.entry_id = '17' AND exp_categories.group_id = '3'
)
Since I'm needing to filter by categories from 3 different category groups I do 3 LEFT JOINs. The first two are just focused on a single category (2=published, 7=music), the third could return multiple values from the subquery so I'm using IN. The subquest asked For entry_id = X (in this case 17, which is the main/single product being displayed) what categories from group_id=3 (music) should I be suggesting?
The final question is, since the subquery returned multiple rows, the main select can return dupes. I want a list of unique products. What now? Group by? Order by?
Pardon this nooby question but to be honest Google sucks when it comes to finding answers. There's tons of crap examples, unanswered questions, etc. I'm know the concepts, but my syntax is rusty. Please help a brother out.
If anyone see any easy way to optimize the above, I'll glad listen. Thank you again.

You can do this with native EE tags using related categories mode. Just make a new channel:entries loop outside of your main product channel:entries tag, or embed it if you have to.

I'd suggest embedding a template within your channel:entries loop, and passing the current entry_id as an embed parameter.
{embed="embeds/related_products" entry_id="{entry_id}"}
Then in your embedded template:
{exp:channel:entries channel="products" category_group="3|4" entry_id="not {embed:entry_id}" limit="5" orderby="random"}
// variables here
{/exp:channel:entries}

DISCLAIMER: Not sure if this will work, and it could very likely do with some optimization, but perhaps this will work:
SELECT DISTINCT ect.*
FROM
exp_channel_titles ect
LEFT JOIN exp_category_posts ecp ON ect.entry_id = ecp.entry_id
LEFT JOIN exp_categories ec ON ecp.cat_id = ec.cat_id
WHERE
ect.entry_id != '17'
AND ec.group_id IN (
SELECT ec.group_id
FROM
exp_channel_titles ect
LEFT JOIN exp_category_posts ecp ON ect.entry_id = ecp.entry_id
LEFT JOIN exp_categories ec ON ecp.cat_id = ec.cat_id
WHERE
ect.entry_id = '17'
);

Related

SQL AND without requirement

I have a table displaying on my website with a list of projects. The SQL statement below pulls in each project and converts the ###_id columns to the ###_name in another table. So far so good.
The problem I have is that this is requiring all fields in a row in the projects table to be filled out. If, for example, the project row has no value for 'proj_industry_id' then the project won't display here at all.
I've tried removing the 'AND' for each match-up in the WHERE statement and separating them with commas, but it errors out.
I've also checked SQL docs and can't seem to find my way to an answer over there.
Any ideas on how I can get my statement to still match up the id with the name when I have one, but still show the record when I don't?
Thanks!
$sql = "SELECT
projects.*,
engagement_types.eng_type_name AS eng_type,
users.user_full_name AS username,
industries.industry_name AS industry_name,
categories.category_name AS category_name,
geographies.geo_name AS geo_name,
status.status_name AS status_name
FROM
projects,
engagement_types,
users,
industries,
categories,
geographies,
status
WHERE
projects.proj_eng_type_id = engagement_types.id
AND projects.proj_lead_id = users.id
AND projects.proj_industry_id = industries.id
AND projects.proj_category_id = categories.id
AND projects.proj_geo_id = geographies.id
AND projects.proj_status_id = status.id
AND projects.proj_geo_id = '$selected_geo_id'";
*****EDIT******
Here is the final correct code from the solution below using multiple left joins!
SELECT
projects.*,
engagement_types.eng_type_name AS eng_type,
users.user_full_name AS username,
industries.industry_name AS industry_name,
categories.category_name AS category_name,
geographies.geo_name AS geo_name,
status.status_name AS status_name
FROM
projects
LEFT JOIN engagement_types ON projects.proj_eng_type_id = engagement_types.id
LEFT JOIN users ON projects.proj_lead_id = users.id
LEFT JOIN industries ON projects.proj_industry_id = industries.id
LEFT JOIN categories ON projects.proj_category_id = categories.id
LEFT JOIN geographies ON projects.proj_geo_id = geographies.id
LEFT JOIN status ON projects.proj_status_id = status.id
GROUP BY
proj_start_date
sounds like your have to look at "LEFT JOIN" https://www.w3schools.com/sql/sql_join_left.asp
Otherwise you miss the left part of the green circle.

MySQL Query limiting results by sub table

I'm really struggling with this query and I hope somebody can help.
I am querying across multiple tables to get the dataset that I require. The following query is an anonymised version:
SELECT main_table.id,
sub_table_1.field_1,
main_table.field_1,
main_table.field_2,
main_table.field_3,
main_table.field_4,
main_table.field_5,
main_table.field_6,
main_table.field_7,
sub_table_2.field_1,
sub_table_2.field_2,
sub_table_2.field_3,
sub_table_3.field_1,
sub_table_4.field_1,
sub_table_4.field_2
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
WHERE sub_table_4.field_1 = '' AND sub_table_4.field_2 = '0' AND sub_table_2.field_1 != ''
The query works, the problem I have is sub_table_1 has a revision number (int 11). Currently I get duplicate records with different revision numbers and different versions of sub_table_1.field_1 which is to be expected, but I want to limit the result set to only include results limited by the latest revision number, giving me only the latest sub_table_1_field_1 and I really can not figure it out!
Can anybody lend me a hand?
Many Thanks.
It's always important to remember that a JOIN can be on a subquery as well as a table. You could build a subquery that returns the results you want to see then, once you've got the data you want, join it in the parent query.
It's hard to 'tailor' an answer that's specific to you problem, as it's too obfuscated (as you admit) to know what the data and tables really look like, but as an example:
Say table1 has four fields: id, revision_no, name and stuff. You want to return a distinct list of name values, with their latest version of stuff (which, we'll pretend varies by revision). You could do this in isolation as:
select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no;
(Note: see fiddle at the end)
That would return each individual name with the latest revision of stuff.
Once you've got that nailed down, you could then swap out
INNER JOIN sub_table_1 ON sub_table_1.id = main_table.id
....with....
INNER JOIN (select t.* from table1 t
inner join
(SELECT name, max(revision_no) maxr
FROM table1
GROUP BY name) mx
on mx.name = t.name
and mx.maxr = t.revision_no) sub_table_1
ON sub_table_1.id = main_table.id
...which would allow a join with a recordset that is more tailored to that which you want to join (again, don't get hung up on the actual query I've used, it's just there to demonstrate the method).
There may well be more elegant ways to achieve this, but it's sometimes good to start with a simple solution that's easier to replicate, then simplify it once you've got the general understanding of the what and why nailed down.
Hope that helps - as I say, it's as specific as I could offer without having an idea of the real data you're using.
(for the sake of reference, here is a fiddle with a working version of the above example query)
In your case where you only need one column from the table, make this a subquery in your select clause instead of than a join. You get the latest revision by ordering by revision number descending and limiting the result to one row.
SELECT
main_table.id,
(
select sub_table_1.field_1
from sub_table_1
where sub_table_1.id = main_table.id
order by revision_number desc
limit 1
) as sub_table_1_field_1,
main_table.field_1,
...
FROM main_table
INNER JOIN sub_table_4 ON sub_table_4.id = main_table.id
INNER JOIN sub_table_2 ON sub_table_2.id = main_table.id
INNER JOIN sub_table_3 ON sub_table_3.id = main_table.id
WHERE sub_table_4.field_1 = ''
AND sub_table_4.field_2 = '0'
AND sub_table_2.field_1 != '';

SQL query - attempting to display results based on certain conditions using AND/OR and JOINS

Attempting to display all proposals in the system as long as they satisfy the following conditions:
the source must be supervisor
the status_code in the record table must not be 8 or 3
I have attempted a number of way in trying to make this work but I keep getting different data back which is not what I want.
The problem is how the tables have been set up. In theory a proposal can be applied for by a student and if that happens a record is automatically added to the 'record' table with a status set to '6' for pending. This status is then changed a number of times throughout the application process.
What I need is to show the users all proposals which have not been taken. The status codes to indicate this is '3' (accepted by a student so another student cannot take it) and '8' (not available).
NOTE: not all proposals may have a record in this table (a student has not applied for them)
This is the query so far which satisfies the first condition of being a "supervisor"
SELECT p.proposal_id, p.proposal_title,
p.description, u.user_record_id, u.forename,
u.surname, c.course_title, r.*,
GROUP_CONCAT(DISTINCT t.tag_title) AS tags
FROM proposal p
LEFT JOIN user u on u.user_record_id = p.user_record_id
LEFT JOIN proposal_tags pt on pt.proposal_id = p.proposal_id
LEFT JOIN tag_details t on t.tag_code = pt.tag_code
LEFT JOIN course_details c on c.course_code = p.course_code
LEFT JOIN record r on r.proposal_id = p.proposal_id
WHERE p.source = "Supervisor"
GROUP BY p.proposal_id
My attempt at getting it to display all records which are available:
SELECT p.proposal_id, p.proposal_title,
p.description, u.user_record_id, u.forename,
u.surname, c.course_title, r.*,
GROUP_CONCAT(DISTINCT t.tag_title) AS tags
FROM proposal p
LEFT JOIN user u on u.user_record_id = p.user_record_id
LEFT JOIN proposal_tags pt on pt.proposal_id = p.proposal_id
LEFT JOIN tag_details t on t.tag_code = pt.tag_code
LEFT JOIN course_details c on c.course_code = p.course_code
LEFT JOIN record r on r.proposal_id = p.proposal_id
WHERE p.source = "Supervisor"
AND (r.status_code != 3 OR r.status_code !=8)
GROUP BY p.proposal_id
The above query still returns proposals with the status code 3 and it fails to display any proposals which have yet not been applied for.
SQLFiddle Generated: http://sqlfiddle.com/#!9/b89a9/1/0
Any help would be much appreciated guys! Thank you.
Turned out I was missing a OR statement to achieve my goal.
To get the end result i needed, the query was modified to:
SELECT p.proposal_id, p.proposal_title, p.description, u.user_record_id, u.forename, u.surname, c.course_title, r.*, GROUP_CONCAT(DISTINCT t.tag_title) AS tags FROM proposal p
LEFT JOIN user u on u.user_record_id = p.user_record_id
LEFT JOIN proposal_tags pt on pt.proposal_id = p.proposal_id
LEFT JOIN tag_details t on t.tag_code = pt.tag_code
LEFT JOIN course_details c on c.course_code = p.course_code
LEFT JOIN record r on r.proposal_id = p.proposal_id
WHERE p.source = "Supervisor"
AND (r.status_code not in (3,8) OR r.status_code IS NULL)
GROUP BY p.proposal_id
Successfully brings back all proposals which are available, are from the supervisor and those which have no record in the 'record' table.
Thank you to everyone who helped

WHERE clause if match

What is the simplest way to make part of a WHERE clause dependent on the result of a JOIN? I realize that is an extremely ambiguous and confusing question, so allow me to simply show you what I am trying to achieve:
SELECT m.member_id, m.first_name, m.last_name
FROM cal_form_answers a
INNER JOIN cal_form_elements e
USING(element_id)
INNER JOIN cal_forms f
USING(form_id)
LEFT JOIN members m
USING(member_id)
WHERE f.org_id = ?
AND m.org_id = ?
AND e.form_id = ?
GROUP BY a.member_id
ORDER BY a.member_id
First, note that the question marks are not invalid syntax—I am using Codeigniter, which uses them as bound parameters.
Line 10 (AND m.org_id = ?) is dependent on whether or not the LEFT JOIN actually finds something. If there is no match in the members table, then Line 10 becomes unnecessary. In fact, it becomes a problem. I would like to limit results by Line 10 unless there is no match in the members table. In that event, I would simply like to ignore that part of the WHERE clause.
I believe this can be achieved using subqueries, though I am admittedly unsure how to work out the syntax. Is there any other way? If yes, how else can this result be achieved? In the event there is no other way, can somebody demonstrate an implementation of a subquery for this situation, and explain how it works?
Thank you!
If a LEFT JOIN does not match a record, then those LEFT JOINed fields are null. Why not just check if that field IS NULL, and when it is not then check it.)
SELECT m.member_id, m.first_name, m.last_name
FROM cal_form_answers a
INNER JOIN cal_form_elements e
USING(element_id)
INNER JOIN cal_forms f
USING(form_id)
LEFT JOIN members m
USING(member_id)
WHERE f.org_id = ?
AND (m.org_id = ? OR m.org_id IS NULL)
AND e.form_id = ?
GROUP BY a.member_id
ORDER BY a.member_id

php mysql join 3 tables

Im looping through a feedback type comment system on a users page, finding the latest 6 posts.
To display the post how I want, I have to grab data from 3 tables.
I figure it'd be less db intensive if it was all pulled in one query, but I'm not sure how to write it, being suck at JOINs and things.
This is my existing code
$sql_result = mysql_query("SELECT * FROM feedback WHERE user='1' ORDER BY date desc LIMIT 6", $db);
while($rs = mysql_fetch_array($sql_result)) {
$sql_result2 = mysql_query("SELECT * FROM members WHERE id= '$rs[author]'", $db);
$rs2 = mysql_fetch_array($sql_result2);
if ($rs2[clan] != 0) {
$sql_result3 = mysql_query("SELECT * FROM clans WHERE id= '$rs2[clan]' LIMIT 1", $db);
$rs3 = mysql_fetch_array($sql_result3);
// write comment
Can someone give me a clue?
This should do it:
select * from feedback
left join members on feedback.author = members.id
left join clans on members.clan = clans.id
where feedback.user = 1
left join means if the table on the right has no matching row, a record with all null values is returned, so you still get the data from the table on the left.
I am no expert in Sql myself, but I have picked up a few tricks here and there :-)
A typical LEFT JOIN that works in Firebird is :
select A.*,B.*,C.*
from FEEDBACK A left join MEMBERS B
on A.USER = B.ID left join CLANS C
ON C.ID = A.USER
where A.USER=1
The logic behind the join is that All rows that now share the same value,
A.USER = B.ID = C.ID will now be visible.
The letters A B and C is just used for simplicity.
F, M and C will work the same way.
This Left Join will pick out all and every column in tables. This is done with A.*,B.*,C.*
Maybe you want only a few columns in each table.
That can be accomplished by naming the columns in the same manner.
Example:
A.USER,A.FIRSTNAME,A.SURNAME,B.COLNAME1,B.COLNAME2,C.COLNAME1,C.COLNAME2
When you need to adress the columns later, remember the Prefix of A. or B. or C. before the actual column-name you address.
Good luck and best regards.
Morten, Norway