Conditional field in mysql join query result - mysql

I have following two tables
1. publisher_site_regionwise_adratio
publisher_id | site | region | ad_ratio | product_code
=========================================================
001 | xyz.com | US | 8:2 | TB
and
2. publisher_site_regionwise_info
publisher_id | site | region | regional_keywords
=======================================================
001 | xyz.com | US | business, warehouse
Now second table has data that is not product_code wise. Regional info for sites is irrespective of product_code for a publisher with a particular site for a particular region. Now I want a query that will give me following fields
site | region | ad_ratio | has_regional_info
============================================
xyz.com | US | 8:2 | 1
has_regional_info column will have 0 or 1 depending upon whether a site in a particular region has regional_keywords mapped or not.
I can't possibly imagine how I can use JOIN and get such result. Any help will be very appreciated.

I would take a simple JOIN into your info table, then check if there is data in the regional_keywords column to determine your 1 or 0 for has_regional_info
SELECT ar.site, ar.region, ar.ad_ratio
, CASE
WHEN i.regional_keywords IS NOT NULL THEN 1
ELSE 0
END AS has_regional_info
FROM publisher_site_regionwise_adratio AS ar
LEFT JOIN publisher_site_regionwise_info AS i ON ar.publisherId = i.publisherId
If your foreign key between publisher_site_regionwise_adratio and publisher_site_regionwise_info is more than just publisherId (difficult to tell with 3 repeated columns in your example) then just add these to the ON statement of the join like: AND ar.otherColumn = i.otherColumn

SELECT ratio.site, ratio.region, ratio.ad_ration,
IF(info.has_regional_info IS NULL, true, false)
FROM publisher_site_regionwise_adratio AS ratio
LEFT JOIN publisher_site_regionwise_info AS info
ON ratio.publisher_id = info.publisher_id

select publisher_site_regionwise_adratio.site,
publisher_site_regionwise_adratio.region,
publisher_site_regionwise_adratio.ad_ratio,
LENGTH(regional_keywords)>0 as has_regional_info
from publisher_site_regionwise_adratio
join publisher_site_regionwise_info.publisher_id
on publisher_site_regionwise_adratio.publisher_id
= publisher_site_regionwise_info.publisher_id

This should do:
SELECT a.publisher_id, a.site, a.ad_ratio,
i.regional_keywords IS NOT NULL AS has_regional_keywords
FROM publisher_site_regionwise_adratio a, publisher_site_regionwise_info i
WHERE a.publisher_id = i.publisher_id;

Related

MySQL: Get all rows from a table only once and add columns from another table

My tables:
test_people: test_things: test_people_things:
id|name id|name id|thing_id|people_id|rating
1|alice 1|hammer 1| 1 | 1 | 3
2|bob 2|table 2| 1 | 2 | 1
3|eve 3|chair 3| 2 | 1 | 0
4|glass 4| 3 | 2 | 2
What I want: Get all rows from test_things (but each row only once!) with the rating a given person applied to that thing or NULL, if no rating has been applied by that person so far. E.g. for people.id = 1 ("alice"), I'd expect this result:
thing.name|people.name|rating
hammer | alice | 3
table | alice | 0
chair | NULL | NULL
glass | NULL | NULL
This is my best try so far:
SELECT
test_things.name,
test_people.name,
test_people_things.rating
FROM
test_things LEFT JOIN test_people_things ON test_things.id = test_people_things.thing_id
LEFT JOIN test_people ON test_people_things.people_id = test_people.id
AND test_people.id = 1
The result is:
thing.name|people.name|rating
hammer | alice | 3
table | alice | 0
hammer | NULL | 1
chair | NULL | 2
glass | NULL | NULL
Here's the sqlfiddle with the test data.
Your method of eliminating people is the problem. You need to do it before you do the 2nd join.
fiddle
SELECT
test_things.name,
test_people.name,
test_people_things.rating
FROM test_things
LEFT JOIN test_people_things ON test_things.id = test_people_things.thing_id
AND test_people_things.people_id = 1
LEFT JOIN test_people ON test_people_things.people_id = test_people.id
Why? Since you only care about Alice, you shouldn't even try to get the names for the others. and the cardinalty between things and people things is causing your records to repeat when you only want 1 per person. your results are including the hammer for BOB but just not showing his name. To prove that just include the people_ID from test_people_things. So reducing your set to Just the "people" you want in people things solves the problem.
We can't put it on the Where cause or the "Left joins" get negated.
Now I think this still has a problem if you allow a thing and people thing to have more than 1 record in test_people_things. However I assume you have a PK Constraint on Things_ID and People_ID on the test_People_things Table. Put another way, alice can't have a rating for 2 hammers or this will not work; and we will need a way o determine which rating you'd want.
There is one word to change in your query. You need to change the first join to RIGHT JOIN;
SELECT
tt.name,
tp.name,
tpt.rating
FROM
test_things tt
RIGHT JOIN test_people_things tpt ON tt.id = tpt.thing_id
LEFT JOIN test_people tp ON tpt.people_id = tp.id
AND tp.id = 1
returns
name name rating
hammer alice 3
table alice 0
hammer (null) 1
chair (null) 2

Retrieve data from tables via JOINS

I have three tables: sessions, urls and visitors.
I need to join these three tables in such a way that I should be able to get data from each table and the maximum number of rows returned should be equal to sessions.
Following is the basic schema of my tables.
Table sessions
session_id | url_id | referrer_id | country
-------------------------------------------
1234 | a1b1 | bb11 | US
4567 | x1y1 | ll33 | IN
6789 | a1b1 | ff99 | UK
Table urls
id | url |
-----------------------------------------
a1b1 | https://url-1.com |
x1y1 | https://url-2.com |
bb11 | https://referrer-url-1.com |
ll33 | https://referrer-url-2.com |
ff99 | https://referrer-url-3.com |
Table visitors
id | session_id | visiting_time |
-----------------------------------------
1 | 1234 | 447383930 |
2 | 4567 | 547383930 |
3 | 6789 | 647383930 |
What I want as the final output should look like:
session_id | visiting_time | url | referrer_url | country
------------------------------------------------------------------------------------------
1234 | 447383930 | https://url-1.com | https://referrer-url-1.com | US |
4567 | 547383930 | https://url-2.com | https://referrer-url-2.com | IN |
6789 | 647383930 | https://url-1.com | https://referrer-url-3.com | UK |
I want to map url_id in sessions table with id in urls table and get the corresponding url from urls table and have the value in the new column named url. Similarly, map referrer_id in sessions table with id in urls table and get the corresponding url from urls table and have the value in the new column named referring_url.
As you can see: JOINS with sessions and visitors is simple and can be simply done via:
select session_id, visiting_time, country
from sessions,
visitors
where sessions.session_id = visitors.session_id;
But joining with urls table and getting the url and referring_url is somewhat tricky. I have tried LEFT JOIN and INNER JOIN but couldn't make it work.
Any help with query or references would be helpful.
Thanks!
You should avoid using comma based Implicit joins and use Explicit Join based syntax
You will need two joins with urls table; one to fetch the url and another for referrer_url.
Try the following:
SELECT s.session_id,
v.visiting_time,
u1.url,
u2.url AS referrer_url,
s.country
FROM sessions AS s
JOIN visitors AS v ON v.session_id = s.session_id
JOIN urls AS u1 ON u1.id = s.url_id
JOIN urls AS u2 ON u2.id = s.referrer_id
select sessions.session_id, visitors.visiting_time, urls.url, urlsReferrer.url referrer_url, sessions.country
from sessions
inner join visitors on sessions.session_id = visitors.session_id
inner join urls on sessions.url_id = url.id
left join urls urlsReferrer on sessions.referrer_id = urlsReferrer.id
You should use a join on urls twice one of url_id and one for referrer_id
select session_id
, visiting_time
, u1.url
, u2.url
, country
from sessions
INNER JOIN visitors ON sessions.session_id = visitors.session_id
INNER JOIN urls u1 on u1.id= sessions.url_id
INNER JOIN urls u2 on u2.id= sessions.referrer_id
In this way you can join the sessions for retrive both then values you need
Joins are defined in the from statement - please read up on https://www.w3schools.com/sql/default.asp to better get a sense of join usage.
Modify the query as needed based on which table "referrer_url" actually comes from
Warning: You must include a where statement which limits your result. I strongly suggest defining a date field and range to prevent you from initiating a long running query and affecting database performance.
see below for query
select
s.session_id,
v.visiting_time,
s.country,
u.url,
u.referrer_url
from
sessions s
join visitors v on session_id
join urls on u.id=s.url_id
;

Left Join takes very long time on 150 000 rows

I am having some difficulties to accomplish a task.
Here is some data from orders table:
+----+---------+
| id | bill_id |
+----+---------+
| 3 | 1 |
| 9 | 3 |
| 10 | 4 |
| 15 | 6 |
+----+---------+
And here is some data from a bills table:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
+----+
I want to list all the bills that have no order associated with.
In order to achieve that, I thought that the use of LEFT JOIN was appropriated so I wrote this request:
SELECT * FROM bills
LEFT JOIN orders
ON bills.id = orders.bill_id
WHERE orders.bill_id IS NULL;
I thought that I would have the following result:
+----------+-----------+----------------+
| bills.id | orders.id | orders.bill_id |
+----------+-----------+----------------+
| 2 | NULL | NULL |
| 5 | NULL | NULL |
+----------+-----------+----------------+
But I can't reach the end of the request, it has run more than 5 minutes without result, I stopped the request because this can't be a production time anyway.
My real dataset has more than 150 000 orders and 100 000 bills. Is the dataset too big?
Is my request wrong somewhere?
Thank you very much for your tips!
EDIT: side note, the tables have no foreign keys defined... *flies away*
Your query is fine. I would use table aliases in writing it:
SELECT b.*
FROM bills b LEFT JOIN
orders o
ON b.id = o.bill_id
WHERE o.bill_id IS NULL;
You don't need the NULL columns from orders, probably.
You need an index on orders(bill_id):
create index idx_orders_billid on orders(bill_id);
By your where statement, I assume your looking for orders that have no bills.
If that's the case you don't need to do a join to the bills table as they would by definition not exist.
You will find
SELECT * FROM orders
WHERE orders.bill_id IS NULL;
A much better performing query.
Edit:
Sorry I missed your "I want to list all the bills that have no order associated with." when reading the question. As #gordon pointed out an index would certainly help. However if changing the scheme is feasible I would rather have a nullable bill.order_id column instead of a order.bill_id because you won't need a left join, an inner join would suffice to get order bills as it would be a quicker query for your other assumed requirements.

MySQL Intermediate-Level Table Relationship

Each row in Table_1 needs to have a relationship with one or more rows that might come from any number of other tables in the database (Table_X). So I set up an intermediate table (Table_2) where each row contains an id from Table_1, and the id from Table_X. It also has its own auto increment id since none of the relationships will be exclusive and therefore both the other ids will not be unique in the table.
My problem now is that when I retrieve the row from Table_1 and would like to see the information from each related row from Table_X, I don't know how to get it. At first I thought I could create a column for the exact name of Table_X for each row in Table_2 and have a second SELECT statement using that information, but I've been seeing inklings about things such as foreign keys and join statements that I think I need to get into. I'm just having trouble sorting it all out. Do I even need Table_2?
This probably isn't overly complicated, but I'm just getting into MySQL and this is the first real challenge I've encountered.
Edit to include requested information: If I understand correctly, I think I'm dealing with a many to many relationship. Table_3 has games; Table_1 has articles. An article can be about multiple games, and a game can also have multiple articles written about it. The only other possibly pertinent information I can see is that when a new article is made, every game that will be related to it is decided all at once. But the list of articles related to a given game can grow over time as more articles are written. That's probably not especially important, however.
If I understood correctly You are talking about one to many relationship in database (for example: one person can have multiple phone numbers), You can store data in two separate tables persons and phones.
Persons:
|person_id|person_name |person_age |
| 1 | Bodan Kustan| 28 |
Phones:
|phone_id |person_id |phone_number|
| 1 | 1 | 31337 |
| 2 | 1 | 370 |
Then you can execute query with Join:
SELLECT * FROM `persons`
LEFT JOIN `phones` ON `persons`.`person_id` = `phones`.`person_id`
WHERE `persons`.`person_id` = 1;
And it will return to You list of persons with phone numbers:
|person_id|person_name |person_age |phone_id |person_id |phone_number|
| 1 | Bodan Kustan| 28 | 1 | 1 | 31337 |
| 1 | Bodan Kustan| 28 | 2 | 1 | 370 |
Another possibility is Many to Many relationship (for example: Any person can love pizza, and pizza is not unique for that person), then You need third table to join tables together person_food
Persons:
|person_id|person_name |person_age |
| 1 | Bodan Kustan| 28 |
Food:
|food_id |food_name |
| 1 | meat |
| 2 | pizza |
Person_Food
|person_id |food_id |
| 1 | 2 |
Then you can execute query with Join:
SELLECT * FROM `persons`
LEFT JOIN `person_food` ON `person`.`person_id` = `person_food`.`person_id`
LEFT JOIN `food` ON `food`.`food_id` = `person_food`.`food_id`
WHERE `persons`.`person_id` = 1;
And it will return data from all tables:
|person_id|person_name |person_age |person_id |food_id |food_name |
| 1 | Bodan Kustan| 28 | 1 | 2 | pizza |
However sometimes you need to join n amount of tables to join, then You could use separate table to hold information about relation. My approach (I don't think it's the best) would be to store table name next to relation (for example split mobile phones and home phones into two separate tables):
Persons:
|person_id|person_name |person_age |
| 1 | Bodan Kustan| 28 |
Mobile_Phone:
|mobile_phone_id |mobile_phone_number |
| 1 | 31337 |
Home_Phone:
|home_phone_id |home_phone_number |
| 1 | 370 |
Person_Phone:
|person_id |related_id |related_column |related_table |
| 1 | 1 | mobile_phone_id | mobile_phone |
| 1 | 1 | home_phone_id | home_phone |
Then query middle table to get all relations:
SELECT * FROM person_phone WHERE person_id = 1
Then build dynamic query (pseudo code, not tested -- might not work):
foreach (results as result)
append_to_final_sql = "LEFT JOIN {related_table}
ON {related_table}.{related_column} = `person_phone`.`related_id`
AND `person_phone`.`related_table` = {related_table}"
final_sql = "SELECT * FROM `persons` "
+ append_to_final_sql +
" WHERE `persons`.`person_id` = 1"
So Your final SQL would be:
SELECT * FROM `persons`
LEFT JOIN `person_phone` ON `person_phone`.`person_id` = `person`.`person_id`
LEFT JOIN `mobile_phone` ON `mobile_phone`.`mobile_phone_id` = `person_phone`.`related_id` AND `person_phone`.`related_table` = 'mobile_phone'
LEFT JOIN `home_phone` ON `home_phone`.`home_phone_id` = `person_phone`.`related_id` AND `person_phone`.`related_table` = 'home_phone'
You only need Table2 if entries in Table_x can be related to multiple rows in Table1 - otherwise a simple key for Table1 will suffice.
Look into joins - very powerful, flexible and fast.
select * from Table1 left join Table2 on Table1_id = Table2_table_1_id
left join Table_X on Tablex_id = Table2_table_x_id
Look at the output and you'll see that it returns all table_x rows with copies of the Table1 and Table2 fields.

How to write a proper If...Else Statement with JOIN in MySQL?

I'm quite a beginner in MySQL I just know the totally basic statements, however now I'ts time for me to get into some more difficult, but worth stuff.
I actually have 3 tables in MySQL, here is the representation:
users:
user_id | name | country
---------------------------
1 | Joseph | US
2 | Kennedy | US
3 | Dale | UK
admins:
admin_id | name | country
----------------------------
1 | David | UK
2 | Ryan | US
3 | Paul | UK
notes:
id | n_id | note | comment | country | type | manager
----------------------------------------------------------------
1 | 3 | This is the 1st note | First | US | admin | 2
2 | 2 | This is the 2nd note | Second | US | user | 1
3 | 2 | This is the 3rd note | Third | UK | user | 2
Now I would like to execute something like this SQL (I'm going to type not real commands here, because I'm not really familiar with all of the SQL expressions):
IF notes.type = admin
THEN
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM notes, admins
WHERE notes.n_id = admin.admin_id
ELSEIF notes.type = 'user'
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM notes, users
WHERE notes.n_id = users.user_id
I hope you understand what would I like to achieve here. I could do this easily with more SQL statements, but I would like to try some query which doesn't use that much resources.
Edit 1:
I would like to Get all of the Notes and get which usergroup has submitted it than apply the user's name to it. I mean, if the admin submitted the note, than SQL should choose the ID from the Admin table (as per the type value) but if a User submitted the note, it should get the name from the Users table.
The result should look something similar to this:
result:
------
id | note | comment | country | name
--------------------------------------------------------
1 | This is the 1st note | First | US | Paul
2 | This is the 2nd note | Second | US | Kennedy
3 | This is the 3rd note | Third | UK | Kennedy
Edit 2:
I have actually forgot to mention, that all of these should be listed to a manager. So a 'manager ID' should be added to the Notes and list all of the notes where the manager is for example: 2.
Here is a method that you can do in one query:
SELECT n.note, n.comment, n.country,
coalesce(a.name, u.name) as name, coalesce(a.country, u.country) as country
FROM notes n left join
admins a
on n.n_id = a.admin_id and n.type = 'admin' left join
users u
on n.n_id = u.user_id and n.type = 'user';
This uses left join to bring the records together from both tables. It then chooses the matching record for the select.
To select a particular manager, remove the semicolon and add:
where n.manager = 2;
If you expect admins and users in one result you have got several options. The simplest way is to make a union select like this:
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM
notes join admins on notes.n_id = admin.admin_id
WHERE
notes.manager = 2
UNION ALL
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM
notes join users on notes.n_id = users.user_id
WHERE
notes.manager = 2