How can I do this with MySQL? - mysql

I am using MySQL and have two database tables as follows:
Users
id username
--------------
1 Bill
2 Steve
Objects
user_id key value
----------------------
1 A X
1 B Y
1 C Z
2 A S
2 C T
What query is required to produce the following result?
username A B C
-------------------
Bill X Y Z
Steve S T
I have tried this with an INNER JOIN, but end up with 5 rows (one for each corresponding object row).
Any help much appreciated.

If 'A', 'B', and 'C' are known beforehand, you can do this:
SELECT users.username,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'A'
) AS a,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'B'
) AS b,
( SELECT objects.value
FROM objects
WHERE objects.user_id = users.id
AND objects.`key` = 'C'
) AS c
FROM users
ORDER
BY users.username
;

select u.username
, oA.value A
, oB.value B
, oC.value C
from users u
left
join objects oA
on u.id = oA.user_id
and oA.key = 'A'
left
join objects oB
on u.id = oB.user_id
and oB.key = 'B'
left
join objects oC
on u.id = oC.user_id
and oC.key = 'C'

perhaps this is not the query that you are asking for, but this is a clean and sipmle query that I have used in your situation:
select objects.*, matrix.*
from
(select users.id, o.key
from users, (select distinct key from objects) as o
)
as matrix left join objects on matrix.id = objects.user_id
and matrix.key = objets.key
order by matrix.id, matrix.key
This query "fills" the empty spaces. So you can consume the resultset with two nested foreach (or whatever similar) and draw the desired table.

Related

SQL Query: Joining 4 tables to one

I tried all the similar questions but I can not get what I want.
Need help with Joining the table.
1. Table bookings:
id = 1 customer_id = 1 booking_details = 'venue etc'
2. Table customers:
id = 1 name = 'John Citizen'
3. Table: booking_objects:
id = 1 booking_id = 1 object_id = 1
id = 2 booking_id = 1 object_id = 2
4. Table objects:
id = 1 name = 'Object 1'
id = 2 name = 'Object 2'
I want to join tables to I get "Venue etc, Jhon Citizen, Oject 1, Object 2"
currently I am getting "Venue etc, Jhon Citizen" using below query
$this->db->select("bookings.bookig_details, customers.name as customer_name, objects.name as object_name")->join("customers", "customers.id = bookings.customer_id", "left outer")
In your requirement, Venue etc, Jhon Citizen, Object 1, Object 2 are mentioned. Object 1 and Object 2 comes under same column i.e, objects.name.
As per my understanding you need booking_details, customer_name, obj_name are your required columns.
select b.booking_details, c.name as customer_name, o.name as obj_name
bookings b
inner join
customers c
on b.customer_id = c.id
inner join
booking_objects bo
on b.id = bo.id
inner join
objects o
on bo.object_id = o.id
You can change the join types based on your requirement.
Instead of using values of the required output, it would have been better to use required columns in your question for clarity.
You need to use Self JOIN
like this.
select b.booking_details,c.name 'customer_name',o1.name 'o1Name',o2.name 'o2Name'
FROM objects o1
join objects o2 ON o1.id = 1 and o2.id = 2
join booking_objects bo on o1.id = bo.object_id
join bookings b on b.customer_id = bo.booking_id
join customers c on bo.booking_id = c.ID
sqlfiddle:http://sqlfiddle.com/#!9/594973/14

How can I accomplish the following in SQL?

I have two tables.
table_a:
id | data_x | data_y
--------------------
1 person joe
2 person bob
3 amount 200
4 addres philville
tableB:
map_id | table_a_id
-------------------
7 1
7 3
7 4
8 4
8 2
The result I want is the map_id if it has an entry in table_a for both data_x = 'person' and data_y = '200'
So with the above table B, the result should be
map_id
------
7
How can I write that query in SQL?
This situation is a perfect fit for an unusual SQL operator: INTERSECT. It is a very declarative, efficient and elegant solution for this problem.
SELECT Map.map_id
FROM Table_B AS Map JOIN Table_A AS Person ON (Person.id = Map.table_a_id) AND (Person.data_x = 'person')
INTERSECT
SELECT Map.map_id
FROM Table_B AS Map JOIN Table_A AS Amount ON (Amount.id = Map.table_a_id) AND (Amount.data_y = '200')
Formally what you are asking for is exactly the intersection of two disjoint sets: the set of map id's that are persons and the set of map id's that have a value of 200.
Please note the INTERSECT operator does not exists in MySQL, but it does in almost all advanced relational DBMS, including PostgreSQL.
This is less elegant than the INTERSECT solution #Malta posted, but it works with the limited capabilities of MySQL as well:
SELECT b1.map_id
FROM table_a a1
JOIN tableb b1 ON a1.id = b1.table_a_id AND a1.data_x = 'person'
JOIN tableb b2 ON b2.map_id = b1.map_id AND b2.table_a_id <> b1.table_a_id
JOIN table_a a2 ON a2.id = b2.table_a_id AND a2.data_y = '200';
SQL Fiddle for MySQL.
SQL Fiddle for Postgres.
Based on your input, the following should get you started using MySQL:
SELECT
map_id
FROM TableB
JOIN Table_A
ON TableB.table_a_id = Table_A.id
AND
((Table_A.data_x = 'person')
OR
(Table_A.data_y = '200')
)
GROUP BY map_id
HAVING COUNT(table_a_id) = 2
;
See it in action: SQL Fiddle.
Update
As Erwin Brandstetter made explicit: If the data can't be trusted to be inherently consistent (along the lines of your inquiry), one option is:
SELECT map_id FROM (
SELECT map_id, 'data_x' t
FROM TableB B JOIN Table_A A ON B.table_a_id = A.id AND A.data_x = 'person'
UNION
SELECT map_id, 'data_y'
FROM TableB B JOIN Table_A A ON B.table_a_id = A.id AND A.data_y = '200'
) T
GROUP BY map_id
HAVING COUNT(DISTINCT t) = 2
;
This should ensure "at least one each". (Alternatives have been suggested by others.) To get "exactly one each", you could try
SELECT map_id FROM (
SELECT map_id, 'data_x' t, data_y
FROM TableB B JOIN Table_A A ON B.table_a_id = A.id AND A.data_x = 'person'
UNION
SELECT map_id, 'data_y', data_y
FROM TableB B JOIN Table_A A ON B.table_a_id = A.id AND A.data_y = '200'
) T
GROUP BY map_id
HAVING COUNT(DISTINCT t) = 2 AND COUNT(DISTINCT data_y) = 2
;
See it in action (with additional test data): SQL Fiddle.
And it works in PostgreSQL as well: SQL Fiddle
Please comment if and as this requires adjustment / further detail.
Join the 2 tables, group by map_id, use conditional counting with either count() or sum(), and filter in having clause (I use mysql syntax below):
select map_id,
sum(
case
when a.data_x='person' or a.data_y='200' then 1
else 0
end
) as matches
from a
inner join b on a.id=b.a_id
group by b.map_id
having matches=2
The above query assumes that you cannot have more than one record for any map_id where data_x is person or data_y is 200. If this assumption is incorrect, then you need to use either exists subqueries or 2 derived tables.
Sounds like you want a standard INNER JOIN.
But I do beg to differ on your result:
map_id if it has an entry in table_a for both data_x = 'person' and data_y = '200'
There is not a record in your data set that has both 'person' and data_y = '200' and therefore no mp_id can be returned
Here is a typical INNER JOIN relating to your narrative.
SELECT DISTINCT
b.map_id
FROM
TableA a
INNER JOIN TableB b
ON a.id = b.table_a_id
WHERE
a.data_x = 'person'
AND a.data_y = '200'
If more than one map_id exists with data_x = 'person' and data_y = '200' then you will get multiple results but only 1 row per map_id
If you want the map_id(s) for records with data_x = 'person' or data_y = '200' then switch the and in the where statement to or and you will receive map_id 7 & 8.
SELECT DISTINCT
b.map_id
FROM
TableA a
INNER JOIN TableB b
ON a.id = b.table_a_id
WHERE
a.data_x = 'person'
OR a.data_y = '200'
Note this encompasses (7,1)(8,2) because 1 & 2 both have data_x = 'person' and then (7,3) because 3 has data_y = '200' therefore it would return map_id 7 & 8.
select map_id from
table_b b
left outer join table_a a1 on (b.table_a_id = a1.id and a1.data_x = 'person')
left outer join table_a a2 on (b.table_a_id = a2.id and a2.data_y = '200')
group by map_id
having count(a1.id) > 0 and count(a2.id) > 0
Lets do it simple:
SELECT * FROM
(
SELECT map_id
FROM table_a a1
inner join TableB b1 ON a1.id = b1.table_a_id
where a1.data_x = 'person'
) as p
inner join
(
SELECT map_id
FROM table_a a1
inner join TableB b1 ON a1.id = b1.table_a_id
where a1.data_y = '200'
) as q
on p.map_id = q.map_id
You may replace SELECT * FROM with SELECT p.map_id FROM.
You may add more sub-set-joins to have more conditions.
sql-fiddle

Selecting from different tables based on condition

I Have a table comments ->
id | comment | type | userid |
1 Hello human 9
2 Hi robot 4
3 Gnaw! animal 1
4 Boo ghost 2
Also i have four more tables human,robot,ghost and animal
These tables contains some basic details about themselves...
Now I have a know value of comment say : $id = 3
if i do
$data = mysqli_query($con,"SELECT type FROM comments WHERE id = $id");
while($row = mysqli_fetch_assoc($data)){
$table = $row['type'];
$table_data = mysqli_fetch_assoc(mysqli_query($con,"SELECT * FROM $table"));
}
this will fetch me all the data about the one who commented but this will prove to be too slow....is there any way i can combine this in one single query ?
One way to do this is with left joins.
SELECT c.type, COALESCE(h.detail,r.detail,a.detail,g.detail)
FROM comments
LEFT JOIN human h ON c.type = 'human' AND c.id = h.id
LEFT JOIN robot r ON c.type = 'robot' AND c.id = r.id
LEFT JOIN animal a ON c.type = 'animal' AND c.id = a.id
LEFT JOIN ghost g ON c.type = 'ghost' AND c.id = g.id
Another way would be to do a UNION on the four tables and then join those:
SELECT c.type, q1.detail
FROM comments c
LEFT JOIN
(
SELECT 'human' AS type, detail FROM human
UNION
SELECT 'robot', detail FROM robot
(etc.)
) q1 ON c.type = q1.type AND q1.id = c.id
I would prefer the second option, because this one makes it easier to join lots of detail-columns. I don't think there's much of a difference perfomance-wise.

(My)SQL JOIN - get teams with exactly specified members

Assume tables
team: id, title
team_user: id_team, id_user
I'd like to select teams with just and only specified members. In this example I want team(s) where the only users are those with id 1 and 5, noone else. I came up with this SQL, but it seems to be a little overkill for such simple task.
SELECT team.*, COUNT(`team_user`.id_user) AS cnt
FROM `team`
JOIN `team_user` user0 ON `user0`.id_team = `team`.id AND `user0`.id_user = 1
JOIN `team_user` user1 ON `user1`.id_team = `team`.id AND `user1`.id_user = 5
JOIN `team_user` ON `team_user`.id_team = `team`.id
GROUP BY `team`.id
HAVING cnt = 2
EDIT: Thank you all for your help. If you want to actually try your ideas, you can use example database structure and data found here: http://down.lipe.cz/team_members.sql
How about
SELECT *
FROM team t
JOIN team_user tu ON (tu.id_team = t.id)
GROUP BY t.id
HAVING (SUM(tu.id_user IN (1,5)) = 2) AND (SUM(tu.id_user NOT IN (1,5)) = 0)
I'm assuming a unique index on team_user(id_team, id_user).
You can use
SELECT
DISTINCT id,
COUNT(tu.id_user) as cnt
FROM
team t
JOIN team_user tu ON ( tu.id_team = t.id )
GROUP BY
t.id
HAVING
count(tu.user_id) = count( CASE WHEN tu.user_id = 1 or tu.user_id = 5 THEN 1 ELSE 0 END )
AND cnt = 2
Not sure why you'd need the cnt = 2 condition, the query would get only those teams where all of users having the ID of either 1 or 5
Try This
SELECT team.*, COUNT(`team_user`.id_user) AS cnt FROM `team`
JOIN `team_user` ON `team_user`.id_team = `team`.id
where `team_user`.id_user IN (1,5)
GROUP BY `team`.id
HAVING cnt = 2

How to optimize this mysql query? Runs slow

I'm using a database that, imho, wasn't designed well, but maybe it's just me not understanding it. Anyways, I have a query that pulls the correct information, but it is really slowing down my php script. I was hoping someone could take a look at this and let me know if nesting queries to this depth is bad, and whether or not there is a way to simplify the query from the relationships depicted in the sql statement below.
SELECT name
FROM groups
WHERE id = (SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (SELECT g.id AS AdminCc
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc')
AND immediateparentid <> (SELECT g.id AS AdminCc
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'))
Please help
Update:
Here is the output from using Explain
You may need to right click and select "View Image" for the text to be clear.
From what I can tell, you can eliminate one sub-select.
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
I'm much more used to PL/SQL on Oracle but I'll give it a try.
Get rid of aliases, you don't need them here.
Make sure columns used in the where clause are indexed (t.Id and g.type).
Don't know if MySQL indexes foreign keys by default but worth the check.
You can shorten your SQL code like that:
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t, groups g
WHERE t.Id = 124 AND t.id = g.instance AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
or:
SELECT name
FROM groups
WHERE id = (
SELECT DISTINCT immediateparentid
FROM cachedgroupmembers
WHERE groupid = (
SELECT g.id
FROM Tickets t inner join groups g on t.id = g.instance
WHERE t.Id = 124 AND g.type = 'AdminCc'
) AND immediateparentid != groupid
)
if your tickets table is big you may consider a temp table instead of querying it twice