Join MySQL Tables to Display Count - mysql

Firstly, I'm a beginner to MySQL and I'm still learning. I'm trying to join 2 tables to display a count. Primarily, I use 2 codes. One code to display names -
SELECT tag_logs.timestamp, People.Name FROM `tag_logs` INNER JOIN People WHERE tag_logs.tag_no = People.nametag
Another code to display count of names -
SELECT tag_logs.tag_no, COUNT(tag_logs.tag_no) FROM tag_logs GROUP BY tag_no HAVING COUNT(tag_no) >= 1
I want to display Name and a count number, instead of a tag number and count. I attempted to join both tables by using the following code, however, I've had little luck -
SELECT People.Name FROM `tag_logs` INNER JOIN People WHERE tag_logs.tag_no = People.nametag AND COUNT(tag_logs.tag_no) FROM tag_logs GROUP BY tag_no HAVING COUNT(tag_no) >= 1
I'm given an error when I try to call 'FROM tag_logs' a second time. Is there a way to work around this?
I aim to make this my final result, except I should be able to see names instead of numbers.

Two tables are joined using ON clause. You should learn joins.
SELECT People.Name ,COUNT(tag_logs.tag_no)
FROM `tag_logs`
INNER JOIN People ON tag_logs.tag_no = People.nametag
GROUP BY tag_logs.tag_no
HAVING COUNT(tag_no) >= 1

It should be
SELECT People.Name FROM `tag_logs`
INNER JOIN People on tag_logs.tag_no = People.nametag
GROUP BY tag_no HAVING COUNT(tag_no) >= 1
EDIT
SELECT People.Name, COUNT(tag_no) FROM `tag_logs`
INNER JOIN People on tag_logs.tag_no = People.nametag
GROUP BY tag_no HAVING COUNT(tag_no) >= 1

I believe the query that you want looks like this:
SELECT p.Name, COUNT(*)
FROM tag_logs tl INNER JOIN
People p
ON tl.tag_no = p.nametag
GROUP BY p.Name;
Notes:
COUNT(*) is shorter than COUNT(tl.tag_no) and they do the same thing.
GROUP BY clause now matches the SELECT. If you could have people with the same names, then add p.nametag to the GROUP BY. A version use only GROUP BY tl.tag_no is invalid SQL and should fail in most databases, because of the non-matching p.Name in the SELECT.
The HAVING clause (HAVING COUNT(tag_no) >= 1) is unnecessary, because the INNER JOIN requires at least one match and tag_no is never NULL (because it is used for the JOIN).
I introduced table aliases, so the query is easier to write and to read.

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.

Multiple COUNT() in JOIN

I'm trying to get the number of rows of two different tables with two LEFT JOIN in a MySQL query. It works well when I have a COUNT on one table like this :
SELECT a.title, a.image, COUNT(o.id) AS occasions
FROM activity a
LEFT JOIN occasion AS o ON a.id = o.activity_id
WHERE a.user_id = 1
GROUP BY a.id
ORDER BY a.created_at
DESC LIMIT 50
Here, everything works and I get the good number of "occasions".
But when I try to add an additional COUNT with an additional LEFT JOIN, the result of the second COUNT is wrong :
SELECT a.title, a.image, COUNT(o.id) AS occasions, COUNT(au.id) AS users
FROM activity a
LEFT JOIN occasion AS o ON a.id = o.activity_id
LEFT JOIN activity_user AS au ON a.id = au.activity_id
WHERE a.user_id = 4
GROUP BY a.id
ORDER BY a.created_at
DESC LIMIT 50
Here, I get the good number of "occasions" but "users" seems to be a copy of the "occasions" count, which is wrong.
So my question is, how to fix this query to have the two COUNT working together?
COUNT() counts non-NULL values. The simple way to fix your query is to use COUNT(DISTINCT):
SELECT a.title, a.image,
COUNT(DISTINCT o.id) AS occasions, COUNT(DISTINCT au.id) AS users
. . .
And this will probably work. However, it creates an intermediate table that is the Cartesian product of the two tables (for each title). That could grow very big. The more scalable solution is to use subqueries and aggregate before joining.
The used left join for activity user limits your result because the DB is not able to found related data. But when you use LEFT OUTER JOIN the it should return all expected rows and their count.

SQL Query, subquery minus subquery?

Ok, so Ive been trying to get this query working for hours now, but nothing I seem to do will get me the results I am after.
SELECT COALESCE(SUM(ps.cost), 0) AS ps_total
FROM Customers c
LEFT JOIN ProductSales ps ON c.customer_ID = ps.customer_ID
GROUP BY c.sex;
SELECT COALESCE(SUM(hc.cost), 0) AS hc_total
FROM Customers c
LEFT JOIN HairCuts hc ON c.customer_ID = hc.customer_ID
GROUP BY c.sex;
So the above two queries work fine. Each one finds the total spent on either products or hair cuts and groups by gener thus giving me the total spent on cuts and products for males and females individually.However somehow I need to combine these in such a way that I can display the gender(s) that spent more on products than on haircuts.
Any help with this would be very much appreciated.
P.S hopefully the question is clear enough. If not Ill try to elaborate.
Another way you can join your both query results as
SELECT t1.sex,
COALESCE(t1.ps_total - t2.hc_total,0) AS `difference`
FROM
(SELECT COALESCE(SUM(ps.cost), 0) AS ps_total ,c.sex
FROM Customers c
LEFT JOIN ProductSales ps ON c.customer_ID = ps.customer_ID
GROUP BY c.sex) t1
LEFT JOIN
(SELECT COALESCE(SUM(hc.cost), 0) AS hc_total ,c.sex
FROM Customers c
LEFT JOIN HairCuts hc ON c.customer_ID = hc.customer_ID
GROUP BY c.sex) t2
USING(sex)
HAVING difference > 0
Edit from comments
I have used a short syntax for ON() clause like USING(sex) = ON(t1.sex=t2.sex) if you have a same name for a column in both tables you can use USING() if you have different then you need to use ON() syntax
Like
Table1 column(category_id primary key,...)
Table2 column(category_id foreign key,...)
Then you can easily use USING()
SELECT * FROM
Table1
JOIN Table2
USING(category_id )
But if you have different name for association then you need to use ON() clause

My SQL query is returning results but they are repeated ~50 times. I don't understand why

The query I'm using calls on a few tables in the database and works fine. However, when I add line 10 to the mix it returns 50 or more repeated results. I'm still somewhat new to SQL and Sequel Pro so I'm sure the solution isn't too complicated but I am truly stumped right now.
Here is the code:
SELECT c.first_name, c.last_name, ca.company, ca.city, ca.state, ct.certificate_number, ct.certificate_date
FROM customer c, customer_type ctype, cust_address ca, certification ct, cust_prof_cert cp
WHERE ca.id_customer = c.id_customer LIKE cp.prof_cert_id_prof_cert
AND c.customer_type_id_customer_type = ctype.id_customer_type
AND ct.customer_id_customer = c.id_customer
AND ca.id_customer = c.id_customer
AND ctype.customer_type IN('CIRA','CIRA, CDBV')
AND ct.course_type_id_course_type = 1
AND ct.certificate_number IS NOT NULL
AND cp.prof_cert_id_prof_cert = "1"
ORDER BY ct.certificate_number ASC, c.last_name ASC;
Thank you for your time.
By Doing your SQL like that you are not relating the data, just selecting it. I would recommend changing your SQL to use JOINS.
SELECT Orders.OrderID, Customers.CustomerName, Orders.OrderDate
FROM Orders
INNER JOIN Customers
ON Orders.CustomerID=Customers.CustomerID;
Here is an article that might be able to help you a bit: w3schools, Joins
Here's your query using the SQL92 syntax for joins. You should use this syntax instead of the SQL89 "comma-style" joins.
SELECT c.first_name, c.last_name, ca.company, ca.city, ca.state,
ct.certificate_number, ct.certificate_date
FROM customer AS c
INNER JOIN customer_type AS ctype ON c.customer_type_id_customer_type = ctype.id_customer_type
INNER JOIN cust_address AS ca ON ca.id_customer = c.id_customer
INNER JOIN certification AS ct ON ct.customer_id_customer = c.id_customer
INNER JOIN cust_prof_cert AS cp -- what's this join condition?
WHERE ca.id_customer = c.id_customer LIKE cp.prof_cert_id_prof_cert
AND ctype.customer_type IN('CIRA','CIRA, CDBV')
AND ct.course_type_id_course_type = 1
AND ct.certificate_number IS NOT NULL
AND cp.prof_cert_id_prof_cert = '1'
ORDER BY ct.certificate_number ASC, c.last_name ASC;
A few weird things I notice in this query:
The first term in the WHERE clause is strange. You should know that LIKE has higher precedence than = so this might not be doing what you think it's doing. It's as if you wrote
WHERE ca.id_customer = (c.id_customer LIKE cp.prof_cert_id_prof_cert)
Which means evaluate the LIKE and produce a 0 or a 1 to represent the boolean condition. Then look for a ca.id_customer matching that 0 or 1.
Given that strange term, I can find no other join condition for the cp table. The default join if you give no restriction for it is that every row matches every row in the joined tables. So if you have 50 rows where cp.prof_cert_id_prof_cert = 1, then it will effectively multiply the results from the rest of the joined tables by 50.
This is called a Cartesian product, or in MySQL parlance it's counted in SHOW STATUS as a Full join.
ctype.customer_type IN('CIRA','CIRA, CDBV') You have quoted the second and third strings together. Basically, this means you are trying to match the column against two strings, one of which happens to contain a comma.
You probably meant to write ctype.customer_type IN('CIRA','CIRA','CDBV') so the column may match any of these three values.
I would suggest not querying multiple tables in your FROM clause, I believe this is the cause of your duplicate rows. If you separate out the tables into separate inner or left joins, (whichever you need) you should be able to match which ever keys in each table manually, instead of having SQL attempt to automatically do this.

group by but still displaying all data?

i wonder how to using group by but still displaying full data? i just want to group it.
here i give an example of my table :
this is my query :
(SELECT dp.menu_paket,d.id_detail,t.no_meja,m.nama_menu,d.jumlah,t.status,t.nama_pegawai
FROM menu m
join detail_paket dp on dp.menu_paket=m.nama_menu
JOIN detail_transaksi d on m.id_menu = d.id_menu
join transaksi t on t.id_transaksi=d.id_transaksi where t.status='progress' and d.status_menu='progress' group by id_detail)
UNION
(SELECT dp.menu_paket,d.id_detail,t.no_meja,p.nama_paket,d.jumlah,t.status,t.nama_pegawai
FROM paket p
join detail_paket dp on dp.id_paket=p.id_paket
JOIN detail_transaksi d on d.id_paket=p.id_paket
join transaksi t on t.id_transaksi=d.id_transaksi where t.status='progress' and d.status_menu='progress' group by id_detail);
thanks..!
You can apply distinct to avoid same multiple records instead of group by. because group by is used when there is aggregate function is your query.
Distinct retrieves single row instead of multiple rows when two rows are totally same.
Try this
select distinct columnname from table name
union
select distinct columnname1 from table name
I think I see two issues.
1) GROUP BY is generally used when you want to group rows for an aggregate function like SUM. You may be looking for ORDER BY, which controls the order of the rows. You can specify multiple columns for ORDER BY to obtain a "grouping" effect. This is what you want if you just want the rows to be next to each other in the list.
2) UNION, at least in the databases I know of, removes duplicate rows. You want UNION ALL if you want to preserve all rows.
Edit:
In response to the poster's comment, you definitely want ORDER BY and maybe UNION ALL. It should be ORDER BY no_meja, id_transaksi. Try the following query and see if it gives you what you want:
SELECT * FROM
((SELECT dp.menu_paket,d.id_detail,t.no_meja,m.nama_menu,d.jumlah,t.status,t.nama_pegawai
FROM menu m
join detail_paket dp on dp.menu_paket=m.nama_menu
JOIN detail_transaksi d on m.id_menu = d.id_menu
join transaksi t on t.id_transaksi=d.id_transaksi
where t.status='progress' and d.status_menu='progress')
UNION ALL
(SELECT dp.menu_paket,d.id_detail,t.no_meja,p.nama_paket,d.jumlah,t.status,t.nama_pegawai
FROM paket p
join detail_paket dp on dp.id_paket=p.id_paket
JOIN detail_transaksi d on d.id_paket=p.id_paket
join transaksi t on t.id_transaksi=d.id_transaksi
where t.status='progress' and d.status_menu='progress')) x
ORDER BY x.no_meja, x.id_transaksi;