Finding the right count syntax in mySQL select statement - php

Goal:
To have a table of SIC codes with their descriptions (joined via another table) and count the number of instances of each DISTINCT SIC code used. Ideal outcome below...followed by current mySQL select statement:
SIC Code | Description | Count of SIC Code
0001 | Desc A | 10
0002 | Desc B | 50
000N | Desc N | 80
Current SQL Statement, but causing error notice:
$sql = "SELECT DISTINCT company_list.SIC_Codes, sic_codes.SIC_desc, COUNT(*) as sicCount, FROM company_list LEFT JOIN sic_codes ON company_list.SIC_Codes = sic_codes.SIC_code;";
Any ideas or perspective on which count syntax to use to obtain the example above?
Thank you.

Try making a subquery for COUNT something like this.
$sql = "SELECT DISTINCT company_list.SIC_Codes, sic_codes.SIC_desc, (SELECT COUNT(*) FROM company_list LEFT JOIN sic_codes ON company_list.SIC_Codes = sic_codes.SIC_code) as sicCount, FROM company_list LEFT JOIN sic_codes ON company_list.SIC_Codes = sic_codes.SIC_code";
Instead of putting the COUNT as field, try making a subquery for it.
Change COUNT(*) to (SELECT COUNT(*) FROM company_list LEFT JOIN sic_codes ON company_list.SIC_Codes = sic_codes.SIC_code)

Related

PHP - Get only single image from many inside while loop [duplicate]

I read many threads about getting only the first row of a left join, but, for some reason, this does not work for me.
Here is my structure (simplified of course)
Feeds
id | title | content
----------------------
1 | Feed 1 | ...
Artists
artist_id | artist_name
-----------------------
1 | Artist 1
2 | Artist 2
feeds_artists
rel_id | artist_id | feed_id
----------------------------
1 | 1 | 1
2 | 2 | 1
...
Now i want to get the articles and join only the first Artist and I thought of something like this:
SELECT *
FROM feeds
LEFT JOIN feeds_artists ON wp_feeds.id = (
SELECT feeds_artists.feed_id FROM feeds_artists
WHERE feeds_artists.feed_id = feeds.id
LIMIT 1
)
WHERE feeds.id = '13815'
just to get only the first row of the feeds_artists, but already this does not work.
I can not use TOP because of my database and I can't group the results by feeds_artists.artist_id as i need to sort them by date (I got results by grouping them this way, but the results where not the newest)
Tried something with OUTER APPLY as well - no success as well.
To be honest i can not really imagine whats going on in those rows - probably the biggest reason why i cant get this to work.
SOLUTION:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'
If you can assume that artist IDs increment over time, then the MIN(artist_id) will be the earliest.
So try something like this (untested...)
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT
MIN(fa.artist_id) a_id
FROM feeds_artists fa
WHERE fa.feed_id = f.feed_id
) a
Version without subselect:
SELECT f.title,
f.content,
MIN(a.artist_name) artist_name
FROM feeds f
LEFT JOIN feeds_artists fa ON fa.feed_id = f.id
LEFT JOIN artists a ON fa.artist_id = a.artist_id
GROUP BY f.id
#Matt Dodges answer put me on the right track. Thanks again for all the answers, which helped a lot of guys in the mean time. Got it working like this:
SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
SELECT artist_id
FROM feeds_artists fa
WHERE fa.feed_id = f.id
LIMIT 1
)
WHERE f.id = '13815'
based on several answers here, i found something that worked for me and i wanted to generalize and explain what's going on.
convert:
LEFT JOIN table2 t2 ON (t2.thing = t1.thing)
to:
LEFT JOIN table2 t2 ON (t2.p_key = (SELECT MIN(t2_.p_key)
FROM table2 t2_ WHERE (t2_.thing = t1.thing) LIMIT 1))
the condition that connects t1 and t2 is moved from the ON and into the inner query WHERE. the MIN(primary key) or LIMIT 1 makes sure that only 1 row is returned by the inner query.
after selecting one specific row we need to tell the ON which row it is. that's why the ON is comparing the primary key of the joined tabled.
you can play with the inner query (i.e. order+limit) but it must return one primary key of the desired row that will tell the ON the exact row to join.
Update - for MySQL 5.7+
another option relevant to MySQL 5.7+ is to use ANY_VALUE+GROUP BY. it will select an artist name that is not necessarily the first one.
SELECT feeds.*,ANY_VALUE(feeds_artists.name) artist_name
FROM feeds
LEFT JOIN feeds_artists ON feeds.id = feeds_artists.feed_id
GROUP BY feeds.id
more info about ANY_VALUE: https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
I've used something else (I think better...) and want to share it:
I created a VIEW that has a "group" clause
CREATE VIEW vCountries AS SELECT * PROVINCES GROUP BY country_code
SELECT * FROM client INNER JOIN vCountries on client_province = province_id
I want to say yet, that I think that we need to do this solution BECAUSE WE DID SOMETHING WRONG IN THE ANALYSIS... at least in my case... but sometimes it's cheaper to do this that to redesign everything...
I hope it helps!
Here is my answer using the group by clause.
SELECT *
FROM feeds f
LEFT JOIN
(
SELECT artist_id, feed_id
FROM feeds_artists
GROUP BY artist_id, feed_id
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id
I want to give a more generalized answer. One that will handle any case when you want to select only the first item in a LEFT JOIN.
You can use a subquery that GROUP_CONCATS what you want (sorted, too!), then just split the GROUP_CONCAT'd result and take only its first item, like so...
LEFT JOIN Person ON Person.id = (
SELECT SUBSTRING_INDEX(
GROUP_CONCAT(FirstName ORDER BY FirstName DESC SEPARATOR "_" ), '_', 1)
) FROM Person
);
Since we have DESC as our ORDER BY option, this will return a Person id for someone like "Zack". If we wanted someone with the name like "Andy", we would change ORDER BY FirstName DESC to ORDER BY FirstName ASC.
This is nimble, as this places the power of ordering totally within your hands. But, after much testing, it will not scale well in a situation with lots of users and lots of data.
It is, however, useful in running data-intensive reports for admin.
For some database like DB2 and PostgreSQL, you have to use the key word LATERAL for specifying a sub query in the LEFT JOIN : (here, it's for DB2)
SELECT f.*, a.*
FROM feeds f
LEFT JOIN LATERAL
(
SELECT artist_id, feed_id
FROM feeds_artists sfa
WHERE sfa.feed_id = f.id
fetch first 1 rows only
) fa ON fa.feed_id = f.id
LEFT JOIN artists a ON a.artist_id = fa.artist_id
I know this is not a direct solution but as I've faced this and it's always a huge problem for me, and also using left join select etc. sometimes lead to a heavy process cost in database and server, I prefer doing this kind of left joins using array in php like this:
First get the data in range from second table and while you need just one row from second table, just save them with left join in-common column as key in result array.
SQL1:
$sql = SELECT artist_id FROM feeds_artists fa WHERE fa.feed_id {...RANGE...}
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$join_data[...$KEY...] = $row['artist_id'];
}
Then, get the base data and add detail of left join table from previous array while fetch them like this:
SQL2:
$sql = SELECT * FROM feeds f WHERE f.id {...RANGE...};
$res = $mysqli->query($sql);
if ($res->num_rows > 0) {
while ($row = $res->fetch_assoc()) {
$key = $row[in_common_col_value];
$row['EXTRA_DATA'] = $join_data[$key];
$final_data[] = $row;
}
Now, you'll have a $final_data array with desire extra data from $join_data array. this usually works good for date range data and like this.

Merge 2 MySQL tables

I have 2 tables with the following structure:
Custom Pricing List
+------------+--------------+------------ +
| stock_code | stored_price | pricing_ref |
+------------+--------------+------------ +
Generic Pricing List
+------------+---------------------+--------------+-------------+--------------+
| stock_code | last_purchase_price | sales_price | description | qty_in_stock |
+------------+---------------------+--------------+-------------+--------------+
What I would like to return is the following:
+------------+---------------------+--------------+--------------+-------------+
| stock_code | last_purchase_price | sales_price | description | qty_in_stock |
+------------+---------------------+--------------+--------------+-------------+
Currently I am only querying the last table, using the following MySQL statement:
SELECT * FROM $table WHERE $column LIKE '%$term%' $or LIMIT 0,10
However I'd like to merge the 2 and still have the ability to do a wildcard search on the stock_code column.
All results MUST return the description and qty_in_stock. The sales_price in the result will be either stored_price from the Custom Pricing Table or sales_price from the Generic Pricing Table however stored_price takes precedence over sales_price from the Generic Pricing Table.
I understand I need to do a UNION query, however I am unsure how I could write it.
EDIT
Using the statement:
SELECT stock.*, price.*
FROM stock
LEFT INNER JOIN price
ON stock.stock_code = price.stock_code
WHERE stock.stock_code LIKE '%123%'
LIMIT 0,10
I now receive the following error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INNER JOIN price
ON stock.stock_code = price.stock_code
WHERE stock.stoc' at line 3
I think you can join both the tables. Try this:
SELECT
generic.stock_code,
generic.last_purchase_price,
IFNULL(custom.stored_price, generic.sales_price) AS actual_price,
generic.description,
generic.qty_in_stock
FROM
Generic_Pricing_List generic
JOIN Custom_Pricing_List custom
on custom.stock_code = generic.stock_code
WHERE $column LIKE '%$term%' $or LIMIT 0,10;
If stored_price is not null and is defined, it will be picked up. Otherwise, sales_price will be picked up.
SELECT g.*
FROM Generic_Pricing_List g
LEFT JOIN Custom_Pricing_List c
ON g.stock_code = c.stock_code
WHERE g.Search_Column LIKE '%Term%'
LIMIT 0,10
Left Inner Join will get you all rows from Generic_Pricing_List and matching rows from Custom_Pricing_List.
SELECT Generic_Pricing_List.*, Custom_Pricing_List.stored_price, Custom_Pricing_List.pricing_ref
FROM Generic_Pricing_List
LEFT INNER JOIN Custom_Pricing_List
ON Generic_Pricing_List.stock_code = Custom_Pricing_List.stock_code
WHERE Generic_Pricing_List.Search_Column LIKE '%Term%'
LIMIT 0,10
SELECT t1.*, t2.field1, t2.field2
FROM $table1 AS t1
LEFT JOIN $table2 AS t2 ON (t1.stock_code = t2.stock_code)
WHERE t1.$column LIKE '%$term%' $or
LIMIT 0,10
simply use JOIN
You could use a union, but it's not required. The union, however, might be more efficient with larger data sets because "if"s can be expensive. Quick and dirty:
SELECT gpl.stock_code, last_purchase_price, if(stored_price is null, sales_price, stored_price) as sales_price, description, qty_in_stock
FROM Generic_Pricing_List gpl
LEFT JOIN Custom_Pricing_List cpl
ON gpl.stock_code = cpl.stock_code
AND cpl.stored_price != ''
AND cpl.stored_price IS NOT NULL
WHERE $column LIKE '%$term%' $or
LIMIT 0,10
Using a union:
SELECT * FROM
(SELECT gpl.stock_code, last_purchase_price, stored_price as sales_price, description, qty_in_stock
FROM Generic_Pricing_List gpl
JOIN Custom_Pricing_List cpl
ON gpl.stock_code = cpl.stock_code
AND cpl.stored_price != ''
AND cpl.stored_price IS NOT NULL
WHERE $column LIKE '%$term%' $or
UNION
SELECT gpl.stock_code, last_purchase_price, sales_price, description, qty_in_stock
FROM Generic_Pricing_List gpl
WHERE $column LIKE '%$term%' $or) foo
GROUP BY foo.stock_code
LIMIT 0,10
Be careful of the "or" statement being injected, as it might cause problems with the results.

get all comments including its votes for a post

I have following three tables in mysql.
postid | post_content => posts
commentid | post | comment_content => comments
commentvoteid | comment | voter => comment_votes
I want to fetch all the comments, counting its votes for a post.
commentid | comment_content | comment_votes => my_result
I have tried the following query but not getting the desired result.
SELECT commentid,comment_content,count_comments.total AS comment_votes
FROM comments
INNER JOIN(SELECT COUNT(*) AS total FROM comment_votes WHERE comment=comments.commentid) AS count_comments
WHERE post={$postId}
Is it possible to fetch the result as I wanted? How can I do that?
You can use GROUP BY to achieve what you want:
SELECT commentid,comment_content,COUNT(*) AS total
FROM comments
INNER JOIN comment_votes ON (comment_votes.comment=comments.commentid)
WHERE post={$postId}
GROUP BY commentid;
The method that you are trying uses a correlated subquery. You can do this, but the correlated subquery needs to go into the select clause:
SELECT c.commentid, c.comment_content,
(SELECT COUNT(*) FROM comment_votes cv WHERE cv.comment = c.commentid
) AS comment_votes
FROM comments c
WHERE post={$postId};
Normally, I much prefer the group by approach but sometimes in MySQL this can be faster.
maybe like this:
select a.commentid, a.comment_content, count(b.*) as total from (
select commentid, comment_content from comments where post={$postId}) a
join comment_votes b on b.comment = a.commentid
group by a.commentid, a.comment_content;

Select only one entry per ID

I'm working with a system that perodically updates itself, within this system I try to select the highest 500 and list them. However, the problem is that I sort it by a varchar that contains a date in the format of 2013-07-08, this is a relic and for the time being I am annoyingly not allowed to change this to a proper date for easy sorting.
My question is, how can I select only 1 of the following 3 pretended results?
id| value | ownerid | date
1| 21300 | 1 | 2013-07-08
2| 21300 | 1 | 2013-07-08
3| 21300 | 1 | 2013-07-08
What I need done is to select one entry from each ownerid, which is the one with the highest value (if it's all the same it doesn't really matter which entry, just that it's only one!)
This is using a mysql database with PDO as the database layer.
Thankful for any and all help!
SELECT *
FROM [table]
ORDER BY STR_TO_DATE(yourdatefield, '%m/%d/%Y') DESC
LIMIT 5
Use a sub-query with a GROUP BY to get a particular id for each day (max or min for example) then JOIN that against the table to get the rest of the details:
SELECT a.id, a.value, a.ownerid, a.date
FROM SomeTable a
INNER JOIN
(
SELECT date, MAX(id) AS MaxId
FROM SomeTable
GROUP BY date
) b
ON a.date = b.date
AND a.id = b.MaxId
Modified version to get the highest value row for each day. As value isn't unique I have done a further sub select to get the highest id for that highest value. Without this you could get multiple rows returned for a day. If there is zero chance of the other values every being different you could use DISTINCT to remove the multiples, but normally not possible to be certain of that.
SELECT a.id, a.`value`, a.ownerid, a.`date`
FROM SomeTable a
INNER JOIN
(
SELECT `date`, MAX(`value`) AS MaxVal
FROM SomeTable
GROUP BY `date`
) b
ON a.`date` = b.`date`
AND a.`value` = b.MaxVal
INNER JOIN
(
SELECT `date`, `value`, MAX(id) AS MaxId
FROM SomeTable
GROUP BY `date`, `value`
) c
ON b.`date` = c.`date`
AND b.MaxVal = c.`value`
AND a.id = c.MaxId

Display only the newest record for each row in MySQL db using PHP

I have two tables of which one is updated daily. I would like to display "only" the latest record for each row.
This is the query I am using now that of course returns all the records.
SELECT *
FROM ss_pumps, secondary_systems WHERE ss_pumps.id=secondary_systems.segment_id
ORDER BY id ASC
Any help would be greatly appreciated!
You can find the latest record for every segment_id by ID using subquery. The result of the subquery is then join against the two tables: ss_pumps and secondary_systems.
SELECT a.*, c.*
FROM ss_pumps a
INNER JOIN
(
SELECT segment_id, MAX(datefield) max_val
FROM secondary_systems
GROUP BY segment_id
)b ON a.id = b.segment_id
INNER JOIN secondary_systems c
ON b.segment_id = c.segment_id AND
b.max_val = c.datefield
Actually, I'm not sure how your tables: ss_pumps and secondary_systems are related with each other.
I think you want it the other ways,
SELECT a.*, b.*
FROM secondary_systems a
INNER JOIN ss_pumps b
ON a.segment_ID = b.segment
INNER JOIN
(
SELECT segment, MAX(ID) max_val
FROM ss_pumps
GROUP BY segment
) c ON b.segment = c.segment AND
b.ID = c.max_val
Use this query:
SELECT * FROM ss_pumps, secondary_systems WHERE ss_pumps.id=secondary_systems.segment_id ORDER BY id DESC LIMIT 1
This is assuming that id is an auto increment column and will always be inserted in order.
Here's what I got:
SELECT *
FROM
ss_pumps ssp
LEFT JOIN ( SELECT * FROM secondary_systems ORDER BY id DESC ) ss ON ( ssp.id = ss.segment_id )
GROUP BY
ssp.id
ORDER BY
ssp.id ASC
CAVEAT: I'm assuming that the secondary_systems has its own id field that also autoincrements. That's the only way you can make sure you're getting "only" the latest record for each row.
Demo: http://sqlfiddle.com/#!2/f816b/2/0
In my demo ss_pumps held the parents while secondary_systems held the children. Each parent has 3 children. All but the last children are boys. The last child is always a girl. According to your problem, the resulting query should yield only girls.
| ID | PARENT | SEGMENT_ID | CHILD |
------------------------------------
| 1 | mother | 1 | betty |
| 2 | father | 2 | tina |

Categories