SQL - Get AVG value from other table - php

My question sounds really easy, but I'm stuck.
Sample Data:
Listing:
id title State
1 Hotel with nice view Arizona
2 Hotel to stay Arizona
Review:
id listing_id rating mail_approved
1 1 4(stars) 1
2 1 4(stars) 0
3 1 3(stars) 1
4 2 5(stars) 1
So now I get the AVG value of the listings, but I want to get only the value of each listing when the review is mail_approved = 1. But when there is none review or no review with mail_approved = 1 it should give me the listing back just with 0.0 review points. So I would like to get all listing back if they have a review just calculate the AVG of those reviews with mail_approved = 1
How can I do this?
Do I have to rewrite the whole query?
Here is my query:
SELECT
ls.id,
title,
state,
ROUND(AVG(rating),2) avg_rating
FROM listing ls
JOIN review rv
ON ls.id = rv.listing_id
WHERE ls.state = '$get_state'
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC

You used join, which is short for inner join. This type of join only gives results if a matching record exists in both tables. Change it to left join (short for left outer join), to also include listings without reviews.
You will need to move the state check and any other check to the join condition too, otherwise those listings without review will be dropped from the result again.
Lastly, you can coalesce the average value to get 0 instead of null for those records.
SELECT
ls.id,
title,
state,
COALESCE(ROUND(AVG(rating),2), 0) avg_rating
FROM listing ls
LEFT JOIN review rv
ON ls.id = rv.listing_id
AND ls.state = '$get_state'
AND ls.mail_approved = 1
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC
As a side note, please check prepared statements (for PDO or MySQLi) for the proper way to pass input parameters to your query instead of concatenating with variables like $get_state. Concatting is error prone, and makes you more vulnerable for SQL injection.

Outer join the avarage ratings to the hotels:
select
l.id,
l.title,
l.state,
coalesce(r.avg_rating, 0)
from listing l
left join
(
select
listing_id,
round(avg(rating), 2) as avg_rating
from review
where mail_approved = 1
group by listing_id
) r on r.listing_id = l.id
where l.state = '$get_state'
order by avg_rating desc;

Related

MySQL formatting results based on table name and multiple table data

I am coming into this project and have a little MySQL background to do basic SELECTs and INSERTs and whatnot. But, this is making me beat my head against the wall.
I have a typical user information table in MySQL:
USERS
+-------+----------+---------+-----+
|user_id|first_name|last_name|email|
+-------+----------+---------+-----+
1 tim jones tj#acme.com
2 sarah peteres sp#acme.com
3 larry doe ld#acme.com
Then I have multiple product tables:
PRODUCTS_ONE
+-------+-------+---------+----------+--------------+
|prod_id|user_id|prod_name|prod_width|prod_ship_date|
+-------+-------+---------+----------+--------------+
1 1 bowl nine 1-1-16
2 1 fork one 1-2-16
3 2 plate eleven 1-3-16
PRODUCTS_TWO
+-------+-------+----------+--------+--------------+
|prod_id|user_id|prod_state|prod_job|prod_ship_date|
+-------+-------+----------+--------+--------------+
1 3 maine min 1-1-16
2 2 texas max 1-2-16
3 1 ohio min 1-1-16
I have 15 total PRODUCT tables that all have prod_id, users_id, and prod_ship_date. The other fields might all be different based on what product table they are in. But, all the different PRODUCT tables have those three common fields.
What I am trying to accomplish is to get a list of USER info and PRODUCT info for products that match a certain ship date.
I want to find all the users and what product table and product id they are getting on a certain date.
So, if I searched on a ship date of 1-1-16, I would get something like:
+----------------+-----------+-------------+-----------+
|users.first_name|users.email|product_table|products_id|
+----------------+-----------+-------------+-----------+
tim tj#acme.com one,two 1,3
larry ld#acme.com one 3
If I searched on a ship date of 1-2-16, I would get something like:
+----------------+-----------+-------------+-----------+
|users.first_name|users.email|product_table|products_id|
+----------------+-----------+-------------+-----------+
tim tj#acme.com one 2
sarah sp#acme.com two 2
I hope this all makes sense. Unfortunately, I cannot change the structure or layout of the various product tables due to legacy issues.
I just can't figure out the MySQL statement to use to get something like this.
The above results will be used for reporting purposes.
you could write a query like this:
select first_name,email,prod_id,group_concat(product_table) as product_table from (
select u.user_id ,first_name,email,prod_id, 'one' as product_table from users u join products_one p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
union
select u.user_id,first_name,email,prod_id, 'two' as product_table from users u join products_two p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
) a
group by a.user_id
order by user_id,product_table
and so on.
With the second group concat and order by
select first_name,email,group_concat(prod_id),group_concat(product_table) as product_table from (
select u.user_id ,first_name,email,prod_id, 'one' as product_table from users u join products_one p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
union
select u.user_id,first_name,email,prod_id, 'two' as product_table from users u join products_two p on u.user_id = p.user_id where prod_ship_date = '2016-01-01 00:00:00'
) a
group by a.user_id
order by user_id,product_table
Check out this sqlfiddle

How to optimise this MySQL SQL query?

I'm working with the join plus union plus group by query, and I developed a query something like mentioned below:
SELECT *
FROM (
(SELECT countries_listing.id,
countries_listing.country,
1 AS is_country
FROM countries_listing
LEFT JOIN product_prices ON (product_prices.country_id = countries_listing.id)
WHERE countries_listing.status = 'Yes'
AND product_prices.product_id = '3521')
UNION
(SELECT countries_listing.id,
countries_listing.country,
0 AS is_country
FROM countries_listing
WHERE countries_listing.id NOT IN
(SELECT country_id
FROM product_prices
WHERE product_id='3521')
AND countries_listing.status='Yes')) AS partss
GROUP BY id
ORDER BY country
And I just realised that this query is taking a lot of time to load results, almost 8 seconds.
I was wondering if there is the possibility to optimize this query to the fastest one?
If I understand the logic correctly, you just want to add a flag for the country as to whether or not there is a price for a given product. I think you can use an exists clause to get what you want:
SELECT cl.id, cl.country,
(exists (SELECT 1
FROM product_prices pp
WHERE pp.country_id = cl.id AND
pp.product_id = '3521'
)
) as is_country
FROM countries_listing cl
WHERE cl.status = 'Yes'
ORDER BY country;
For performance, you want two indexes: countries_listing(status, country) and
product_prices(country_id, product_id)`.
Depending on how often it is executed, prepared statements could help. See PDO for more information.

Query to get the count of items in each category (to show even empty ones with 0 items)

I just wrote this query for my tables: NEWS and NEWS-CATEGORIES in order to count the items of each category:
SELECT DISTINCT CAT.cid, CAT.c_title, N.n_category, count(*) AS cat_count
FROM news N
inner join news - categories CAT
on CAT.cid = N.n_category
GROUP BY N.n_category
but the problem is that it just shows me the categories which contains news! but I wana get all of the categories even the ones with empty news...
my NEWS table is:
nid | n_category | etc
my NEWS-CATEGORY table is:
cid | c_title | etc
Thanks for your help
Regards
Try this:
SELECT
CAT.cid,
CAT.c_title,
count(N.n_category) AS cat_count
FROM `news-categories` CAT
LEFT JOIN `news` N
ON CAT.cid = N.n_category
GROUP BY CAT.cid,
CAT.c_title
Use LEFT JOIN:
SELECT CAT.cid, CAT.c_title, IFNULL(COUNT(N.n_category), 0) AS cat_count
FROM `news-categories` AS CAT
LEFT JOIN news AS N ON CAT.cid = N.n_category
GROUP BY CAT.cid
Things to note: 1) You have to use a column from news in the COUNT() expression, not COUNT(*), so that the null match is not counted. 2) There's no need to select N.n_category, since that's always equal to CAT.cid and you're already selecting that. 3) The GROUP BY column has to be from the news-categories table -- you can't group by a column in the table that may not have any matching rows, since that value will always be NULL.
I'm just going to point out that you can do this with a subquery as well:
SELECT CAT.cid, CAT.c_title,
(SELECT COUNT(*) FROM news N WHERE CAT.cid = N.n_category)
FROM `news - categories` CAT;
Under some circumstances, this can even have better performance.

MySQL Join and create new column value

I have an instrument list and teachers instrument list.
I would like to get a full instrument list with id and name.
Then check the teachers_instrument table for their instruments and if a specific teacher has the instrument add NULL or 1 value in a new column.
I can then take this to loop over some instrument checkboxes in Codeigniter, it just seems to make more sense to pull the data as I need it from the DB but am struggling to write the query.
teaching_instrument_list
- id
- instrument_name
teachers_instruments
- id
- teacher_id
- teacher_instrument_id
SELECT
a.instrument,
a.id
FROM
teaching_instrument_list a
LEFT JOIN
(
SELECT teachers_instruments.teacher_instrument_id
FROM teachers_instruments
WHERE teacher_id = 170
) b ON a.id = b.teacher_instrument_id
my query would look like this:
instrument name id value
--------------- -- -----
woodwinds 1 if the teacher has this instrument, set 1
brass 2 0
strings 3 1
One possible approach:
SELECT i.instrument_name, COUNT(ti.teacher_id) AS used_by
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
GROUP BY ti.teacher_instrument_id
ORDER BY i.id;
Here's SQL Fiddle (tables' naming is a bit different).
Explanation: with LEFT JOIN on instrument_id we'll get as many teacher_id values for each instrument as teachers using it are - or just a single NULL value, if none uses it. The next step is to use GROUP BY and COUNT() to, well, group the result set by instruments and count their users (excluding NULL-valued rows).
If what you want is to show all the instruments and some flag showing whether or now a teacher uses it, you need another LEFT JOIN:
SELECT i.instrument_name, NOT ISNULL(teacher_id) AS in_use
FROM teaching_instrument_list AS i
LEFT JOIN teachers_instruments AS ti
ON ti.teacher_instrument_id = i.id
AND ti.teacher_id = :teacher_id;
Demo.
Well this can be achieved like this
SELECT
id,
instrument_name,
if(ti.teacher_instrument_id IS NULL,0,1) as `Value`
from teaching_instrument_list as til
LEFT JOIN teachers_instruments as ti
on ti.teacher_instrument_id = til.id
Add a column and check for teacher_instrument_id. If found set Value to 1 else 0.

sql join not displaying correctly

I'm currently trying to join two tables with a left join:
--portal--
id_portal (index)
id_venue
name_portal
--access--
id_access (index)
id_event
id_portal
id_tickets
scan_access
'access' contains a number of ticket types per portal for each event. I need to combine these to get the sum total of the scan_access column for each portal but include the portals that have 'null' scan_access to come up with '0'. To achieve this I've used a left join:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
which means I get the following:
Portal 1 - Null
Portal 2 - 40
Portal 3 - 33
Portal 4 - Null
but I have an issue when I need to also get the above result when taking into account the event (id_event) because when I use the following:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId AND access.id_event = 20
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
I get:
Portal 2 - 40
Portal 3 - 33
which makes sense as those are the only two rows that have an id_event value. But how can I take this col into account without losing the other portals? also, is there a way in sql to make the 'null' a zero when returning a result? (I can fix the null after with php but wanted to see if it was possible)
By putting access.id_event = 20 in your WHERE clause, you turn your LEFT JOIN into an INNER JOIN. Move access.id_event = 20 into your join criteria to preserve your LEFT JOIN. As #echo_me mentioned, you can use COALESCE() to get rid of your zeroes. I'd put it around the SUM(), instead of inside.
SELECT portal.name_portal, COALESCE( SUM(access.scan_access), 0 ) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal AND access.id_event = 20
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
to convert NULL to 0 use this
COALESCE(col, 0)
in your example it will be
SUM(COALESCE(access.scan_access, 0)) AS total_scan

Categories