I'm currently working on a filter form to display a set of products. The product name is located in TABLE a with the filter tags in TABLE b. As filters are selected, the query needs to return all products from table a that fits all criteria selected.
My current query is (this example has two filters selected):
SELECT a.genus
,a.botanical
FROM a
WHERE a.ID IN (
SELECT filter.filtered
FROM (
SELECT b.ID AS filtered
,COUNT(*) c
FROM b
WHERE b.tag_ID = 1
OR b.tag_ID = 2
GROUP BY b.ID
HAVING c = 2
) AS filtered
)
So, first things first, this does work; I wonder, however, if I'm missing some stupidly easy way to simplify this query . . . or should I accept this and move on?
SQL Fiddle here
I had attempted many different types of joins with no success. If only one filter would ever be selected, and simple LEFT JOIN would be perfect. I run into issues when I need to handle more than one filter selection.
Solution 1 using subquery:
SELECT a.genus, a.botanical
FROM a
WHERE a.ID IN (
SELECT b.ID
FROM b
WHERE b.tag_ID = 1
OR b.tag_ID = 2
GROUP BY b.ID
HAVING COUNT(*) = 2
)
Solution 2 using INNER JOIN:
SELECT a.genus, a.botanical
FROM a INNER JOIN
( SELECT b.ID
FROM b
WHERE b.tag_ID IN (1,2)
GROUP BY b.ID
HAVING COUNT(*) = 2 ) b2
USING(ID)
You can try with cross apply like this,
SELECT a.genus
,a.botanical
FROM a
CROSS APPLY (
SELECT b.ID
,COUNT(*) c
FROM b
WHERE (
b.tag_ID = 1
OR b.tag_ID = 2
)
AND a.ID = b.id
GROUP BY b.ID
HAVING c = 2
) b
Related
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)
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.
I'm not that into MySQL joins, so maybe you could give me a hand. I've got the following tables:
Table a
Fields ID,name
Table b
Fields aID,cID,ID,found
Table c
Fields ID,name
The result I want to get is the following: I want all the records where b.found = 1. Of these records I don't want a.id or a.name, but I want the number of records that would have been returned if I would have wanted so. So if there are five records that have b.found = 1 and c.id = (for example) 3, then I want a returned value of 5, c.id and c.name.
Someone is able to do this?
Actually this is what I want to get from the database:
A list of all records in table C and a count of records in table B that has found = 1 and b.c_id = c.id
Table: a
Fields: ID, name
Table: b
Fields: aID, cID, found
Table: c
Fields: ID, name
SELECT c.ID, c.name, COUNT(1)
FROM b
JOIN c ON c.ID = b.cID AND b.found=1
GROUP BY c.ID
SELECT c.id, c.name, COUNT(*)
FROM c
INNER JOIN b
ON c.id = b.c_id
AND b.found = 1
GROUP BY c.id, c.name
SELECT COUNT(*), c.id, c.name
FROM a, b, c
WHERE a.id = b.a.id AND c.id = b.a.id AND b.found = 1 AND c.id = idThatIAmSearchingFor
Apologies if I didn't get the syntax exact, but I believe that's the basic structure you want. The COUNT function returns the number of rows found by the query.
Something like:
SELECT count(`c`.*),
`c`.`id`,
`c`.`name`
FROM `b`
JOIN `c`
ON `c`.`id` = `b`.`c_id`
WHERE `b.found` = 1
I think this would provide the required output -
select count(*), b.cID, c.name from b
inner join c on c.id=b.cID and b.found=1
group by b.cID
SELECT COUNT(*) AS Count, c.id, c.name
FROM b join a on a.id = b.a_id
WHERE b.found = 1
GROUP BY c.Id;
COUNT returns count of records in each group from GROUP BY.
This is the query I'm performing (without some Joins that are not relevant):
SELECT a.*, c.id
FROM a
LEFT OUTER JOIN b ON a.id = b.id_anunciante
LEFT OUTER JOIN c ON c.id = b.id_rubro
GROUP BY a.id
Each row of "a" is linked with 1 to 5 rows in "b".
The problem is that GROUP BY has performance issues (it takes 10x or more using GROUP BY than not using it). I need to retrieve only one row of each member in "a".
How can I make this faster?
edit: I need to be able to filter by a.id AND/OR c.id. The resultset I should be getting is only 1 row per "valid" member of "a", meaning the rows that match the constraints. Rows that don't match the filters shouldn't be returned.
In my original query, this would be done this way:
SELECT a.*, c.id
FROM a
LEFT OUTER JOIN b ON a.id = b.id_anunciante
LEFT OUTER JOIN c ON c.id = b.id_rubro
WHERE c.id = 1
OR a.id = 1
GROUP BY a.id
a.id, b.id_anunciante, b.id_rubro, c.id are all indexes.
SELECT a.*,
(
SELECT c.id
FROM b
JOIN с
ON c.id = b.id_rubro
WHERE b.id_anunciante = a.id
-- add the ORDER BY condition to define which row will be selected.
LIMIT 1
)
FROM a
Create the index on b (id_anunciante) for this to work faster.
Update:
You don't need the OUTER JOINs here.
Rewrite your query as this:
SELECT a.*, c.id
FROM a
JOIN b
ON b.id_anunciante = a.id
JOIN c
ON c.id = b.id_rubro
WHERE a.id = 1
UNION ALL
SELECT a.*, 1
FROM a
WHERE EXISTS
(
SELECT NULL
FROM c
JOIN b
ON b.id_rubro = c.id
WHERE c.id = 1
AND b.id_anunciante = a.id
)
Add ORDER BY NULL to avoid the implicit sorting MySQL does when doing a group by.
I suppose you have indexes/PKs on a.id, b.id_anunciante, b.id_rubro and c.id ? I guess you could try adding a composite index on (b.id_anunciante, b.id_rubro) if your mysql version is not able to do an index merge.