I need help on selection statement in mySQL - mysql

I am not familiar with the advance queries in mySQL. So please kindly help me.
clubs table (has more than these fields)
cid | clubname |
----+-----------
12 | club one
13 | club two
members table ( has more fields)
mid | cid | name | desig
----+-----+------+------
52 | 12 |peter | President
53 | 12 |Sam | Member
54 | 12 |Tiger | Secretary
55 | 12 |Sila | Member
56 | 13 |Suzy | Member
57 | 13 |tim | Member
58 | 13 |dave | President
59 | 13 |mark | Secretary
60 | 13 |rita | Member
Desired Resultant record set so that I don't have to run various loops on the page.
clubname | Presname | secname | totcount
---------+-----------+---------+---------
club one | peter | Tiger | 4
club two | dave | mark | 5
I had tried the following mySQL queries, but this takes a long, long time to execute:
SELECT c.clubname,
(select name from members where desig = 'President' and cid= c.cid) as presname,
(select name from members where desig = 'Secretary' and cid= c.cid) as secname
FROM clubdetails c, members m
where c.cid = m.cid
group by c.clubname
Any help would be appreciated. Thank you so much.

You don't provide enough data to diagnose why the query is slow. But, in any case, I would rewrite the query to use joins:
select c.clubname, p.name as presname, s.name as secname
from clubs c
left join members p
on p.cid = c.cid
and p.desig = 'President'
left join members s
on p.cid = c.cid
and p.desig = 'Secretary'
EDIT
Here is another way to write the query that allows you to include the count in the result:
select c.clubname,
max(case when m.desig = 'President' then m.name end) as presname,
max(case when m.desig = 'Secretary' then m.name end) as secname,
count(m.mid) as totcount
from clubs c
left join members m
on m.cid = c.cid
group by c.cid, c.clubname

you could rearrange your query using two joins, one for getting the presidents, another for getting the secretaries
SELECT c.clubname,
mp.name as presname,
ms.name as secnam,
(SELECT
count(*)
FROM members mc
WHERE mc.cid=c.id
) as members_count
FROM clubs c
LEFT OUTER JOIN members as mp ON (mp.cid=c.cid AND mp.desig='President')
LEFT OUTER JOIN members as ms ON (ms.cid=c.cid AND ms.desig='Secretary')
and be sure that you have index for the table members for the columns cid + desig.
Have fun!

Try This
ALTER TABLE clubs
ADD FOREIGN KEY (cid)
REFERENCES members(cid)
SELECT c.clubname,
(SELECT name FROM members m WHERE desig LIKE 'President' and m.cid = c.cid) as presname,
(SELECT name FROM members m WHERE desig LIKE 'Secretary' and m.cid = c.cid) as secname,
FROM clubdetails c
GROUP BY c.clubname

Related

MySQL Query JOIN 3 tables

Looking to join 3 tables, but having trouble on the last one.
members
ID | name
---------
1 | John
2 | Jane
3 | Jack
member_points (can have multiple transactions between members)
ID | date | id_from | id_to
---------------------------
1 | 8/8 | 1 | 2
2 | 8/8 | 1 | 2
3 | 8/8 | 3 | 2
member_ratings (one member can only rate another member, one time)
ID | id_from | id_to | rating
-----------------------------
1 | 2 | 1 | 5
Each member may rate each member only once, and can only rate the member they received a point from, based on the member_points table.
My current query achieves this, however I'm having difficulty introducing the 3rd table, that will include the rating accoringly.
Here is what I have so far:
$sql = '
SELECT *,
m.id AS id,
c1.id AS id_from,
c1.name AS name_from,
c2.id AS id_to,
c2.name AS name_to
FROM member_points AS m
JOIN members AS c1 ON m.id_from = c1.id
JOIN members AS c2 ON m.id_to = c2.id
and m.id_to='.$_SESSION["userid"].'
GROUP BY name_from';
My goal is join the 3rd table so I can call the associated rating.
ID | name_from | name_to | rating
----------------------------------
1 | 2 | 1 | 5
2 | 2 | 3 | pending
#Andrew you need to use a LEFT JOIN to join in the member_ratings table like this:
$sql = '
SELECT *,
m.id AS id,
c1.id AS id_from,
c1.name AS name_from,
c2.id AS id_to,
c2.name AS name_to,
IF(mr.rating IS NULL, 'pending', mr.rating) AS rating
FROM member_points AS m
JOIN members AS c1 ON m.id_from = c1.id
JOIN members AS c2 ON m.id_to = c2.id
and m.id_to='.$_SESSION["userid"].'
LEFT JOIN member_ratings AS mr ON mr.id_from = c1.id
AND mr.id_to = c2.id
GROUP BY name_from';
please let me know if this isn't what you need and I'll try to help you further.

MYSQL - Group Contact rows with records NOT IN

My case looks simple but i'm messing around with this..
I have 4 tables: User, Macros, Categories, and another one that relate users with categories. One Macro have many Categories.
What i need, is a query that based on the Macro, get the users and the Categories where user is NOT IN.
Example: I have a macro named VEICULES, with categories CAR,TRUCK and Motorcycle. User José is on category CAR and User Julio on category CAR and TRUCK, so my query should return:
José | TRUCK,Motorcycle
Julio | Motorcycle
Tables:
prd_users
id | name | Email
---------------------------
1 | José | jose#email.com
2 | Júlio | julio#email.com
3 | André | andre#email.com
cat_macros
macro_id | macro_name
-----------------------
1 | Veicules |
cat_categories
category_id | category_name | macro_id
---------------------------------------
1 | Cars | 1
2 | Trucks | 1
3 | Motorcycles | 1
prd_tr_rabbit_catg
id | category_id | tasker_user_id
---------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
I'm stucked on just getting the categories where the user already is ..
SELECT prd_users.id, prd_users.name,
prd_users.email,cat_macros.macro_name as macro,
GROUP_CONCAT(cat_categories.category_name SEPARATOR ', ') as in_categories
FROM prd_users
INNER JOIN prd_tr_rabbit_catg ON prd_tr_rabbit_catg.tasker_user_id = prd_users.id
INNER JOIN cat_categories ON cat_categories.category_id = prd_tr_rabbit_catg.category_id
INNER JOIN cat_macros ON cat_macros.macro_id = cat_categories.macro_id
WHERE cat_macros.macro_id = '45'
GROUP BY prd_users.id;
To solve this problem it's necessary to create a list of all users joined with all categories for the given macro category. This can be done with a CROSS JOIN:
SELECT *
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
This can then be LEFT JOINed to the prd_tr_rabbit_catg table and by selecting those rows where there is no matching entry in the prd_tr_rabbit_catg table, we can find the users who don't have an entry for the given category:
SELECT c.macro_name, u.id AS user_id, u.name, u.Email, GROUP_CONCAT(c.category_name) AS missing_cats
FROM prd_users u
CROSS JOIN (SELECT m.macro_id, m.macro_name, c.category_name, c.category_id
FROM cat_macros m
JOIN cat_categories c ON c.macro_id = m.macro_id) c
LEFT JOIN prd_tr_rabbit_catg x ON x.tasker_user_id = u.id AND x.category_id = c.category_id
WHERE x.id IS NULL
AND c.macro_id = 1
GROUP BY c.macro_name, u.id
For your sample data, this gives:
macro_name user_id name Email missing_cats
Veicules 1 José jose#email.com Motorcycles,Trucks
Veicules 2 Júlio julio#email.com Motorcycles
Veicules 3 André andre#email.com Cars,Motorcycles,Trucks
Update
To exclude users who don't have any of the categories, add a HAVING clause:
HAVING COUNT(*) < (SELECT COUNT(*) FROM cat_categories WHERE macro_id = 1)
Demo on SQLFiddle

confused how to go about the query..self join or through subqueries?

Coincidence. Have two guests with the same surname ever stayed in the hotel on the evening? Show the last name and both first names. Do not include duplicates.
the result should be in following format
+-----------+------------+-------------+
| last_name | first_name | first_name |
+-----------+------------+-------------+
| Davies | Philip | David T. C. |
| Evans | Graham | Mr Nigel |
| Howarth | Mr George | Sir Gerald |
| Jones | Susan Elan | Mr Marcus |
| Lewis | Clive | Dr Julian |
| McDonnell | John | Dr Alasdair |
+-----------+------------+-------------+
below correct solution attached:
SELECT DISTINCT c.last_name, c.first_name,d.first_name
FROM booking a
JOIN booking b ON a.booking_date <= b.booking_date AND DATE_ADD(a.booking_date, INTERVAL (a.nights -1) DAY) >= b.booking_date AND a.booking_id != b.booking_id
JOIN guest c ON c.id = a.guest_id
JOIN guest d ON d.id = b.guest_id
WHERE concat(a.guest_id, '_', b.guest_id) IN
(
SELECT concat(a.id, '_', b.id)
FROM guest a
JOIN guest b ON a.last_name = b.last_name AND a.first_name > b.first_name
)
ORDER BY 1
It uses self - join twice. First time, to obtain a table of all pairs containing bookings that occurred on given evening. From all the pairs I've selected only those where guest are sharing the same last name (here I'm using self-join second time).
You will need a self join to join the table with self and the DISTINCT clause to remove duplicates.
SELECT DISTINCT g1.last_name, g1.first_name, g2.first_name
FROM Guests g1
INNER JOIN
Guests g2
ON g1.last_name = g2.last_name

sql query combine two queries into one with empty rows

This is my sql table structure:
Table1: details
|--id--|--id_user--|--price--|
| 1 | 1 | 10 |
| 2 | 2 | 15 |
| 3 | 1 | 25 |
| 4 | 3 | 30 |
| 5 | 3 | 7 |
------------------------------
Table2: users
|--id--|--id_country--|
| 1 | 1 |
| 2 | 2 |
| 3 | 0 |
-----------------------
Table3: country
|--id--|--country--|
| 1 | France |
| 2 | Italy |
--------------------
What I need is to get the SUM of price by country:
SELECT c.country, SUM(d.price) AS price
FROM details d
INNER JOIN users u ON u.id = d.id_user
INNER JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
I get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
-----------------------
BUT I'd need to get this:
|--country--|--price--|
| France | 35 |
| Italy | 15 |
| Undefined | 37 |
-----------------------
where undefined would be if id_country=0. (I can't add to country table the id=0 or id=undefined, it will messed up other things). Right now I'm achieving this by two separate queries, the second one is:
SELECT SUM(d.price) as price
FROM details d
INNER JOIN users u ON u.id = d.id_user AND u.id_country=0
GROUP BY u.id_country
I'm thinking if... is it possible to do this in one query?
You need to use left join in this case:
SELECT c.country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
If you use INNER JOIN, you will only get results that exists in both tables.
To replace NULL with Undefined use:
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
One way to sort to get Undefined last is to add a Sortfield
SELECT A.Country,A.Price FROM (
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price, IFNULL(c.Country,'ZZZZZZZZ') AS Sort
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
) A
ORDER BY A.Sort
Edit: ORDER BY suggested in comments
SELECT IFNULL(c.country,'Undefined') AS Country, SUM(d.price) AS price
FROM details d
LEFT JOIN users u ON u.id = d.id_user
LEFT JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country IS NULL, c.country
Try below query.
SELECT
CASE
WHEN c.country is NULL THEN 'Undefined'
ELSE c.country
END as country
, SUM(d.price) AS price
FROM users u
left JOIN details d ON u.id = d.id_user
left JOIN country c ON c.id = u.id_country
GROUP BY c.country
ORDER BY c.country
For Demo :
SqlfiddlE Demo :
Please let us know if you have any que.

User has many computers, computers have many attributes in different tables, best way to JOIN?

I have a table for users:
USERS:
ID | NAME |
----------------
1 | JOHN |
2 | STEVE |
a table for computers:
COMPUTERS:
ID | USER_ID |
------------------
13 | 1 |
14 | 1 |
a table for processors:
PROCESSORS:
ID | NAME |
---------------------------
27 | PROCESSOR TYPE 1 |
28 | PROCESSOR TYPE 2 |
and a table for harddrives:
HARDDRIVES:
ID | NAME |
---------------------------|
35 | HARDDRIVE TYPE 25 |
36 | HARDDRIVE TYPE 90 |
Each computer can have many attributes from the different attributes tables (processors, harddrives etc), so I have intersection tables like this, to link the attributes to the computers:
COMPUTER_PROCESSORS:
C_ID | P_ID |
--------------|
13 | 27 |
13 | 28 |
14 | 27 |
COMPUTER_HARDDRIVES:
C_ID | H_ID |
--------------|
13 | 35 |
So user JOHN, with id 1 owns computer 13 and 14. Computer 13 has processor 27 and 28, and computer 13 has harddrive 35. Computer 14 has processor 27 and no harddrive.
Given a user's id, I would like to retrieve a list of that user's computers with each computers attributes.
I have figured out a query that gives me a somewhat of a result:
SELECT computers.id, processors.id AS p_id, processors.name AS p_name, harddrives.id AS h_id, harddrives.name AS h_name,
FROM computers
JOIN computer_processors ON (computer_processors.c_id = computers.id)
JOIN processors ON (processors.id = computer_processors.p_id)
JOIN computer_harddrives ON (computer_harddrives.c_id = computers.id)
JOIN harddrives ON (harddrives.id = computer_harddrives.h_id)
WHERE computers.user_id = 1
Result:
ID | P_ID | P_NAME | H_ID | H_NAME |
-----------------------------------------------------------
13 | 27 | PROCESSOR TYPE 1 | 35 | HARDDRIVE TYPE 25 |
13 | 28 | PROCESSOR TYPE 2 | 35 | HARDDRIVE TYPE 25 |
But this has several problems...
Computer 14 doesnt show up, because it has no harddrive.
Can I somehow make an OUTER JOIN to make sure that all computers show up, even if there a some attributes they don't have?
Computer 13 shows up twice, with the same harddrive listet for both. When more attributes are added to a computer (like 3 blocks of ram), the number of rows returned for that computer gets pretty big, and it makes it had to sort the result out in application code. Can I somehow make a query, that groups the two returned rows together? Or a query that returns NULL in the h_name column in the second row, so that all values returned are unique?
EDIT:
What I would like to return is something like this:
ID | P_ID | P_NAME | H_ID | H_NAME |
-----------------------------------------------------------
13 | 27 | PROCESSOR TYPE 1 | 35 | HARDDRIVE TYPE 25 |
13 | 28 | PROCESSOR TYPE 2 | 35 | NULL |
14 | 27 | PROCESSOR TYPE 1 | NULL | NULL |
Or whatever result that make it easy to turn it into an array like this
[13] =>
[P_NAME] =>
[0] => PROCESSOR TYPE 1
[1] => PROCESSOR TYPE 2
[H_NAME] =>
[0] => HARDDRIVE TYPE 25
[14] =>
[P_NAME] =>
[0] => PROCESSOR TYPE 1
Use LEFT JOIN instead of INNER JOIN. The two joins are different from each other. The INNER JOIN (which you are currently using) only returns the records which both has at least one match on either side of the table. And that is why computer 14 won't should because it has no match on table COMPUTER_HARDDRIVES. LEFT JOIN on the other hand, returns all records on the left hand side table whether it no match on the other side of the tables.
SELECT a.ID AS UserID,
a.Name as UserName,
b.ID as ComputerID,
d.ID as ProcessorID,
d.Name as ProcessorName,
f.ID as HardDriveID,
f.name as HardDriveName
FROM users a
INNER JOIN computers b
ON a.ID = b.user_ID
LEFT JOIN computer_processors c
ON b.ID = c.C_ID
LEFT JOIN PROCESSORS d
ON c.p_ID = d.ID
LEFT JOIN COMPUTER_HARDDRIVES e
ON b.ID = e.c_ID
LEFT JOIN HARDDRIVE f
ON e.h_ID = f.ID
WHERE a.ID = 1
See SQLFiddle Demo
for more details about joins, see Visual Representation of SQL Joins
and since you want to groupt the rows together, youcan take advantage of GROUP_CONCAT() function. basically, what it does is it combines the value of the columns into comma separated value
SELECT a.ID AS UserID,
a.Name as UserName,
b.ID as ComputerID,
GROUP_CONCAT(DISTINCT d.ID) as ProcessorID,
GROUP_CONCAT(DISTINCT d.Name) as ProcessorName,
GROUP_CONCAT(DISTINCT f.ID) as HardDriveID,
GROUP_CONCAT(DISTINCT f.name) as HardDriveName
FROM users a
INNER JOIN computers b
ON a.ID = b.user_ID
LEFT JOIN computer_processors c
ON b.ID = c.C_ID
LEFT JOIN PROCESSORS d
ON c.p_ID = d.ID
LEFT JOIN COMPUTER_HARDDRIVES e
ON b.ID = e.c_ID
LEFT JOIN HARDDRIVE f
ON e.h_ID = f.ID
WHERE a.ID = 1
GROUP BY UserID, UserName, ComputerID
See SQLFiddle Demo
Try:
SELECT computers.id,
group_concat(distinct processors.id) AS p_ids,
group_concat(distinct processors.name) AS p_names,
group_concat(distinct harddrives.id) AS h_ids,
group_concat(distinct harddrives.name) AS h_names
FROM computers
JOIN computer_processors ON (computer_processors.c_id = computers.id)
JOIN processors ON (processors.id = computer_processors.p_id)
LEFT JOIN computer_harddrives ON (computer_harddrives.c_id = computers.id)
LEFT JOIN harddrives ON (harddrives.id = computer_harddrives.h_id)
WHERE computers.user_id = 1
group by computers.id
SELECT computers.id, processors.id AS p_id, processors.name AS p_name, harddrives.id AS h_id, harddrives.name AS h_name
FROM computers
LEFT JOIN computer_processors ON (computer_processors.c_id = computers.id)
LEFT JOIN processors ON (processors.id = computer_processors.p_id)
LEFT JOIN computer_harddrives ON (computer_harddrives.c_id = computers.id)
LEFT JOIN harddrives ON (harddrives.id = computer_harddrives.h_id)
WHERE computers.user_id = 1
select u.ID user_id,
u.name user_name
p.id,
p.name,
h.id,
h.name
from USERS u join COMPUTERS c
on u.id=c.USER_ID
join PROCESSORS p
on c.id=p.id
join HARDDRIVES h
on c.id=h.id
where u.id=1