Query with sub-results from another table - mysql

I have the following two database tables:
`membership_members` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` tinytext NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
)
`membership_payments` (
`date` date NOT NULL,
`amount` int(11) NOT NULL,
`member` int(11) NOT NULL,
KEY `member` (`member`)
)
I wish to list all the members, and for each of them I would like to show the list of all the payments by that member. The membership_payments.member column is a foreign key membership_members.id (this is MySQL so I cannot explicitly specify a foreign key). Notice that I do want to list a member even if he doesn't have any payments.
So something like:
* John Smith
- 2012-05-06 $100
- 2012-01-02 $100
* Brian Jones
* Mike Jackson
- 2012-09-02 $50
I have tried with this query:
SELECT id, name, active, date, amount
FROM `membership_members`,
`membership_payments`
WHERE membership_members.id = member
This of course gives me tha data I need, but not exactly how I need it as it returns a row for each payment. That means that I later have to group the rows (in PHP) by member id so that I don't list a member multiple times. I can do that, but I believe that it would be easier and faster to do it in SQL. Also, this query only gives me users which have payments with their id.
I feel that this should be simple, but last time I did anything but the most simple stuff in SQL was 6-7 years ago.
EDIT:
LEFT OUTER JOIN suggested in one of the answers solves the issue with the "missing" members. But what is the best way of grouping results by member IDs? I know there is a GROUP BY clause, but it doesn't give me all the payments for the given member, only one. I suppose I can run a new payments query for each member, but I fear this would be very inefficient (we have around 300-400 members).

You'll need to JOIN the two tables based on referencing key columns.
SELECT id, name, active, date, amount
FROM membership_members
LEFT OUTER JOIN membership_payments
ON membership_members.id = membership_payments.member;
I chose LEFT OUTER JOIN so that the members without payments are also shown.
For more info on joins check http://dev.mysql.com/doc/refman/5.0/en/join.html
EDIT
Use ORDER BY membership_members.id to get records ordered by a certain column.
Grouping does not behave like sorting. GROUPING merges all records by the column you provided. ORDER BY sorts

use LEFT JOIN
basically, what you are doing now is an INNER JOIN. INNER JOIN only displays a records if it has atleast one record on each table. But LEFT JOIN operates differently. It displays all records on the Lefthand side table whether it has matching record or not on the righthand side table.
SELECT id, name, active, date, amount
FROM `membership_members` a
LEFT JOIN `membership_payments` b
ON a.ID = b.member
To further gain more knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins

Take a look at GROUP_CONCAT MySQL clause.
Example
OR
You can use CONCAT.
Example
SELECT CONCAT(`membership_payments.date`, ' ', `membership_payments.amount`)
FROM `membership_members`
LEFT OUTER JOIN membership_payments
ON membership_members.id = membership_payments.member;

Related

MySQL select rows from parent table only if records in child table match records in another child table

I have two sets of parent/child tables:
referrals / modalities_match_referrals
locations / modalities_match_providers
There is another table modalities which is a list of all of the modalities that we typically deal with.
For any referral, I need to find a list of providers (locations table) which can perform each of the modalities required by the referral. For example, if the referral requires an MRI and an arthrogram, the list of should be limited to those who can perform both an MRI and an arthrogram, even if such a provider offers other modalities besides the ones needed for this referral.
My question: is there a single query (with subqueries) that will result in a list of only those locations/providers who can perform all of the modalities linked to a referral through the modalities_match_referrals table?
My problem is one of over-inclusion when a referral requires more than one modality. My query is resulting in a list of providers than can perform at least one of the modalities and is including providers that are not able to perform all modalities.
Here are the child/matching tables:
CREATE TABLE modalities_match_referrals (
id INT AUTO_INCREMENT PRIMARY KEY NOT NULL
,referral_id INT NOT NULL
,modality_id INT NOT NULL
,on_off TINYINT DEFAULT 0
,INDEX(referral_id)
,INDEX(modality_id)
,FOREIGN KEY(referral_id) REFERENCES referrals(id)
,FOREIGN KEY(modality_id) REFERENCES modalities(id)
);
CREATE TABLE modalities_match_providers (
id INT AUTO_INCREMENT PRIMARY KEY NOT NULL
,location_id INT NOT NULL
,modality_id INT NOT NULL
,on_off TINYINT DEFAULT 0
,INDEX(location_id)
,INDEX(modality_id)
,FOREIGN KEY(location_id) REFERENCES locations(id)
,FOREIGN KEY(modality_id) REFERENCES modalities(id)
);
What I have tried so far
I have a long production query which is too long to include here, but I am getting the same list of providers from this shorter query and subqueries:
SELECT locations.id AS 'Location ID'
,locations.name AS 'Provider'
FROM locations
JOIN (
SELECT p.location_id
FROM modalities_match_providers AS p
RIGHT JOIN (
SELECT modality_id
FROM modalities_match_referrals
WHERE on_off=1
AND referral_id=9660
) AS r ON r.modality_id = p.modality_id
WHERE on_off=1
) AS l ON l.location_id=locations.id
WHERE locations.recycle=0
AND locations.locationstateid=1
GROUP BY locations.id
ORDER BY locations.name
LIMIT 10;
The results include providers that can perform MRIs and/or arthrograms, and what I am looking for are only those providers who can perform both.
In the past, the modalities where handled as columns in both the referrals and locations tables. The sql was a lot simpler but the issue with that system was having columns to those tables when a new modality was introduced (this happens more than you might think - open MRI vs stand up MRI, etc.)
I'm also thinking of creating a PHP class to be called during the while loop that renders the list onto the webpage. The class would do the comparing between modalities_match_referrals and modalities_match_providers and return true or false. Based on the return value, the provider would be displayed or not displayed. The downside there is complexity and I would be concerned about the performance of the page.
Thank you in advance for any help you can offer.
Cross join the referral modalities with the locations so as to get the optimal result. Then outer join the providers' modalities to see how much is actually covered. Use GROUP BY and HAVINGthe get the referral / location pairs that lack no modality.
select
r.referral_id,
l.id as location_id
from modalities_match_referrals r
cross join locations l
left outer join modalities_match_providers p
on p.referral_id = r.referral_id
and p.modality_id = r.modality_id
and p.location_id = l.id
group by r.referral_id, l.id
having count(*) = count(p.id)
order by r.referral_id, l.id;
This should give you all referrals with all providers that can perform all their modalities.

MySQL "For Each"

I'm trying to make a code question website, like CodeChef.
So far, I have two tables, problems and submissions.
problems consists of a single id field which is auto_incremented. (It really has more fields, but they are not relevant.)
submissions consists of an id field which is auto_incremented, a problem_id field which is foreign key-ed to problems.id, and a bool correct field.
Here are their (simplified) creation statements:
create table problems
(
id bigint auto_increment primary key not null,
/* more fields that are unimportant */
);
create table submissions
(
id bigint auto_increment primary key not null,
problem_id bigint not null,
correct bool not null,
/* more fields */
foreign key (problem_id) references problems(id)
);
Now, what I'm trying to do is the following:
For each id in problems, return a row consisting of that ID, the number of correct submissions for that problem, and the number of total submissions for that problem.
Basically, I want to use the following query:
select
problems.id,
sum(submissions.correct),
count(submissions.id)
from submissions inner join problems
on submissions.problem_id=problems.id and problems.id=[problem_id];
where [problem_id] goes over every id in problems.
It seems like I need to use a sub-query, but I tried it and it did not work at all.
How do I create a query to return these results?
Your query has some defaults.
You do not specify the grouping criteria for the aggregates you use
you join, this is a 'for each' per se. You don't need anything else than the join condition you already specified
Your join should be a left join since maybe there's no correct submission for your problem, leading to 0 results at all in case of an inner join.
This is my proposition:
SELECT
p.id,
SUM(IF(s.correct, 1, 0)) AS correct_submissions,
COUNT(s.id) AS total_submissions
FROM problems p
LEFT JOIN submissions s ON p.id = s.problem_id
GROUP BY
p.id
All you need for your query is a group by clause:
select problems.id,
sum(submissions.correct),
count(submissions.id)
from submissions inner join
problems
on submissions.problem_id=problems.id
group by problems.id;
try
select
problems.id,
sum(submissions.correct),
count(submissions.id)
from submissions inner join problems
on submissions.problem_id=problems.id
where problems.id=[problem_id]
group by problems.id;

Query multiple tables including rows with null foreign keys MySQL

It's my first time with mysql and I need some help. I couldn't find any solution to this probably because it's too easy or I'm too noob to look it up properly yet.
I want to make a query from multiple tables from my test database. Let's say tables are:
category (*id_category, *id_product)
product (*id_product, id_category, id_supplier, id_manufacturer)
manufacturer (*id_manufacturer, name)
supplier (*id_supplier, name)
The columns with * are integer primary key not null with auto_increment(starting from 1). The other id columns are just integer foreign keys(default null).
I need a query also including rows with null id_manufacturer and/or id_supplier on the product table. Those empty fields in the product table return value '0', so when I make a query like
SELECT category.id_product, manufacturer.name, supplier.name
FROM category, product, manufacturer, supplier
WHERE category.id_category = product.id_category AND manufacturer.id_manufacturer = product.id_manufacturer AND supplier.id_supplier = product.id_supplier;
None of the rows with null values(0)are shown.
I'd like you to point me in the most clean and efficient way to do it without changing table properties. Any help would be very much appreciated.
You need to use Left Join then.
SELECT category.id_product, manufacturer.name, supplier.name, P.ID_PRODUCT
FROM product
LEFT JOIN category C
ON C.id_category = P.id_category
LEFT JOIN manufacturer M
ON M.id_manufacturer = P.id_manufacturer
LEFT JOIN supplier S
ON S.id_supplier = P.id_supplier;
These will give all the rows of product table even if it has no corresponding row from other tables.

Dependant SubQuery v Left Join

This query displays the correct result but when doing an EXPLAIN, it lists it as a "Dependant SubQuery" which I'm led to believe is bad?
SELECT Competition.CompetitionID, Competition.CompetitionName, Competition.CompetitionStartDate
FROM Competition
WHERE CompetitionID NOT
IN (
SELECT CompetitionID
FROM PicksPoints
WHERE UserID =1
)
I tried changing the query to this:
SELECT Competition.CompetitionID, Competition.CompetitionName, Competition.CompetitionStartDate
FROM Competition
LEFT JOIN PicksPoints ON Competition.CompetitionID = PicksPoints.CompetitionID
WHERE UserID =1
and PicksPoints.PicksPointsID is null
but it displays 0 rows. What is wrong with the above compared to the first query that actually does work?
The seconds query cannot produce rows: it claims:
WHERE UserID =1
and PicksPoints.PicksPointsID is null
But to clarify, I rewrite as follows:
WHERE PicksPoints.UserID =1
and PicksPoints.PicksPointsID is null
So, on one hand, you are asking for rows on PicksPoints where UserId = 1, but then again you expect the row to not exist in the first place. Can you see the fail?
External joins are so tricky at that! Usually you filter using columns from the "outer" table, for example Competition. But you do not wish to do so; you wish to filter on the left-joined table. Try and rewrite as follows:
SELECT Competition.CompetitionID, Competition.CompetitionName, Competition.CompetitionStartDate
FROM Competition
LEFT JOIN PicksPoints ON (Competition.CompetitionID = PicksPoints.CompetitionID AND UserID = 1)
WHERE
PicksPoints.PicksPointsID is null
For more on this, read this nice post.
But, as an additional note, performance-wise you're in some trouble, using either subquery or the left join.
With subquery you're in trouble because up to 5.6 (where some good work has been done), MySQL is very bad with optimizing inner queries, and your subquery is expected to execute multiple times.
With the LEFT JOIN you are in trouble since a LEFT JOIN dictates the order of join from left to right. Yet your filtering is on the right table, which means you will not be able to use an index for filtering the USerID = 1 condition (or you would, and lose the index for the join).
These are two different queries. The first query looks for competitions associated with user id 1 (via the PicksPoints table), which the second joins with those rows that are associated with user id 1 that in addition have a null PicksPointsID.
The second query is coming out empty because you are joining against a table called PicksPoints and you are looking for rows in the join result that have PicksPointsID as null. This can only happen if
The second table had a row with a null PickPointsID and a competition id that matched a competition id in the first table, or
All the columns in the second table's contribution to the join are null because there is a competition id in the first table that did not appear in the second.
Since PicksPointsID really sounds like a primary key, it's case 2 that is showing up. So all the columns from PickPointsID are null, your where clause (UserID=1 and PicksPoints.PicksPointsID is null) will always be false and your result will be empty.
A plain left join should work for you
select c.CompetitionID, c.CompetitionName, c.CompetitionStartDate
from Competition c
left join PicksPoints p
on (c.CompetitionID = p.CompetitionID)
where p.UserID <> 1
Replacing the final where with an and (making a complex join clause) might also work. I'll leave it to you to analyze the plans for each query. :)
I'm not personally convinced of the need for the is null test. The article linked to by Shlomi Noach is excellent and you may find some tips in there to help you with this.

sql on mysql about join

the code below provide a result too much Infact i want to list the customer that never buy somethink How can i fix the code below
SELECT
webboard.listweb.id,
webboard.listweb.iditempro,
webboard.listweb.url,
webboard.listweb.useradddate,
webboard.listweb.expiredate,
webboard.prorecord.urlpostonweb
webboard.prorecord.urlpostonweb
FROM
webboard.listweb ,
webboard.prorecord
Where listweb.id Not In
(select webboard.prorecord.idlist From webboard.prorecord )
Using the syntax
FROM
webboard.listweb ,
webboard.prorecord
will perform a cartesian, or cross, join on the tables involved. So for every row in the table listweb all the rows in prorecord are displayed.
You need to use an INNER JOIN to only select the rows in listweb that have related rows in the prorecord table. What are the fields which identify the rows (your Primary Keys) and what is the name of the foreign key field in the prorecord table?
EDIT: Just re-read the question and comments and I see you want the rows in listweb which do not have an entry in prorecord
Your SELECT will then look like:
SELECT
webboard.listweb.id,
webboard.listweb.iditempro,
webboard.listweb.url,
webboard.listweb.useradddate,
webboard.listweb.expiredate,
webboard.prorecord.urlpostonweb
-- webboard.prorecord.urlpostonweb -- You have this field twice
FROM webboard.listweb LEFT JOIN webboard.prorecord
ON webboard.listweb.id = webboard.prorecord.idlist -- I'm guessing at the foreign key here
WHERE webboard.prorecord.idlist IS NULL