MySQL: Using NULL as wildcard in JOIN statement - mysql

I have a table containing perfectly defined items and a second table with potentially vague/greedy orders as NULL would require all available values for this parameter.
items
+-----------------------+
| item_id | color | size |
|---------+-------+------|
| 1 | blue | 8 |
| 2 | red | 6 |
| 3 | green | 7 |
| 4 | black | 6 |
+------------------------+
orders
+-------------------------+
| order_id | color | size |
|----------+-------+------|
| 1 | red | 6 |
| 2 | green | 8 |
| 3 | NULL | 6 |
| 4 | blue | NULL |
| 5 | NULL | NULL |
+-------------------------+
Is there an efficient way to generate a complete list of items needed to fill all orders?
+--------------------+
| order_id | item_id |
|----------+---------|
| 1 | 2 |
| 3 | 2 |
| 3 | 4 |
| 4 | 1 |
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 5 | 4 |
+--------------------+
It seems to me like an INNER JOIN should be able to do this, but something like this obviously doesn't consider the possibility of NULL values as greedy wildcards in the orders table:
SELECT order_id, item_id
FROM orders
INNER JOIN items ON orders.color = items.color AND orders.size = items.size
Any ideas?

Try the following:
SELECT order_id, item_id
FROM orders
INNER JOIN items ON (orders.color IS NULL OR orders.color = items.color)
AND (orders.size IS NULL OR orders.size = items.size)
Let me know if that helps, or if I misunderstood the question.

If you rewrite the conditions for the JOIN you can get the desired result:
SELECT order_id, item_id
FROM orders
JOIN items
ON ((orders.color = items.color OR orders.color IS NULL)
AND (orders.size = items.size OR orders.size IS NULL))
However, the orders table should probably look more like the result of this query than the current orders table.

You could use the IFNULL function for this:
SELECT order_id, item_id
FROM orders
JOIN items ON IFNULL(orders.color, items.color) = items.color
AND IFNULL(orders.size, items.size) = items.size
If the value in orders is null, then it'll use the value from items (and thus match).

Related

How can I get a total count of items?

I'm trying to figure out a MYSQL string and my noob-ness is getting in my way. I'm trying to count the total number of teams per phase.
Tables to consider:
phases
+----+------------+
| id | phase_name |
+----+------------+
| 1 | start |
| 2 | middle |
| 3 | end |
| 4 | finish |
+----+------------+
teams
+----+-----------+----------+
| id | team_name | phase_id |
+----+-----------+----------+
| 1 | team1 | 2 |
| 2 | team2 | 3 |
| 3 | team3 | 3 |
| 4 | team4 | 4 |
| 4 | team5 | 3 |
+----+-----------+----------+
Desired result
+----------+------------+-----------+
| phase_id | phase_name | tot_teams |
+----------+------------+-----------+
| 1 | start | NULL |
| 2 | middle | 1 |
| 3 | end | 3 |
| 4 | finish | 1 |
+----------+------------+-----------+
I've tried:
SELECT
T.phase_id, P.phase_name, COUNT(*) AS tot_teams
FROM
teams T
LEFT JOIN
phases P ON P.id = T.phase_id
GROUP BY
phase_id;
but that only shows the affected phase_id's...and I'm hoping to get ALL phase_id's in a table. I also tried:
SELECT
P.phase_name, T.phase_id, COUNT(*)
FROM
teams T
RIGHT JOIN
phases P on P.`id` = T.`phase_id`
GROUP BY
P.id
but that shows invalid data. (For example, phase_id has a qty of 1 but doesn't show up in the teams table.
Can you point me in the right direction?
Thanks!
The RIGHT JOIN is correct, but you need to use COUNT(T.phase_id) instead of COUNT(*). Otherwise, you're counting the row containing NULL that's generated for the phase with no teams.
Most people prefer to use LEFT JOIN, putting the master table first.
SELECT P.phase_name, P.phase_name, COUNT(T.phase_id)
FROM phase AS P
LEFT JOIN teams AS T ON P.id = T.phase_id
GROUP BY P.id

MySQL function: rank table by most similar attributes

I have a table of products ids and keywords that looks like the following:
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| product_id | int(10) unsigned | YES | MUL | NULL | |
| keyword | varchar(255) | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
This table simply stores product ids, and keywords associated with those products. So for example, it might contain:
+----+------------+---------+
| id | product_id | name |
+----+------------+---------+
| 1 | 1 | soft |
| 2 | 1 | red |
| 3 | 1 | leather |
| 4 | 2 | cloth |
| 5 | 2 | red |
| 6 | 2 | new |
| 7 | 3 | soft |
| 8 | 3 | red |
| 9 | 4 | blue |
+----+------------+---------+
In other words:
product 1 is soft, red, and leather.
product 2 is cloth, red and new.
Product 3 is red and soft,
product 4 is blue.
I need some way to take in a product ID, and get back a sorted list of product ids ranked by the number of common keywords
So for example, if I pass in product_id 1, I'd expect to get back:
+----+-------+------------+
| product_id | matches |
+------------+------------+
| 3 | 2 | (product 3 has two common keywords with product 1)
| 2 | 1 | (product 2 has one common keyword with product 1)
| 4 | 0 | (product 4 has no common keywords with product 1)
+------------+------------+
One option uses a self right outer join with conditional aggregation to count the number of matched names between, e.g. product ID 1, and all other product IDs:
SELECT t2.product_id,
SUM(CASE WHEN t1.name IS NOT NULL THEN 1 ELSE 0 END) AS matches
FROM yourTable t1
RIGHT JOIN yourTable t2
ON t1.name = t2.name AND
t1.product_id = 1
WHERE t2.product_id <> 1
GROUP BY t2.product_id
ORDER BY t2.product_id
Follow the link below for a running demo:
SQLFiddle
You need to use an outer join against the keywords for productid 1:
select y.productid, count(y2.keyword)
from yourtable y
left join (
select keyword from yourtable y2 where y2.productid = 1
) y2 on y.keyword = y2.keyword
where y.productid <> 1
group by y.productid
order by 2 desc
SQL Fiddle Demo
Results:
| productid | count(y2.keyword) |
|-----------|-------------------|
| 3 | 2 |
| 2 | 1 |
| 4 | 0 |

Filtering data in left join

Here is some data:
record
-------------------------------------------------
| id | name |
-------------------------------------------------
| 1 | Round Cookie |
| 2 | Square Cookie |
| 3 | Oval Cookie |
| 4 | Hexagon Cookie |
-------------------------------------------------
record_field_data
----------------------------------------------
| id | record_id | data | type_id |
----------------------------------------------
| 1 | 1 | White | 1 |
| 2 | 1 | Round | 2 |
| 3 | 2 | Green | 1 |
| 4 | 2 | Square | 2 |
| 5 | 3 | Blue | 1 |
| 6 | 3 | Oval | 2 |
| 7 | 4 | Hexagon | 2 |
----------------------------------------------
record_type_field
-------------------------------------------------
| id | data_type |
-------------------------------------------------
| 1 | Color |
| 2 | Shape |
-------------------------------------------------
I am trying to get a list of all records left joined with the record_field_data of type "Color". This needs to be a left join because there may not be record_field_data of a given type, and I still want the record if the case.
This is the query I have come up with but it is returning a left join with ALL record_field_data and not just the specific ones I want.
SELECT record.id AS id, recordfield.data, recordtype.field_name
FROM record
LEFT JOIN record_field_data AS recordfield ON (record.id = recordfield.record_id)
LEFT JOIN record_type_field AS recordtype ON (recordfield.type_id = recordtype.id AND recordtype.data_type = 'Color');
I could do this with a subquery in the JOIN but I can't use a subquery. I have to translate this to HQL and subqueries are not supported in HQL for joins.
The result I am looking for is records ordered by the record_field_data where record_type_field.data_type is 'Color'. Note that "Hexagon cookie" doesn't have a color defined, I don't know if it should be at the top or bottom at this point. Either way will work.
-------------------------------------------------
| id | name |
-------------------------------------------------
| 3 | Oval Cookie |
| 2 | Square Cookie |
| 1 | Round Cookie |
| 4 | Hexagon Cookie |
-------------------------------------------------
SELECT r.id, r.name
FROM record r
JOIN record_type_field rf
ON rf.data_type = 'Color'
LEFT JOIN
record_type_data rd
ON rd.record_id = r.id
AND rd.type_id = rf.id
ORDER BY
rd.data
Have you tried using the SQL 'IN' clause.
select record.id as id, recordfield.data, recordtype.field_name
from record
left join record_field_data recordfield ON record.id = recordfield.record_id
WHERE id IN (SELECT id FROM record_type_field where data_type='Color');
The IN clause allows you to specify a list condition. So the subquery gets all of the ids where the type is "Color", and you are then doing the join and selecting all records from that join that have an id in the list of ids corresponding to the type "Color".
You have to change the second join to a INNER JOIN.
If you use two LEFT JOINs you are selecting all the records from record AND from record_field_data.

Query needed to get an column values of one table which are not there in string column of another table

I have a two tables :
mysql> select * from quizquestionbank;
| ID | QuestionFilePath | CorrectAnswer |
| 1 | p.wav | 1 |
| 2 | q.wav | 2 |
| 3 | a.wav | 3 |
| 4 | b.wav | 1 |
| 5 | m.wav | 3 |
Second table is :
mysql> select * from quizuserdetails;
| ID | MSISDN | QuestionIdDetails | AnswerRecord |
| 1 | 235346 | 1,3,4,5 | S,F,S,F |
| 2 | 564574 | 4,5,67,88 | F,S,F,s |
| 3 | 500574 | 5,55,66,44,2 | F,F,F,F |
I want to get the IDs from table 1 which are not there in QuestionIdDetails column of second table.
I tried query
Select ID from quizquestionbank where ID not in (Select QuestionIdDetails from quizuserdetails where msisdn = '235346 ');
But this does not work
CAn anybody suggest a way to do it
Use find_in_set() to match the id to the list, but that's not all:
Select disting qb.ID
from quizquestionbank qb
left join quizuserdetails qd
on find_in_set(qb.id, QuestionIdDetails) > 0
and msisdn = '235346'
where qd.id is null
There's 3 key things going on here:
using a left join and including the extra condition in the join condition
the use of find_in_set(), which finds a value in a CSV string, to make the join
using a where clause that filters out matches, leaving only the missed joins

Join top 3 interest fields along with each user row

I'm trying to get the top 3 interests of each user, probably as a LEFT JOIN query.
The way the app is designed, each user has a set of interests which are no other than 'childs' (rows without parent) of the categories table.
Here are some simplified table schemas w/mock data (see SQL Fiddle demo)
-- Users table
| ID | NAME |
--------------
| 1 | John |
| 2 | Mary |
| 3 | Chris |
-- Categories table -- Interests table
| ID | NAME | PARENT | | ID | USER_ID | CATEGORY_ID |
-------------------------------------- ------------------------------
| 1 | Web Development | (null) | | 1 | 1 | 1 |
| 2 | Mobile Apps | (null) | | 2 | 1 | 1 |
| 3 | Software Development | (null) | | 3 | 1 | 1 |
| 4 | Marketing & Sales | (null) | | 4 | 2 | 1 |
| 5 | Web Apps | 1 | | 5 | 2 | 1 |
| 6 | CSS | 1 | | 6 | 3 | 1 |
| 7 | iOS | 2 | | 7 | 3 | 1 |
| 8 | Streaming Media | 3 | | 8 | 3 | 1 |
| 9 | SEO | 4 |
| 10 | SEM | 4 |
To get the top 3 interests of a given user, I've usually performed this query:
SELECT `c`.`parent` as `category_id`
FROM `interests` `i` LEFT JOIN `categories` `c` ON `c`.`id` = `i`.`category_id`
WHERE `i`.`user_id` = '2'
GROUP BY `c`.`parent`
ORDER BY count(`c`.`parent`) DESC LIMIT 3
This query returns the top 3 categories (parents) of user with id = 2
I would like to find out how I can query the users table and get their top 3 categories either in 3 different fields (preferred) or as a group_concat(..) in one field
SELECT id, name, top_categories FROM users, (...) WHERE id IN ('1', '2', '3');
Any ideas how I should go about doing this?
Thanks!
First build a groped query that lists on distinct rows, the top three skills for each user. Then pivot that into to pull the three skills for eah user out to the right. You will need to use the Max(isnull(skill,'')) expression on the skills in each skill column.
It is very crude way of doing it in MYSQL to get top 3 records for each user
SELECT u.id, c.name
FROM
users u,
categories c,
(SELECT i.id,
i.user_id,
i.category_id,
#running:=if(#previous=i.user_id,#running,0) + 1 as rId,
#previous:=i.user_id
FROM
(SELECT * FROM intersect ORDER BY user_id) i JOIN
(SELECT #running=0, #previous=0 ) r) i
WHERE
u.id = i.USER_ID AND
i.CATEGORY_ID = c.id AND
i.rId <= 3
group by u.id, c.name ;
Hope it helps
FIDDLE