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
Related
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.
I have created this SQL in order to find customers that haven't ordered for X days.
It is returning a result set, so this post is mainly just to get a second opinion on it, and possible optimizations.
SELECT o.order_id,
o.order_status,
o.order_created,
o.user_id,
i.identity_firstname,
i.identity_email,
(SELECT COUNT(*)
FROM orders o2
WHERE o2.user_id=o.user_id
AND o2.order_status=1) AS order_count,
(SELECT o4.order_created
FROM orders o4
WHERE o4.user_id=o.user_id
AND o4.order_status=1
ORDER BY o4.order_created DESC LIMIT 1) AS last_order
FROM orders o
INNER JOIN user_identities ui ON o.user_id=ui.user_id
INNER JOIN identities i ON ui.identity_id=i.identity_id
AND i.identity_email!=''
INNER JOIN subscribers s ON i.identity_id=s.identity_id
AND s.subscriber_status=1
AND s.subsriber_type=e
AND s.subscription_id=1
WHERE DATE(o.order_created) = "2013-12-14"
AND o.order_status=1
AND o.user_id NOT IN
(SELECT o3.user_id
FROM orders o3
WHERE o3.user_id=o.user_id
AND o3.order_status=1
AND DATE(o3.order_created) > "2013-12-14")
Can you guys find any potential problems with this SQL? Dates are dynamically inserted.
The final SQL that I put in production, will basically only include o.order_id, i.identity_id and o.order_count - this order_count will need to be correct. The other selected fields and 'last_order' subquery will not be included, it's only for testing.
This should give me a list of users that have their last order on that particular day, and is a newsletter subscriber. I am particular in doubt about correctness of the NOT IN part in the WHERE clause, and the order_count subquery.
There are several problems:
A. Using functions on indexable columns
You are searching for orders by comparing DATE(order_created) with some constant. This is a terrible idea, because a) the DATE() function is executed for every row (CPU) and b) the database can't use an index on the column (assuming one existed)
B. Using WHERE ID NOT IN (...)
Using a NOT IN (...) is almost always a bad idea, because optimizers usually have trouble with this construct, and often get the plan wrong. You can almost always express it as an outer join with a WHERE condition that filters for misses using an IS NULL condition for a joined column (and adds the side benefit of not needing DISTINCT, because there's only ever one miss returned)
C. Leaving joins that filtering out of large portions of rows too late
The earlier you can mask off rows by not making joins the better. You can do this by joining less likely to match tables earlier in the joined table list, and by putting non-key conditions into join rather than the where clause to get the rows excluded as early as possible. Some optimizers to this anyway, but I've often found they don't
D. Avoid correlated subqueries like the plague!
You have several correlated subqueries - ones that are executed for every row of the main table. That's really an incredibly bad idea. Again sometimes the optimizer can craft them into a join, but why rely (hope) on that. Most correlated subqueries can be expressed as a join; you examples are no exception.
With the above in mind, there are some specific changes:
o2 and o4 are the same join, so o4 may be dispensed with entirely - just use o2 after conversion to a join
DATE(order_created) = "2013-12-14" should be written as order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"
This query should be what you want:
SELECT
o.order_id,
o.order_status,
o.order_created,
o.user_id,
i.identity_firstname,
i.identity_email,
count(o2.user_id) AS order_count,
max(o2.order_created) AS last_order
FROM orders o
LEFT JOIN orders o2 ON o2.user_id = o.user_id AND o2.order_status=1
LEFT JOIN orders o3 ON o3.user_id = o.user_id
AND o3.order_status=1
AND o3.order_created >= "2013-12-15 00:00:00"
JOIN user_identities ui ON o.user_id=ui.user_id
JOIN identities i ON ui.identity_id=i.identity_id AND i.identity_email != ''
JOIN subscribers s ON i.identity_id=s.identity_id
AND s.subscriber_status=1
AND s.subsriber_type=e
AND s.subscription_id=1
WHERE o.order_created between "2013-12-14 00:00:00" and "2013-12-14 23:59:59"
AND o.order_status=1
AND o3.order_created IS NULL -- This gets only missed joins on o3
GROUP BY
o.order_id,
o.order_status,
o.order_created,
o.user_id,
i.identity_firstname,
i.identity_email;
The last line is how you achieve the same as NOT IN (...) using a LEFT JOIN
Disclaimer: Not tested.
Can't really comment on the results as you have not posted any table declares or example data, but your query has 3 correlated sub queries which is likely to make it perform poorly (OK, one of those is for last_order and is only for testing).
Eliminating the correlated sub queries and replacing them with joins would give something like this:-
SELECT o.order_id,
o.order_status,
o.order_created,
o.user_id,
i.identity_firstname,
i.identity_email,
Sub1.order_count,
Sub2.last_order
FROM orders o
INNER JOIN user_identities ui ON o.user_id=ui.user_id
INNER JOIN identities i ON ui.identity_id=i.identity_id
AND i.identity_email!=''
INNER JOIN subscribers s ON i.identity_id=s.identity_id
AND s.subscriber_status=1
AND s.subsriber_type=e
AND s.subscription_id=1
LEFT OUTER JOIN
(
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE order_status=1
GROUP BY user_id
) Sub1
ON o.user_id = Sub1.user_id
LEFT OUTER JOIN
(
SELECT user_id, MAX(order_created) as last_order
FROM orders
WHERE order_status=1
GROUP BY user_id
) AS Sub2
ON o.user_id = Sub2.user_id
LEFT OUTER JOIN
(
SELECT DISTINCT user_id
FROM orders
WHERE order_status=1
AND DATE(order_created) > "2013-12-14"
) Sub3
ON o.user_id = Sub3.user_id
WHERE DATE(o.order_created) = "2013-12-14"
AND o.order_status=1
AND Sub3.user_id IS NULL
I need a query. I'm trying to sum of one field with joined tables. Some records not in second table. So this records sum should be zero. But the query only sum the records which are in the second table.
select s.*,sum(sd.fiyat) as konak from fuar_sozlesme1 s
left outer join fuar_sozlesme1_detay sd on (sd.sozlesme_id = s.id)
------EDIT-------
I added group by into the query and solved my problem. Here is the new ;
select s.*,sum(sd.fiyat) as konak from fuar_sozlesme1 s
left outer join fuar_sozlesme1_detay sd on (sd.sozlesme_id = s.id)
group by sd.sozlesme_id
I thinik you need to use IFNULL(sd.fiyat,0) instead of sd.fiyat to get zeros for the NULL values coming from the second table because of the LEFT JOIN like so:
SELECT s.*, SUM(IFNULL(sd.fiyat, 0)) as konak
FROM fuar_sozlesme1 s
LEFT OUTER JOIN fuar_sozlesme1_detay sd ON sd.sozlesme_id = s.id
GROUP BY s.someFields
Here is a simple example, you may help: http://sqlfiddle.com/#!2/41481/1
This is an old thread, but I spent a couple of hours trying to solve the same issue.
My query has two joins, a filter and a SUM function. I'm no SQL expert, but this helped me achieve the desired result of still showing a result even if the joined table had no rows to sum.
The key for me in order to show results even if the sum was totaling nothing, was the GROUP BY. I'm still not 100% sure why.
The two types of joins were chosen based on this article - MySQL Multiple Joins in one query?
SELECT registrations.reg_active, registrations.team_id, registrations.area_id, registrations.option_id, registrations.reg_fund_goal, registrations.reg_type, registrations.reg_fee_paid, registrations.reg_has_avatar, users.user_name, users.user_email, users.user_phone, users.user_zip, users.user_age, users.user_gender, users.user_active, SUM(IFNULL(donations.donation_amount,0)) as amt from registrations
INNER JOIN `users`
ON registrations.user_id = users.user_id
AND registrations.event_id = :event_id
LEFT OUTER JOIN `donations`
ON registrations.reg_id = donations.reg_id
GROUP BY donations.reg_id
ORDER BY users.user_name ASC
I have a query that needs the most recent record from a secondary table called tbl_emails_sent.
That table holds all the emails sent to clients. And most clients have several to hundreds of emails recorded. I want to pull a query that displays the most recent.
Example:
SELECT c.name, c.email, e.datesent
FROM `tbl_customers` c
LEFT JOIN `tbl_emails_sent` e ON c.customerid = e.customerid
I'm guessing a LEFT JOIN with a subquery would be used, but I don't delve into subqueries much. Am I going the right direction?
Currently the query above isn't optimized for specifying the most recent record in the table, so I need a little assistance.
It should be like this, you need to have a separate query to get the maximum date (or the latest date) that the email was sent.
SELECT a.*, b.*
FROM tbl_customers a
INNER JOIN tbl_emails_sent b
ON a.customerid = b.customerid
INNER JOIN
(
SELECT customerid, MAX(datesent) maxSent
FROM tbl_emails_sent
GROUP BY customerid
) c ON c.customerid = b.customerid AND
c.maxSent = b.datesent
Would this not work?
SELECT t1.datesent,t1.customerid,t2.email,t2.name
FROM
(SELECT max(datesent) AS datesent,customerid
FROM `tbl_emails_sent`
) as t1
INNER JOIN `tbl_customers` as t2
ON t1.customerid=t2.customerid
Only issue you have then is what if two datesents are the same, what is the deciding factor in which one gets picked?
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