Working with following structure, how can I select a subset of requests respecting multiple conditions in this 1:n relation?
# Table 1: Request
uid
name
# Table 2: Additional Information
uid
type
value
request
Note for table 2: type can be anything, i.e. 'product_name' or 'rating'.
If I'd just want to select Requests by a given product_name I can do this:
SELECT * FROM request as r
LEFT JOIN additional_information as i
ON r.uid = i.request
WHERE i.type = 'product' AND i.value = 'Product Name'
I'm stuck at what my statement must look like if I want to select Requests by a given product_name AND rating. I have tried to simply add another join but this gave me all requests that related to a given product_name as well as all requests related to a given rating. I need the statement to respect both conditions.
This, as mentioned, does not work for me.
SELECT * FROM request as r
LEFT JOIN additional_information as i
ON r.uid = i.request
LEFT JOIN additional_information as a
ON r.uid = a.request
WHERE i.type = 'product' AND i.value = 'Product Name'
OR a.type = 'rating' AND a.value = 1
Appreciate the help!
Move those conditions from WHERE to JOIN ON condition like
SELECT * FROM request as r
LEFT JOIN additional_information as i
ON r.uid = i.request
AND i.type = 'product' AND i.value = 'Product Name'
LEFT JOIN additional_information as a
ON r.uid = a.request
AND a.type = 'rating' AND a.value = 1;
And Yes absolutely, considering the valuable comment (which missed) from Strwabery, instead of doing a select * or select r.* you might actually want to specify the column names you want to fetch which is better than * performance wise since you are not getting unnecessary data using projection; unless you really want to fetch everything.
It might be slow from the other offering since it is joining to your additional data TWICE, once for product, again on rating. This should probably be changed to a single left-join otherwise you could get a Cartesian result bloating your answer.
SELECT *
FROM
request as r
LEFT JOIN additional_information as i
ON r.uid = i.request
AND ( ( i.type = 'product' AND i.value = 'Product Name' )
OR ( i.type = 'rating' AND i.value = 1 );
Related
Say I have two tables, users and addresses (contains columns id and text). I have two address columns in users, say temp_address_id and permanent_address_id.
If I want to get add the users with temp_address = 'SF' or permanent_address = 'MTV', is the following query the only way out?
SELECT * FROM users
JOIN addresses ON (
(users.temp_address_id = addresses.id AND addresses.text = 'SF')
OR (users.permanent_address_id = addresses.id AND addresses.text = 'MTV')
)
I don't think there is a way to specify the filters (addresses.text = 'SF' and addresses.text = 'MTV') in the WHERE clause. Is that right?
If you want only the users, I would recommend two exists:
SELECT u.*
FROM users u
WHERE EXISTS (SELECT 1
FROM addresses a
WHERE u.temp_address_id = a.id AND a.text = 'SF'
) OR
EXISTS (SELECT 1
FROM addresses a
WHERE u.permanent_address_id = a.id AND a.text = 'MTV'
);
This should have much better performance than OR in the JOIN clause, especially with an index on addresses(id, text).
If you need address information returned, use two left joins:
SELECT u.*, COALECE(ta.text, tp.text) as text
FROM users u LEFT JOIN
addresses ta
ON u.temp_address_id = ta.id AND
ta.text = 'SF' LEFT JOIN
addresses tp
ON u.permanent_address_id = ta.id AND
ta.text = 'MTV'
WHERE ta.id IS NOT NULL OR tp.id IS NOT NULL;
The downside to this approach is that you need to use COALESCE() for each of the columns selected from the address table.
Edit: Changed join condition to include both addresses.
Why can't you do following? Have you tried it?
SELECT u.*,COALESCE(a.text,b.text) AS text
FROM users u
LEFT JOIN addresses a ON u.temp_address_id = a.id AND a.text = 'SF'
LEFT JOIN addresses b ON u.permanent_address_id = b.id AND b.text = 'MTV';
I have a query which gives result as below, how to replace duplicate values with NULL
Query:
SELECT
word.lemma,
synset.definition,
synset.pos,
sampletable.sample
FROM
word
LEFT JOIN
sense ON word.wordid = sense.wordid
LEFT JOIN
synset ON sense.synsetid = synset.synsetid
LEFT JOIN
sampletable ON synset.synsetid = sampletable.synsetid
WHERE
word.lemma = 'good'
Result:
Required Result: all the greyed out results as NULL
First, this is the type of transformation that is generally better done at the application level. The reason is that it presupposes that the result set is in a particular order -- and you seem to be assuming this even with no order by clause.
Second, it is often simpler in the application.
However, in MySQL 8+, it is not that hard. You can do:
SELECT w.lemma,
(CASE WHEN ROW_NUMBER() OVER (PARTITION BY w.lemma, ss.definition ORDER BY st.sample) = 1
THEN ss.definition
END) as definition,
ss.pos,
st.sample
FROM word w LEFT JOIN
sense s
ON w.wordid = s.wordid LEFT JOIN
synset ss
ON s.synsetid = ss.synsetid LEFT JOIN
sampletable st
ON ss.synsetid = st.synsetid
WHERE w.lemma = 'good'
ORDER BY w.lemma, ss.definition, st.sample;
For this to work reliably, the outer ORDER BY clause needs to be compatible with the ORDER BY for the window function.
If you are using Mysql 8 try with Rank().. As I didn't have your table or data couldn't test this query.
SELECT
word.lemma
,case when r = 1 synset.definition else null end as definition
,synset.pos
,sampletable.sample
FROM
(
SELECT
word.lemma
,synset.definition
,synset.pos
,sampletable.sample
,RANK() OVER (PARTITION BY synset.definition ORDER BY synset.definition) r
FROM
(
SELECT
word.lemma,
synset.definition,
synset.pos,
sampletable.sample
FROM
word
LEFT JOIN
sense ON word.wordid = sense.wordid
LEFT JOIN
synset ON sense.synsetid = synset.synsetid
LEFT JOIN
sampletable ON synset.synsetid = sampletable.synsetid
WHERE
word.lemma = 'good'
) t
)t1;
I have four tables by the name candidate, program_type, program_of_interest and desired_intake. The table candidate has a many to one relationship with the rest of the three tables.
The candidate table looks like this:
The program_type table:
The program_of_interest table:
The desired_intake table:
I am trying to replace the program_type,program_of_interest,desired_intake id's in the candidate table with the corresponding values in their respective tables.
I am able to select the values as needed with help from this thread SQL Replace multiple variables from another table in query result. Here is my solution to select:
SELECT
c.id,
p.value as 'Program type',
p1.value as 'Program of interest',
d.value as 'Desired intake'
FROM candidate c
JOIN program_type p on p.id = c.program_type
JOIN program_of_interest p1 on p1.id = c.program_of_interest
JOIN desired_intake d on d.id = c.desired_intake
My question is how do I replace the ids in the candidate table with their respective values?
You could use an update basend on join as
Update candidate c
INNER JOIN program_type p on p.id = c.program_type
INNER JOIN program_of_interest p1 on p1.id = c.program_of_interest
INNER JOIN desired_intake d on d.id = c.desired_intake
set c.program_type = p.value,
c.program_of_interest = p1.value,
c.desired_intake = d.value
but seems strange you want update eg: program_type with the value (c.program_type = p.value)
and you are using program_type for join ( p.id = c.program_type)
Good morning -
This is my first post here, after many years using SO as a very useful resource.
I've run into a problem with a complex (for me) query I'm pulling together for a wordpress site running woocommerce to process orders. I'm trying to add a filter to the order list which filters orders which contain products in a particular product category.
I'm afraid I've gotten in over my head with this query which joins a variety of meta tables on inner queries in order to get at the information I need in order to determine the product's category.
The problem is that I can't get the scoping rules to work in order to access required outer table information in the inner queries.
The query is:
SELECT SQL_CALC_FOUND_ROWS
wp_ot6q6i_posts.ID
FROM
wp_ot6q6i_posts
WHERE
1 = 1 AND YEAR(wp_ot6q6i_posts.post_date) = 2015 AND MONTH(wp_ot6q6i_posts.post_date) = 12 AND wp_ot6q6i_posts.post_type = 'shop_order' AND(
(
wp_ot6q6i_posts.post_status = 'wc-pending' OR wp_ot6q6i_posts.post_status = 'wc-processing' OR wp_ot6q6i_posts.post_status = 'wc-on-hold' OR wp_ot6q6i_posts.post_status = 'wc-completed' OR wp_ot6q6i_posts.post_status = 'wc-cancelled' OR wp_ot6q6i_posts.post_status = 'wc-refunded' OR wp_ot6q6i_posts.post_status = 'wc-failed'
)
) AND EXISTS(
SELECT
t2.PROD_ID
FROM
(
SELECT
wp_ot6q6i_woocommerce_order_itemmeta.meta_value AS PROD_ID
FROM
wp_ot6q6i_woocommerce_order_items
LEFT JOIN
wp_ot6q6i_woocommerce_order_itemmeta
ON
wp_ot6q6i_woocommerce_order_itemmeta.order_item_id = wp_ot6q6i_woocommerce_order_items.order_item_id
WHERE
wp_ot6q6i_woocommerce_order_items.order_item_type = 'line_item' AND wp_ot6q6i_woocommerce_order_itemmeta.meta_key = '_product_id' AND wp_ot6q6i_posts.ID = wp_ot6q6i_woocommerce_order_items.order_id
) t1
INNER JOIN
(
SELECT DISTINCT
wposts.ID AS PROD_ID
FROM
wp_ot6q6i_posts wposts
LEFT JOIN
wp_ot6q6i_postmeta wpostmeta
ON
wposts.ID = wpostmeta.post_id
LEFT JOIN
wp_ot6q6i_term_relationships
ON
(
wposts.ID = wp_ot6q6i_term_relationships.object_id
)
LEFT JOIN
wp_ot6q6i_term_taxonomy
ON
(
wp_ot6q6i_term_relationships.term_taxonomy_id = wp_ot6q6i_term_taxonomy.term_taxonomy_id
)
WHERE
wp_ot6q6i_term_taxonomy.taxonomy = 'product_cat' AND wp_ot6q6i_term_taxonomy.term_id IN(
SELECT
term_id
FROM
`wp_ot6q6i_terms`
WHERE
slug = 'preorder'
)
ORDER BY
wpostmeta.meta_value
) t2
ON
t1.PROD_ID = t2.PROD_ID
)
ORDER BY
wp_ot6q6i_posts.post_date
DESC
LIMIT 0, 20
And the error I'm getting is:
1054 - Unknown column 'wp_ot6q6i_posts.ID' in 'where clause'
Thanks all for your help. I ended up going in a different direction to solve this problem, one I'm more comfortable with as a dev...I'm pulling the fixed list of items from the last join and building a query in code that has a series of more simple queries in the where clause, thereby avoiding the whole Exists approach.
Thanks again for your help.
I want to list all teams, then count how many times each team appears in my second table. Some users are not in the second table, so the count would be zero. The problem is when I use the count function it only lists users that are in the second table. How do I count, and list 0 if they dont appear in second table?
$query = "SELECT t.id as id, t.t_name as name, t.t_city as city, (count(pd.rs)) as pd FROM #__bl_regions as r, #__bl_teams as t, #__bl_paid as pd WHERE t.id != 0 AND t.id != 1 AND (t.id IN($teams)) AND r.id = ".$t_id." AND pd.rs = 1 AND pd.t_id = ".$t_id." ORDER BY t.t_name";
$db->setQuery($query);
$players = $db->loadObjectList();
Tried Left Join
Ok, so because I am including 3 tables I believe I have to use 2 queries. Same thing is still happening, only listing schools with count. #__bl_paid is the table I want to count, #__bl_teams is the table I want to list all.
$query = "SELECT t.id as id FROM #__bl_regions as r, #__bl_teams as t WHERE t.id != 0 AND t.id != 1 AND (t.id IN($teams)) AND r.id = ".$t_id." ORDER BY t.t_name";
$db->setQuery($query);
$players1 = $db->loadResultArray();
if ($players1){
$players2 = implode(",",$players1);
}else{
$players2 = 0;
}
$query = "SELECT t.id as id, t.t_name as name, t.t_city as city, coalesce((count(pd.rs)),0) as pdc FROM #__bl_paid as pd LEFT JOIN #__bl_teams as t ON pd.t_id = t.id WHERE (t.id IN($players2)) ORDER BY t.t_name";
$db->setQuery($query);
$players = $db->loadObjectList();
You need two pieces to get what you want:
an outer join -- left join is the typical MySQL version used
a way to detect if a column is null, and if so, supply a different value. I often use coalesce
An inner join drops rows that don't have matches in the other table; a left join is similar to an inner join, but preserves all the rows in the left table, and supplies columns with null if there's no matching row in the right table.
Here's an example:
select column1, coalesce(column2, 0) as `newcolumn2`
from lefttable
left join righttable
on lefttable.something = righttable.something
What this will do: whenever column2 is null, it will be replaced with 0.
You should use LEFT JOIN statement instead of INNER JOIN.