There's a table message and a table sender, and I want to retrieve all "message information" associated to a specific id, along with the sender name and email. I have this query:
SELECT
a.id,
a.subject,
a.body,
a.sender_id,
(
SELECT b.name
FROM sender b
WHERE b.id = a.sender_id
LIMIT 1
) AS sender_name,
(
SELECT b.email
FROM sender b
WHERE b.id = a.sender_id
LIMIT 1
) AS sender_email,
a.sent_time
FROM message a
WHERE a.id = <id>
LIMIT 1;
It works, but it has to perform two different SELECT subqueries to the same table.
In this case (which is an extremely simplified example) it probably doesn't hurt the performance at all, but in a real-life scenario where a lot of fields have to be retrieved from an external table, the best way to do it is using a JOIN statement? Isn't it possible to have a SELECT subquery that retrieves many fields?
I'm using MySQL with MyISAM storage engine in case it matters.
Just join to the sender tables once in the FROM clause.
SELECT a.id,
a.subject,
a.body,
a.sender_id,
b.name,
b.email,
a.sent_time
FROM message a
INNER JOIN
sender b
ON b.id = a.sender_id
WHERE a.id = <id>
LIMIT 1;
Try this:
SELECT
a.id,
a.subject,
a.body,
a.sender_id,
b.name AS sender_name,
b.email AS sender_email,
a.sent_time
FROM message a, sender b
WHERE a.id = <id>
AND b.id = a.sender_id
LIMIT 1;
If you really don't want to use a join, you could use concat in your subquery CONCAT(b.name,";",b.email) and later explode the result on ; - but I'd go for join :D
Related
...
Trying to learn SQL and the following query:
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
GROUP BY a.id, a.name, w.channel
HAVING COUNT(*) > 6 AND w.channel = 'facebook'
ORDER BY use_of_channel;
returns 46 results (first query results), JUST ADDING A JOIN of an unrelated table returns 220 results.
Its not a CROSS JOIN since it seems properly formatted, just added down here at line 5 a JOIN with "orders" table
SELECT a.id, a.name, w.channel, COUNT(*) use_of_channel
FROM accounts a
JOIN web_events w
ON a.id = w.account_id
JOIN orders o
ON o.account_id = a.id
GROUP BY a.id, a.name, w.channel
HAVING COUNT(*) > 6 AND w.channel = 'facebook'
ORDER BY use_of_channel;
...but why would another table compromise the results?
It is a cross join. That is, each account has multiple events. And each account has multiple orders. So within each account you are getting a Cartesian product.
A quick way to fix this is to use count(distinct) on a primary key:
SELECT a.id, a.name, w.channel,
COUNT(w.id) as use_of_channel
This query is NOT legal syntax and I'm trying to understand what the efficient way of writing it is. This is what I have:
SELECT a.*, b.id, lapsed FROM
( SELECT DATEDIFF(CURDATE(), MAX(day)) AS lapsed FROM c ) AS x
FROM first_table a
INNER JOIN second_table b ON a.id = b.some_id
INNER JOIN third_table c ON c.user_id = a.user_id
WHERE a.some_col = 'm'
AND b.num >= lapsed
There's three tables being joined. Normally this would be trivial, but the problem is my last part of the WHERE clause, specifically b.num >= lapsed is doing a comparison on a derived value. Is there a correct way to write this?
Haven't tested this, but if the subquery is correct then this should work.
I also assumed that the 'c' in the example of the question is also referring to that third_table and not some table/view called c.
And the INNER JOIN to third_table was commented out, since it's mostly useless to INNER JOIN table/views when you don't use any fields of it. Well, it could be used to limit on records that are in that table, but most often it's just useless to do that.
SELECT a.*, b.id, x.lapsed
FROM first_table a
INNER JOIN second_table b ON a.id = b.some_id
--INNER JOIN third_table c ON c.user_id = a.user_id
LEFT JOIN (
SELECT DATEDIFF(CURDATE(), MAX(day)) AS lapsed
FROM third_table
) AS x ON (1=1)
WHERE a.some_col = 'm'
AND b.num >= x.lapsed;
Given the MAX() aggregate, it looks like you want the latest day value for each user_id value from third_table.
To get that, we can write a query like this:
SELECT c.user_id
, DATEDIFF(CURDATE(),MAX(c.day)) AS lapsed
FROM third_table c
GROUP BY c.user_id
You can use the resultset from this query as a rowsource in another query, by using that query as a inline view in place of a table reference. (MySQL refers to an inline view as a "derived table".)
We just wrap that query in parens, and use it in place of where we would normally find a table reference. The inline view (derived table) will need to be assigned an alias.
For example:
SELECT a.*
, b.id
, d.lapsed
FROM first_table a
JOIN second_table b
ON b.some_id = a.id
JOIN ( SELECT c.user_id
, DATEDIFF(CURDATE(),MAX(c.day)) AS lapsed
FROM third_table c
GROUP BY c.user_id
) d
ON d.user_id = a.user_id
WHERE a.some_col = 'm'
AND b.num > d.lapsed
A query similar to that should run. But whether that returns the resultset you expect to achieve, that really depends on what result you are attempting to return. Absent a specification (apart from some invalid query syntax), we're just guessing.
I have three tables A,B,C.Their relation is A.id is B's foreign key and B.id is C's foreign key.I need to sum the value when B.id = C.id and A.id = B.id ,I can count the number by query twice. But now I need some way to count the summation just once time !
My inefficient solution
select count(C.id) from C,B where C.id = B.id; //return the value X
select count(A.id) from C,B where A.id = B.id; //return the value Y
select X + Y; // count the summation fo X and Y
How can I optimize ? Thks! :)
PS:
My question is from GalaXQL,which is a SQL interactive tutorial.I have abstract the problem,more detail you can check the section 17.SELECT...GROUP BY... Having...
You can do these things in one query. For instance, something like this:
select (select count(*) from C join B on C.id = B.id) +
(select count(*) from C join A on C.id = A.id)
(Your second query will not parse because A is not a recognized table alias.)
In any case, if you are learning SQL, the first thing you should learn is modern join syntax. The implicit joins that you are using were out of date 15 years ago and have been part of the ANSI standard for over 20 years. Learn proper join syntax.
Try Like This
select sum(cid) (
select count(*) as cid from C join B on C.id = B.id
union all
select count(*) as cid from A join B on A.id = B.id ) as tt
try this one:
select
(select count(*) from C join B on C.id = B.id)
union
(select count(*) from C join A on C.id = A.id)
This is my requirment
i select users id based on email matching between two tables(a.b).then i will select infomation based on that users id in another table(c).
SELECT a.email, b.id
FROM `user_fnf_info` AS a
JOIN users AS b ON a.email = b.email
WHERE a.user_id =1;
it's possible in two queries in mysql,but i need to know how to write in a single query (mysql).
anyone help me on this requirement.thanks in advance.
JOIN them with the third table:
SELECT a.email, b.id
FROM `user_fnf_info` AS a
INNER JOIN users AS b ON a.email = b.email
INNER JOIN thethirdtable AS c ON a.user_id = c.user_id
WHERE a.user_id =1;
I have 5 tables: a, b, c, d and e.
Each table is joined by an INNER JOIN on the id field.
My query is working perfectly fine as it is but I need to enhance it to count the result so I can echo it to the screen. I have not been able to get the count working.
There are very specific fields I am querying:
state_nm
status
loc_type
These are all parameters I enter manually into the query like so:
$_POST["state_nm"] = 'AZ'; ... // and for all other below values..
SELECT *
FROM main_table AS a
INNER JOIN table_1 AS b ON a.id = b.id
INNER JOIN table_2 AS c ON b.id = c.id
INNER JOIN blm table_3 AS d ON c.id = d.id
INNER JOIN table_4 AS e ON d.id = e.id
WHERE a.trq != ''
AND b.state_nm = '".$_POST["state_nm"]."'
AND b.loc_type LIKE \ "%".$_ POST ["loc_type"]."%\"
AND b.STATUS = '".$_POST["status"]."'
GROUP BY b.NAME
ORDER BY c.county ASC;
not sure I get exactly what is your goal here.
anyway, using "select *" and group by in the same query is not recommended and in some databases will raise an error
what I would do is something like that:
select a.name, count(*) from (
SELECT * FROM main_table as a
INNER JOIN table_1 as b
ON a.id=b.id
INNER JOIN table_2 as c
ON b.id=c.id
INNER JOIN blm table_3 as d
ON c.id=d.id
INNER JOIN table_4 as e
ON d.id=e.id
WHERE a.trq != ''
AND b.state_nm = '".$_POST["state_nm"]."'
AND b.loc_type LIKE \"%".$_POST["loc_type"]."%\"
AND b.status = '".$_POST["status"]."'
)a
group by a.name
the basic idea is to add an outer query and use group by on it...
hopefully this solves your problem.
In place of
SELECT *
in your query, you could replace that with
SELECT COUNT(*)
That query should return the number of rows that would be in the resultset for the query using SELECT *. Pretty easy to test, and compare the results.
I think that answers the question you asked. If not, I didn't understand your question.
I didn't notice the GROUP BY in your query.
If you want to get a count of rows returned by that query, wrap it in outer query.
SELECT COUNT(1) FROM (
/* your query here */
) c
That will give you a count of rows returned by your query.