Subquery returns more than 1 row - mysql

im geting this error when trying to do 2 counts inside of my query
first ill show you the query:
$sql = mysql_query("select c.id, c.number, d.name,
(select count(*) from `parts` where `id_container`=c.id group by `id_car`) as packcount,
(select count(*) from `parts` where `id_container`=c.id) as partcount
from `containers` as c
left join `destinations` as d on (d.id = c.id_destination)
order by c.number asc") or die(mysql_error());
now the parts table has 2 fields that i need to use in the count:
id_car
id_container
id_car = the ID of the car the part is for
id_container = the ID of the container the part is in
for packcount all i want is a count of the total cars per container
for partcount all i want it a count of the total parts per container

It's because of GROUP BY You're using
Try something like
(select count(distinct id_car) from `parts` where `id_container`=c.id)
in You're subquery (can't check right now)
EDIT
PFY - I think UNIQUE is for indexes

Your grouping in your first sub-query is causing multiple rows to be returned, you will probably need to run separate queries to get the results you are looking for.

This subquery may return more than one row.
(select count(*) from `parts` where `id_container`=c.id group by `id_car`) as packcount, ...
so, i'd suggest to try something of the following:
(select count(DISTINCT `id_car`) from `parts` where `id_container`=c.id) as packcount, ...
see: COUNT(DISTINCT) on dev.mysql.com
and: QA on stackoverflow

Related

Fetch rows with MAX DATE and GROUP BY

I have a table name payment_schedule with following contents
I want to fetch records with MAX(due_date) GROUPED BY loan_application_id
With reference to records in above image, i expect the result to be following
I tried using following SQL query
SELECT
id,
MAX(due_date) as due_date,
loan_application_id
FROM
payment_schedule
GROUP BY
loan_application_id
Which returns me the following result.
As you see it does not return the corresponding id for a given due date.
Additionally, I have another column called payment_type_id and I need to exclude rows when payment_type_id has value of 3.
I tried several solution available here, nothing seems to work, how to go about it?
Thanks.
This is called Group-wise Maximum and tagged here as greatest-n-per-group. The most traditional approach is to find the value you want and do a join to get the corresponding row per group like this:
SELECT
ps.id,
ps.due_date,
ps.loan_application_id
FROM
(
SELECT
MAX(due_date) as due_date,
loan_application_id
FROM payment_schedule
WHERE payment_type_id != '3'
GROUP BY loan_application_id
) ps2
LEFT JOIN payment_schedule ps USING (loan_application_id)
WHERE ps.due_date = ps2.due_date
AND ps.payment_type_id != '3'
GROUP BY ps.loan_application_id
It's also worth mentioning that this query will run a bazillion times faster if you have an index on your loan_application_id and due_date columns.
Best discussion I've seen here on SO is this: Select first row in each GROUP BY group?
Also addressed in the official docs here: http://dev.mysql.com/doc/refman/5.7/en/example-maximum-column-group-row.html
If due date per loan_application_id is distinct, you can remove the keyword distinct below:
select distinct a.*
from payment_schedule a, (
select loan_application_id, max(due_date) max_date
from payment_schedule
where payment_type_id <> 3
group by 1
) as b
where a.loan_application_id = b.loan_application_id
and a.due_date = b.max_date
In most databases, this is easiest using window functions. In MySQL, you can use a join and group by:
select ps.*
from payment_schedule ps join
(select load_application_id, max(due_date) as maxdd
from payment_schedule
group by load_application_id
) l
on ps.load_application_id = l.load_application_id and ps.due_date = l.maxdd;

MySQL is not using INDEX in subquery

I have these tables and queries as defined in sqlfiddle.
First my problem was to group people showing LEFT JOINed visits rows with the newest year. That I solved using subquery.
Now my problem is that that subquery is not using INDEX defined on visits table. That is causing my query to run nearly indefinitely on tables with approx 15000 rows each.
Here's the query. The goal is to list every person once with his newest (by year) record in visits table.
Unfortunately on large tables it gets real sloooow because it's not using INDEX in subquery.
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id
Does anyone know how to force MySQL to use INDEX already defined on visits table?
Your query:
SELECT *
FROM people
LEFT JOIN (
SELECT *
FROM visits
ORDER BY visits.year DESC
) AS visits
ON people.id = visits.id_people
GROUP BY people.id;
First, is using non-standard SQL syntax (items appear in the SELECT list that are not part of the GROUP BY clause, are not aggregate functions and do not sepend on the grouping items). This can give indeterminate (semi-random) results.
Second, ( to avoid the indeterminate results) you have added an ORDER BY inside a subquery which (non-standard or not) is not documented anywhere in MySQL documentation that it should work as expected. So, it may be working now but it may not work in the not so distant future, when you upgrade to MySQL version X (where the optimizer will be clever enough to understand that ORDER BY inside a derived table is redundant and can be eliminated).
Try using this query:
SELECT
p.*, v.*
FROM
people AS p
LEFT JOIN
( SELECT
id_people
, MAX(year) AS year
FROM
visits
GROUP BY
id_people
) AS vm
JOIN
visits AS v
ON v.id_people = vm.id_people
AND v.year = vm.year
ON v.id_people = p.id;
The: SQL-fiddle
A compound index on (id_people, year) would help efficiency.
A different approach. It works fine if you limit the persons to a sensible limit (say 30) first and then join to the visits table:
SELECT
p.*, v.*
FROM
( SELECT *
FROM people
ORDER BY name
LIMIT 30
) AS p
LEFT JOIN
visits AS v
ON v.id_people = p.id
AND v.year =
( SELECT
year
FROM
visits
WHERE
id_people = p.id
ORDER BY
year DESC
LIMIT 1
)
ORDER BY name ;
Why do you have a subquery when all you need is a table name for joining?
It is also not obvious to me why your query has a GROUP BY clause in it. GROUP BY is ordinarily used with aggregate functions like MAX or COUNT, but you don't have those.
How about this? It may solve your problem.
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
If you need to show the person, the most recent visit, and the note from the most recent visit, you're going to have to explicitly join the visits table again to the summary query (virtual table) like so.
SELECT a.id, a.name, a.year, v.note
FROM (
SELECT people.id, people.name, MAX(visits.year) year
FROM people
JOIN visits ON people.id = visits.id_people
GROUP BY people.id, people.name
)a
JOIN visits v ON (a.id = v.id_people and a.year = v.year)
Go fiddle: http://www.sqlfiddle.com/#!2/d67fc/20/0
If you need to show something for people that have never had a visit, you should try switching the JOIN items in my statement with LEFT JOIN.
As someone else wrote, an ORDER BY clause in a subquery is not standard, and generates unpredictable results. In your case it baffled the optimizer.
Edit: GROUP BY is a big hammer. Don't use it unless you need it. And, don't use it unless you use an aggregate function in the query.
Notice that if you have more than one row in visits for a person and the most recent year, this query will generate multiple rows for that person, one for each visit in that year. If you want just one row per person, and you DON'T need the note for the visit, then the first query will do the trick. If you have more than one visit for a person in a year, and you only need the latest one, you have to identify which row IS the latest one. Usually it will be the one with the highest ID number, but only you know that for sure. I added another person to your fiddle with that situation. http://www.sqlfiddle.com/#!2/4f644/2/0
This is complicated. But: if your visits.id numbers are automatically assigned and they are always in time order, you can simply report the highest visit id, and be guaranteed that you'll have the latest year. This will be a very efficient query.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT id_people, max(id) id
FROM visits
GROUP BY id_people
)m
JOIN people p ON (p.id = m.id_people)
JOIN visits v ON (m.id = v.id)
http://www.sqlfiddle.com/#!2/4f644/1/0 But this is not the way your example is set up. So you need another way to disambiguate your latest visit, so you just get one row per person. The only trick we have at our disposal is to use the largest id number.
So, we need to get a list of the visit.id numbers that are the latest ones, by this definition, from your tables. This query does that, with a MAX(year)...GROUP BY(id_people) nested inside a MAX(id)...GROUP BY(id_people) query.
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON (p.id_people = v.id_people AND p.year = v.year)
GROUP BY v.id_people
The overall query (http://www.sqlfiddle.com/#!2/c2da2/1/0) is this.
SELECT p.id, p.name, v.year, v.note
FROM (
SELECT v.id_people,
MAX(v.id) id
FROM (
SELECT id_people,
MAX(year) year
FROM visits
GROUP BY id_people
)p
JOIN visits v ON ( p.id_people = v.id_people
AND p.year = v.year)
GROUP BY v.id_people
)m
JOIN people p ON (m.id_people = p.id)
JOIN visits v ON (m.id = v.id)
Disambiguation in SQL is a tricky business to learn, because it takes some time to wrap your head around the idea that there's no inherent order to rows in a DBMS.

mysql select count union

I have something like this:
(SELECT COUNT(*) AS total FROM `transactions` WHERE
`asset`='u_{$user_id}' GROUP BY id)
UNION DISTINCT
(SELECT COUNT(*) AS total FROM transactions tr
INNER JOIN payments pa ON tr.asset = CONCAT('p_', pa.id)
WHERE pa.user_id = '{$user_id}'
GROUP BY tr.id)
It gives 1
Now works like this:
SELECT
(SELECT COUNT(*) FROM `transactions`
WHERE `asset`='u_{$user_id}')
+
(SELECT COUNT(*) FROM transactions tr
INNER JOIN payments pa ON tr.asset = CONCAT('p_', pa.id)
WHERE pa.user_id = '{$user_id}')
It gives 6
But i need to get 5.., sow how to make a right query?
Sure, i can do this by php, but..HOW by sql..?
Really "and" and "or" conditions does not matter, they works correctly, the problem is in counting UNION`ed query. The second query correctly counts summ (1+5), but values ​​intersect queries. The first one gives result of just first subquery. So, i need to unique results before count...
In php, it should look like that: i get transactions id list by inner join with payments, than construct a long query in a loop, to get something like SELECT COUNT(*) FROM transactions WHERE (*what i have now* OR id=$id_1 OR id=$id_2 OR id=$id_3 etc..)
UPD: cutted
RESOLVED!=)
SELECT COUNT(*) FROM(
SELECT tr.* FROM transactions tr
WHERE tr.asset='u_{$user_id}' OR (tr.liability='g' AND tr.l_info='{$user_name}')
UNION SELECT tr.* FROM transactions tr
INNER JOIN payments pa ON tr.asset = CONCAT('p_', pa.id)
WHERE pa.user_id = '{$user_id}' AND pa.status='100')
AS total
AS total is importantly!
I gone through your question, and i think you want to fetch the maximum from the union clause, well i am not aware of the mySql, so i have solved your question in MS-SQL.
Logic:- I have used CTE, and afterwards performed UNION operation and then i have selected MAXIMUM from the two.
WITH COUNTT AS (SELECT 1 AS TEST
UNION
SELECT 5 AS TEST)
SELECT MAX(TEST) FROM COUNTT
And, in place of hardcoded "1" and "5", you can use your count query, i think it is what you are looking for. And, please mark it as an answer.

MySQL's count(*) doesn't work properly

I want to count all rows from the game_votes where g_id is equal to 14, it is working almost good, but if there aren't any records with g_id = 14 it does still show 1 in the cnt field.
Here is my query:
SELECT
SUM(vote) as vote,
COUNT(*) as cnt
FROM (`games`)
LEFT JOIN `game_votes`
ON
`game_votes`.`g_id` = `games`.`id`
WHERE `games`.`id` = '14'
Whats wrong? Am I missing something?
You're getting a count of 1 because COUNT(*) essentially counts the number of rows in your result set. Since you're selecting a row out of your games table with an id of 14 and left joining that to the game_votes table, you're going to always have 1 or more rows as long as there exists a game with an id of 14 regardless of whether or not it contains any corresponding votes in your game_votes table. Instead of COUNT(*), try COUNT(game_votes.g_id). If there are no votes, the g_id field will contain a null value, and COUNT() will not include a null value in its calculation.
Check the result of
SELECT *
FROM (`games`)
LEFT JOIN `game_votes`
ON
`game_votes`.`g_id` = `games`.`id`
WHERE `games`.`id` = '14'
and you will see that your using LEFT JOIN (instead of JOIN) is the cause.
SELECT SUM(vote) as vote, COUNT(*) as cnt from games,game_votes where game_votes.g_id=games.id and games.id=14;
This should work
The MySQL SUM function requires a GROUP BY ... see http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html

mysql intersection, comparison, opposite of UNION?

I'm trying to compare two set of resutls aving hard time to undesrtand how subqueries work and if they are efficient. I'm not gonna explain all my tables, but just think i have apair of arrays...i might do it in php but i wonder if i can do it in mysql right away...
this is my query to check how many items user 1 has in lists he owns
SELECT DISTINCT *
FROM list_tb
INNER JOIN item_to_list_tb
ON list_tb.list_id = item_to_list_tb.list_id
WHERE list_tb.user_id = 1
ORDER BY item_to_list_tb.item_id DESC
this is my query to check how many items user 2 has in lists he owns
SELECT DISTINCT *
FROM list_tb
INNER JOIN item_to_list_tb
ON list_tb.list_id = item_to_list_tb.list_id
WHERE list_tb.user_id = 1
ORDER BY item_to_list_tb.item_id DESC
now the problem is that i would intersect those results to check how many item_id they have in common...
thanks!!!
Unfortunately, MySQL does not support the Intersect predicate. However, one way to accomplish that goal would be to exclude List_Tb.UserId from your Select and Group By and then count by distinct User_Id:
Select ... -- everything except List_Tb.UserId
From List_Tb
Inner Join Item_To_List_Tb
On List_Tb.List_Id = Item_To_List_Tb.List_Id
Where List_Tb.User_Id In(1,2)
Group By ... -- everything except List_Tb.UserId
Having Count( Distinct List_Tb.User_Id ) = 2
Order By item_to_list_tb.item_id Desc
Obviously you would replace the ellipses with the actual columns you want to return and on which you wish to group.