mysql left join get two queries - mysql

ok i have two tables, houses and pictures. what i would like to call is
for each house i would like to call number of pictures for that house and also minimum sort_order number for that photo.
i tried left join in different types, one returns me total photos which is fine ,but doesnt return the min sort order, the other one returns me min sort order but doesnt give me total photos.
my current code is
SELECT properties.p_id, properties.s_property_id, properties.a_property_id,
properties.p_advert_heading, properties.p_postcode, properties.p_price,
properties.p_bedrooms, properties.p_bathrooms, properties.p_status,
properties.p_priority, property_photos.photo_url, property_photos.sort_order, property_photos.photo_local,
COUNT(property_photos.p_id) AS tot_photos
FROM properties
LEFT JOIN property_photos on properties.p_id = property_photos.p_id
WHERE properties.is_archieved = 0 AND properties.p_status = 1 AND properties.a_id = 16
GROUP BY properties.p_id
ie:
house_table
----------
property_id
property_name
photo_table
-----------
photo_id
property_id
sort_order
so i need to find total number of pictures and also the id of the photo with minimum sort_order...

You can get both values most easily by using the substring_index()/group_concat() method to get the first photo_id:
select property_id, count(*) as numphotos,
substring_index(group_concat(photo_id order by sort_order), ',', 1) as min_photo_id
from photo_table pt
group by property_id;

You just need to use GROUP BY and aggregation functions:
SELECT
h.property_id,
h.property_name,
COUNT(p.photo_id) AS photo_count,
MIN(p.sort_order) AS min_sort
FROM house_table AS h
LEFT JOIN photo_table AS p
ON h.property_id = p.property_id
GROUP BY h.property_id

Related

Join tables with specific order

I've got two tables, for example: Teacher and Pupil and table LastViewedPupil with fields who watched him and when (teacherId & pupilId). So I want to return the list of Pupils that was ordered by last viewed date, but there are not all pupils inside LastViewedPupil, but last few for example, I want to show after that ordered by date all left records no matter in wich order, how can I do that?
I can do without last part like
select * from Pupil as p, (
select * from LastViewedPupil lvp where lvp.teacherId = 5 ORDER BY lastViewDate
) as lvp where lvp.pupilId = p.pupilId;
Or should I add corresponding records in LastViewDatePupil for all pupils or need to Join table itself (sounds awkward)?
You should try this one:
SELECT p.*
LEFT JOIN LastViewedPupil lvp ON p.id = lvp
WHERE lvp.teacher_id = 5
ORDER BY lvp.lastViewDate DESC
I'm not sure if that query puts NULL at the beginning or at the end. If that doesn't order the results properly, try this other. I used a CASE for reordering data
SELECT p.*,
CASE WHEN lvp.lvp.lastViewDate IS NULL THEN 1 ELSE 0, END AS notNullfirst FROM Pupil p
LEFT JOIN LastViewedPupil lvp ON p.id = lvp
WHERE lvp.teacher_id = 5
ORDER BY notNullfirst, lvp.lastViewDate DESC

COUNT evaluate to zero if no matching records

Take the following:
SELECT
Count(a.record_id) AS newrecruits
,a.studyrecord_id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
WHERE (((a.visit_type_id)=1))
GROUP BY a.studyrecord_id;
I want to amend the COUNT part to display a zero if there are no records since I assume COUNT will evaluate to Null.
I have tried the following but still get no results:
IIF(ISNULL(COUNT(a.record_id)),0,COUNT(a.record_id)) AS newrecruits
Is this an issue because the join is on record_id? I tried changing the INNER to LEFT but also received no results.
Q
How do I get the above to evaluate to zero if there are no records matching the criteria?
Edit:
To give a little detail to the reasoning.
The studies table contains a field called 'original_recruits' based on activity before use of the database.
The visits tables tracks new_recruits (Count of records for each study).
I combine these in another query (original_recruits + new_recruits)- If there have been no new recruits I still need to display the original_recruits so if there are no records I need it to evalulate to zero instead of null so the final sum still works.
It seems like you want to count records by StudyRecords.
If you need a count of zero when you have no records, you need to join to a table named StudyRecords.
Did you have one? Else this is a nonsense to ask for rows when you don't have rows!
Let's suppose the StudyRecords exists, then the query should look like something like this :
SELECT
Count(a.record_id) AS newrecruits -- a.record_id will be null if there is zero count for a studyrecord, else will contain the id
sr.Id
FROM
visits AS a
INNER JOIN
(
SELECT
record_id
, MAX(modtime) AS latest
FROM
visits
GROUP BY
record_id
) AS b
ON (a.record_id = b.record_id) AND (a.modtime = b.latest)
LEFT OUTER JOIN studyrecord sr
ON sr.Id = a.studyrecord_id
WHERE a.visit_type_id = 1
GROUP BY sr.Id
I solved the problem by amending the final query where I display the result of combining the original and new recruits to include the IIF there.
SELECT
a.*
, IIF(IsNull([totalrecruits]),consents,totalrecruits)/a.target AS prog
, IIf(IsNull([totalrecruits]),consents,totalrecruits) AS trecruits
FROM
q_latest_studies AS a
LEFT JOIN q_totalrecruitment AS b
ON a.studyrecord_id=b.studyrecord_id
;

Comparing two values from the same select query

I have a select query which selects all products from my inventory table and joins them with two other tables (tables l_products and a_products)
SELECT
i.*,
b.title,
ROUND((i.price/100*80) - l.price,2) AS margin,
l.price AS l_price,
a.price AS a_price,
ROUND((a.price/100*80) - l.price, 2) AS l_margin
FROM inventory i
LEFT JOIN products b ON i.id = b.id
LEFT JOIN a_products a ON i.id = a.id
LEFT JOIN l_products l ON i.id = l.id
WHERE
a.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
AND l.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
This select query will normally give me a table such as...
id, title, condition, margin, l_price, a_price ...
001-new ... new 10 20 10
001-used ... used 10 25 20
002....
Now I need a condition in the query which will ignore all used products that are more expensive (have a higher a_price) than their 'new' counterparts, such as in the example above you can see that 001-used has a higher a_price than 001-new.
How can I achieve this with out having to resolve to using php
FULL JOIN this query with it self on a column which has a uniquely same value for each id prefix.
You may achieve this effect by adding another field to your SELECT call which produces same unique value for 001-new and 001-used, 002-new and 002-used...
Such value generation can be done by defining your own SQL Routine to extract first 3 characters from a column.

Multiple GROUP_CONCAT on different fields using MySQL

I have a query like this:
SELECT product.id,
GROUP_CONCAT(image.id) AS images_id,
GROUP_CONCAT(image.title) AS images_title,
GROUP_CONCAT(facet.id) AS facets_id
...
GROUP BY product.id
And the query works, but not as expected, because if I have a product with 5 facets and 1 image (suppose an image with id=7), then I get something like this in "images_id":
"7,7,7,7,7"
If I have 2 images (7 and 3) then I get something like:
"7,7,7,7,7,3,3,3,3,3"
and in facets I get something like:
"8,7,6,5,4,8,7,6,5,4"
I think MySQL is making some type of union of the differents rows returned by the query, and then concatenating everything.
My expected result is (for the last example):
images_id = "7,3"
facets_id = "8,7,6,5,4"
I can obtain that using DISTINCT in the GROUP_CONCAT, but then I have another problem:
If I have two images with the same title, one of them is ommited, and then I get something like:
images_id = "7,3,5"
images_title = "Title7and3,Title5"
So I miss the relation between images_id and images_title.
Does somebody know if it's possible to make this query in MySQL?
Maybe I'm complicating everything without any real benefits.
I'm trying to execute only one query because performance, but now I'm not so sure if it's even faster to execute two queries (one for selecting the facets and another for the images for example).
Please explain what do you think is the best solution for this and why.
Thanks !
Just add DISTINCT.
Example:
GROUP_CONCAT(DISTINCT image.id) AS images_id
You'll need to get each group separately:
SELECT
p.id,
images_id,
images_title,
facets_id,
...
FROM PRODUCT p
JOIN (SELECT product.id, GROUP_CONCAT(image.id) AS images_id
FROM PRODUCT GROUP BY product.id) a on a.id = p.id
JOIN (SELECT product.id, GROUP_CONCAT(image.title) AS images_title
FROM PRODUCT GROUP BY product.id) b on b.id = p.id
JOIN (SELECT product.id, GROUP_CONCAT(facet.id) AS facets_id
FROM PRODUCT GROUP BY product.id) b on c.id = p.id
...
You can add just the DISTINCT keyword, you'll get your desire results.
SELECT tb_mod.*, tb_van.*,
GROUP_CONCAT(DISTINCT tb_voil.vt_id) AS voil,
GROUP_CONCAT(DISTINCT tb_other.oa_id) AS other,
GROUP_CONCAT(DISTINCT tb_ref.rp_id) AS referral
FROM cp_modules_record_tbl tb_mod
LEFT JOIN cp_vane_police_tbl tb_van ON tb_van.mr_id= tb_mod.id
LEFT JOIN cp_mod_voilt_tbl tb_voil ON tb_voil.mr_id= tb_mod.id
LEFT JOIN cp_mod_otheraction_tbl tb_other ON tb_other.mr_id= tb_mod.id
LEFT JOIN cp_mod_referral_tbl tb_ref ON tb_ref.mr_id= tb_mod.id
WHERE tb_mod.mod_type = 2 GROUP BY tb_mod.id
If the issue is speed, then it may be a lot faster to simply select all the data you need as separate rows, and do the grouping in the application, i.e.:
SELECT product.id, image.id, image.title, facet.id
Then in the application:
foreach row:
push product_id onto list_of_product_ids
push image_id onto list_of_image_ids
etc.

Top N Per Group with Multiple Table Joins

Based on my research, this is a very common problem which generally has a fairly simple solution. My task is to alter several queries from get all results into get top 3 per group. At first this was going well and I used several recommendations and answers from this site to achieve this (Most Viewed Products). However, I'm running into difficulty with my last one "Best Selling Products" because of multiple joins.
Basically, I need to get all products in order by # highest sales per product in which the maximum products per vendor is 3 I've got multiple tables being joined to create the original query, and each time I attempt to use the variables to generate rankings it produces invalid results. The following should help better understand the issue (I've removed unnecessary fields for brevity):
Product Table
productid | vendorid | approved | active | deleted
Vendor Table
vendorid | approved | active | deleted
Order Table
orderid | `status` | deleted
Order Items Table
orderitemid | orderid | productid | price
Now, my original query to get all results is as follows:
SELECT COUNT(oi.price) AS `NumSales`,
p.productid,
p.vendorid
FROM products p
INNER JOIN vendors v ON (p.vendorid = v.vendorid)
INNER JOIN orders_items oi ON (p.productid = oi.productid)
INNER JOIN orders o ON (oi.orderid = o.orderid)
WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0)
AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0)
AND o.`Status` = 'SETTLED'
AND o.Deleted = 0
GROUP BY oi.productid
ORDER BY COUNT(oi.price) DESC
LIMIT 100;
Finally, (and here's where I'm stumped), I'm trying to alter the above statement such that I received only the top 3 product (by # sold) per vendor. I'd add what I have so far, but I'm embarrassed to do so and this question is already a wall of text. I've tried variables but keep getting invalid results. Any help would be greatly appreciated.
Even though you specify LIMIT 100, this type of query will require a full scan and table to be built up, then every record inspected and row numbered before finally filtering for the 100 that you want to display.
select
vendorid, productid, NumSales
from
(
select
vendorid, productid, NumSales,
#r := IF(#g=vendorid,#r+1,1) RowNum,
#g := vendorid
from (select #g:=null) initvars
CROSS JOIN
(
SELECT COUNT(oi.price) AS NumSales,
p.productid,
p.vendorid
FROM products p
INNER JOIN vendors v ON (p.vendorid = v.vendorid)
INNER JOIN orders_items oi ON (p.productid = oi.productid)
INNER JOIN orders o ON (oi.orderid = o.orderid)
WHERE (p.Approved = 1 AND p.Active = 1 AND p.Deleted = 0)
AND (v.Approved = 1 AND v.Active = 1 AND v.Deleted = 0)
AND o.`Status` = 'SETTLED'
AND o.Deleted = 0
GROUP BY p.vendorid, p.productid
ORDER BY p.vendorid, NumSales DESC
) T
) U
WHERE RowNum <= 3
ORDER BY NumSales DESC
LIMIT 100;
The approach here is
Group by to get NumSales
Use variables to row number the sales per vendor/product
Filter the numbered dataset to allow for a max of 3 per vendor
Order the remaining by NumSales DESC and return only 100
I like this elegant solution, however when I run an adapted but similar query on my dev machine I get a non-deterministic result-set returned. I believe this is due to the way the MySql optimiser deals with assigning and reading user variables within the same statement.
From the docs:
As a general rule, you should never assign a value to a user variable and read the value within the same statement. You might get the results you expect, but this is not guaranteed. The order of evaluation for expressions involving user variables is undefined and may change based on the elements contained within a given statement; in addition, this order is not guaranteed to be the same between releases of the MySQL Server.
Just adding this note here in case someone else comes across this weird behaviour.
The answer given by #RichardTheKiwi worked great and got me 99% of the way there! I am using MySQL and was only getting the first row of each group marked with a row number, while the rest of the rows remained NULL. This resulted in the query returning only the top hit for each group rather than the first three rows. To fix this, I had to initialize #r in the initvars subquery. I changed,
from (select #g:=null) initvars
to
from (select #g:=null, #r:=null) initvars
You could also initialize #r to 0 and it would work the same. And for those less familiar with this type of syntax, the additional section is reading through each sorted group and if a row has the same vendorid as the previous row, which is tracked with the #g variable, it increments the row number, which is stored in the variable #r. When this process reaches the next group with a new vendorid, the IF statement will no longer evaluate as true and the #r variable (and thereby the RowNum) will be reset to 1.