Writting SQL code with using count and gorup_concat - mysql

I've already read every post with the similarly title but didn't find right answer.
What I really need to do is to count some data from MySQL table and then do group_concat because I got more than one row.
My table looks like this
and here is how I tried to run the query
SELECT
count(cal.day) * 8,
w.name
FROM claim as c
RIGHT JOIN calendar as cal ON c.id = cal.claim_id
RIGHT JOIN worker as w ON c.worker_id = w.id
GROUP BY c.id
ORDER BY w.name asc
But I get for some workers more than one row and I can't group_concat them because of count(). I need this for mysql procedure I've making so please help me if you can.
I hope I've gived you enough informations
Edit for Dylan:
See the difference in output
GROUP BY w.id
GROUP BY c.id

MySQL does'not allow two aggregate functions used together, like GROUP_CONCAT(COUNT(...)).
Therefore, we can use a sub-query to work around as below.
SELECT
GROUP_CONCAT(t.cnt_cal_day) as cnt_days,
t.name
FROM
(
SELECT
count(cal.day) * 8 as cnt_cal_day,
w.name
FROM claim as c
RIGHT JOIN calendar as cal ON c.id = cal.claim_id
RIGHT JOIN worker as w ON c.worker_id = w.id
GROUP BY c.id
ORDER BY w.name asc
) t

While the question is still not clear for me, I try to guess what you need.
This query:
SELECT
w.name,
COUNT(cal.day) * 8 AS nb_hours
FROM worker w
LEFT JOIN claim c ON w.id = c.worker_id
INNER JOIN calendar cal ON c.id = cal.claim_id
GROUP BY w.id
ORDER BY w.name ASC
returns the names of all workers and, for each one, the number of hours of vacation approved for them.
If you use LEFT JOIN calendar instead you will get the number of hours of vacation claimed by each worker (approved and not approved). In order to separate them you should make the query like this:
SELECT
w.name,
c.approved, # <---- I assumed the name of this field
COUNT(cal.day) * 8 AS nb_hours
FROM worker w
LEFT JOIN claim c ON w.id = c.worker_id
LEFT JOIN calendar cal ON c.id = cal.claim_id
GROUP BY w.id, c.approved
ORDER BY w.name ASC
This query should return 1 or 2 rows for each worker, depending on the types of vacation claims they have (none, approved only, not approved only, both). For workers that don't have any vacation claim, the query returns NULL in column approved and 0 in column nb_hours.

Related

MySQL View in place of subquery does not return the same result

The query below is grabbing some information about a category of toys and showing the most recent sale price for three levels of condition (e.g., Brand New, Used, Refurbished). The price for each sale is almost always different. One other thing - the sales table row id's are not necessarily in chronological order, e.g., a toy with a sale id of 5 could have happened later than a toy with a sale id of 10).
This query works but is not performant. It runs in a manageable amount of time, usually about 1s. However, I need to add yet another left join to include some more data, which causes the query time to balloon up to about 9s, no bueno.
Here is the working but nonperformant query:
SELECT b.brand_name, t.toy_id, t.toy_name, t.toy_number, tt.toy_type_name, cp.catalog_product_id, s.date_sold, s.condition_id, s.sold_price FROM brands AS b
LEFT JOIN toys AS t ON t.brand_id = b.brand_id
JOIN toy_types AS tt ON t.toy_type_id = tt.toy_type_id
LEFT JOIN catalog_products AS cp ON cp.toy_id = t.toy_id
LEFT JOIN toy_category AS tc ON tc.toy_category_id = t.toy_category_id
LEFT JOIN (
SELECT date_sold, sold_price, catalog_product_id, condition_id
FROM sales
WHERE invalid = 0 AND condition_id <= 3
ORDER BY date_sold DESC
) AS s ON s.catalog_product_id = cp.catalog_product_id
WHERE tc.toy_category_id = 1
GROUP BY t.toy_id, s.condition_id
ORDER BY t.toy_id ASC, s.condition_id ASC
But like I said it's slow. The sales table has about 200k rows.
What I tried to do was create the subquery as a view, e.g.,
CREATE VIEW sales_view AS
SELECT date_sold, sold_price, catalog_product_id, condition_id
FROM sales
WHERE invalid = 0 AND condition_id <= 3
ORDER BY date_sold DESC
Then replace the subquery with the view, like
SELECT b.brand_name, t.toy_id, t.toy_name, t.toy_number, tt.toy_type_name, cp.catalog_product_id, s.date_sold, s.condition_id, s.sold_price FROM brands AS b
LEFT JOIN toys AS t ON t.brand_id = b.brand_id
JOIN toy_types AS tt ON t.toy_type_id = tt.toy_type_id
LEFT JOIN catalog_products AS cp ON cp.toy_id = t.toy_id
LEFT JOIN toy_category AS tc ON tc.toy_category_id = t.toy_category_id
LEFT JOIN sales_view AS s ON s.catalog_product_id = cp.catalog_product_id
WHERE tc.toy_category_id = 1
GROUP BY t.toy_id, s.condition_id
ORDER BY t.toy_id ASC, s.condition_id ASC
Unfortunately, this change causes the query to no longer grab the most recent sale, and the sales price it returns is no longer the most recent.
Why is it that the table view doesn't return the same result as the same select as a subquery?
After reading just about every top-n-per-group stackoverflow question and blog article I could find, getting a query that actually worked was fantastic. But now that I need to extend the query one more step I'm running into performance issues. If anybody wants to sidestep the above question and offer some ways to optimize the original query, I'm all ears!
Thanks for any and all help.
The solution to the subquery performance issue was to use the answer provided here: Groupwise maximum
I thought that this approach could only be used when querying a single table, but indeed it works even when you've joined many other tables. You just have to left join the same table twice using the s.date_sold < s2.date_sold join condition and make sure the where clause looks for the null value in the second table's id column.

Running an SQL query in the loop of another SQL query?

So I have the following query which fetches Active Competitions within an organisation and also tries to fetch the user who is leading the competition.
Currently the query correctly fetches the active competitions, and the totalPoints for each user. It now grabs all users, I only want it to grab the top user, so I am assuming the solution lies in the GROUP BY query, some sort of LIMIT?
In this image, you can see the results I am getting. As you can see, I am getting every user for each competition, where I only need the top user for each competition.
http://i.imgur.com/5OXen4e.png
Any idea on how I could solve this?
SELECT c.competitionId, c.name, c.start_date, c.end_date, a.userid, u.name, u.profilePic ,
SUM(activity_weight) AS totalPoints
FROM activity_entries a INNER JOIN users1 u ON u.id = a.userid INNER JOIN competitions c ON c.competitionId = a.competitionId
WHERE c.organisationId = 1 AND c.start_date < now() AND c.end_date > now()
GROUP BY a.userid, c.competitionId ORDER BY c.id DESC
There is a better way. Just run one query with and join to the competitions table. The second query you posted shows that you know how to join tables.

Sorting results from joins

While running this query:
SELECT
a.id,
pub.name AS publisher_name,
pc.name AS placement_name,
b.name AS banner_name,
a.lead_id,
a.partner_id,
a.type,
l.status,
s.correctness,
a.landing_page,
t.name AS tracker_name,
a.date_view,
a.date_action
FROM actions AS a
LEFT JOIN publishers AS pub ON a.publisher_id = pub.id
LEFT JOIN placements AS pc ON pc.publisher_id = pub.id
LEFT JOIN banners AS b ON b.campaign_id = a.campaign_id
LEFT JOIN leads l ON
l.lead_id = a.lead_id
AND l.created = (
SELECT MAX(created) from leads l2 where l2.lead_id = l.lead_id
)
LEFT JOIN statuses AS s ON l.status = s.status
LEFT JOIN trackers AS t ON t.id = a.tracker_id
LIMIT 10
I am able to sort by every column from actions table. However when I try to for example ORDER BY b.name (from banners table, joined on actions.banner_id) or ORDER BY l.lead_id (joined from leads on more complex condition as seen above) MySQL is running query for a loooong time (most tables have tens of thousands records). Is it possible, performance-wise, to sort by joined columns?
You should rewrite the query with a inner join on the table where the column you want to sort on is.
For example, if you sort on actions.banner_id
SELECT ...
FROM actions AS a
JOIN banners AS b ON b.campaign_id = a.campaign_id
LEFT JOIN *rest of the query*
You will get the same results unless there is not enough banners that can be joined to action to produce a total of 10 rows.
I'm guessing it's not the case otherwise you wouldn't be sorting on banner_id.
You could first filter (order by, where, etc.) your records in a subquery and then join the result with the rest of the tables.

sql nested inner join only returning 1 result

I'm building a Chatapplication that's a bit like the facebookchat. I have users,conversations and messages. All 3 have their own tables. For now I try to get all converstations containing a certain user and the latest message of the conversation.
I tried this query, but in a fact I only get 1 row back, but there are more rows matching
SELECT conversations.id as converid,
messages.from as messageauthor,
messages.message as message
FROM conversations INNER JOIN (SELECT * FROM messages
ORDER BY date DESC LIMIT 1) as messages
ON messages.conversationid=conversations.id
WHERE user1=3
OR user2=3
When I do i.e.
SELECT conversations.id as converid,
messages.from as messageauthor
FROM conversations INNER JOIN messages
ON messages.conversationid=conversations.id
WHERE user1=3
OR user2=3
I get all results, for sure, and when I check the converid's I get 3 unique Id's, so at least there are 3 converstations going on with userid 3. So the top query should also return 3. Now I don't understand why it only returns 1 row. Does the limit 1 from the nested query affect the whole query?
Looking forward for some pointers...
No. The limit 1 affects the subquery, so it is only returning one row. So, there is only one match.
What is the issue with this query (your second query, but formatted differently):
SELECT c.id as converid, m.from as messageauthor
FROM conversations c INNER JOIN
messages m
ON m.conversationid=c.id
WHERE user1=3 OR user2=3;
I see, you want the latest message. Try calculating it and joining back in:
SELECT c.id as converid, m.from as messageauthor
FROM conversations c INNER JOIN
messages m
ON m.conversationid=c.id join
(select m.conversationid, max(date) as maxdate
from messages m
group by m.conversationid
) mmax
on mmax.conversationid = m.conversationid and m.date = mmax.maxdate
WHERE user1=3 OR user2=3;

MySql query to get count of days spent in each country for each purpose? (Get count of all record in second table present in first table)

I have three tables tl_log, tl_geo_countries,tl_purpose. I am trying to get the count of number of days spent in each country in table 'tl_log' for each purpose in table 'tl_purpose'.
I tried below mysql query
SELECT t.country_id AS countryID,t.reason_id AS reasonID,count(t.reason_id) AS
days,c.name AS country, p.purpose AS purpose
FROM `tl_log` AS t
LEFT JOIN tl_geo_countries AS c ON t.country_id=c.id
LEFT JOIN tl_purpose AS p ON t.reason_id=p.id
GROUP BY t.reason_id,t.country_id ORDER BY days DESC
But landed up with.
I am not able to get the count for purpose for each country in 'tl_log' that is not present in table 'tl_log'. Any help is greatly appreciated. Also, Please let me know if the question is difficult to understand.
Expected Output:
Below is the structure of these three tables
tl_log
tl_geo_countries
tl_purpose
If you want all possible combination of countries and purposes, even those that do not appear on the log table (these will be shown with a count of 0), you can do first a cartesian product of the two tables (a CROSS join) and then LEFT join to the log table:
SELECT
c.id AS countryID,
p.id AS reasonID,
COUNT(t.reason_id) AS days,
c.name AS country,
p.purpose AS purpose
FROM
tl_geo_countries AS c
CROSS JOIN
tl_purpose AS p
LEFT JOIN
tl_log AS t
ON t.country_id = c.id
AND t.reason_id = p.id
GROUP BY
p.id,
c.id
ORDER BY
days DESC ;
If you want the records for only the countries that are present in the log table (but still all possible reason/purposes), a slight modification is needed:
SELECT
c.id AS countryID,
p.id AS reasonID,
COUNT(t.reason_id) AS days,
c.name AS country,
p.purpose AS purpose
FROM
( SELECT DISTINCT
country_id
FROM
tl_log
) AS dc
JOIN
tl_geo_countries AS c
ON c.id = dc.country_id
CROSS JOIN
tl_purpose AS p
LEFT JOIN
tl_log AS t
ON t.country_id = c.id
AND t.reason_id = p.id
GROUP BY
p.id,
c.id
ORDER BY
days DESC ;
LEFT JOIN should be replaced by RIGHT JOIN