mysql - conditionals with joins - only distinct rows - mysql

Sorry for not precise subject. More precise description is here:
Consider example tables:
City:
cityId | cityName
----------------------
51 | NY
52 | Chicago
53 | SanFrancisco
People:
peopleId | peopleName |
-----------------------
21 | John |
22 | Emma |
23 | Frank |
24 | George |
25 | Albert |
Goods:
goodsId | good | peopleId | cityId
------------------------------------
1 | bread | 21 | 51
2 | steel | 22 | 51
3 | onion | 23 | 0
4 | chair | 22 | 52
5 | knife | 22 | 0
Input data:
cityId: 51
peopleId: (21, 22, 23, 25)
Expected result:
peopleId | peopleName | cityId | cityName | goodId | good
-----------------------------------------------------------
21 | John | 51 | NY | 1 | bread
22 | Emma | 51 | NY | 2 | steel
23 | Frank | 0 | --- | 3 | onion
25 | Albert | --- | --- | --- | ----
22 | Emma | 0 | --- | 5 | knife
Even if there is almost empty data still some row is displayed (here peopleId 25). What is going on here?
I want to select all the goods.good that have people.peopleId in input data (21, 22, 23, 25) and cityId = 51. However not every row in goods table have specified goods.cityId, and also not every goods.peopleId is in goods table. I want my result to be conditionally dependent:
if peopleId exists in goods table and this row have also cityId i am looking - print it.
else if peopleId exists in goods table but goods.cityId = 0 - select it.
else if peopleId doesnt exist in goods table - print only peopleId and peopleName, rest of the fields leave empty.
I already made this:
SELECT DISTINCT people.peopleId, people.peopleName,
CASE
WHEN goods.cityId = 51
THEN (SELECT goods.good FROM goods WHERE goods.zalozenie = 51 AND goods.peopleId = people.peopleId)
ELSE
CASE
WHEN goods.cityId = 0
THEN (SELECT goods.good FROM goods WHERE goods.cityId = 0 AND goods.peopleId = people.peopleId)
ELSE -1
END
END AS goods FROM people LEFT JOIN goods ON people.peopleId = goods.peopleId WHERE people.peopleId IN ( 21, 22, 23, 25 )
This works almost fine but result shows:
peopleId | peopleName | cityId | cityName | goodId | good
-----------------------------------------------------------
21 | John | 51 | NY | 1 | bread
22 | Emma | 51 | NY | 2 | steel
23 | Frank | 0 | --- | 3 | onion
25 | Albert | --- | --- | --- | ----
22 | Emma | 0 | --- | 5 | knife
Unwanted row is the last one - it should only select second line and ignore last one if peopleId = 22 with cityId > 0 is already on result.
Hope you can help me!
Kalreg

I think you are trying to do something like this:
SELECT p.peopleId, p.peopleName, c.cityId, c.cityName, g.goodId, g.good
FROM people p left join goods g
on p.peopleId = g.peopleId
left join city c
on c.cityId = g.cityId
WHERE
(g.goodId is null or g.cityId = 51 or (g.cityId = 0 and NOT EXISTS (SELECT * FROM goods g2 WHERE g2.peopleId=p.peopleId and g2.cityId>0)))
and p.peopleId IN (21, 22, 23, 25);
See this sql fiddle to play around with it a bit. Hopefully that's a starting point, anyway.

Related

What kind of query i need to compare this values

I wanted to add "ID_CHOOSE2", "ID_CHOOSE3" etc to each user, what kind of query would I have to create for it to be correct? I Tried use "OR":
ON w.ID_CHOOSE OR w.ID_CHOOSE2 = m.ID_PERSON AND m.ID_CHOOSE OR m.ID_CHOOSE2 = w.ID_PERSON
And
ON (w.ID_CHOOSE = m.ID_PERSON AND m.ID_CHOOSE = w.ID_PERSON)
OR (w.ID_CHOOSE2 = m.ID_PERSON AND m.ID_CHOOSE2 = w.ID_PERSON)
As you can see, the query is not working well because it is poorly structured.
I mean a comparison of the type: A person chose some person (choose) and another (choose2). Check if the other person chose the first person in all their choices (choose, choose2 etc). If so, they match, if not, do not display and check others people. I want output like:
TOM W | KATE B
MARK K | KATE B
MARK K | ALEX S
My code with desc: https://onecompiler.com/mysql/3xz4552uv
-- EDIT FOR MORE INFO ---
I have a table with men and women (users)
And two other tables containing the choices of men and women
Now I would like to find a match (who matches whom based on choices in "Choose" columns - they are not always in sequence. Sometimes it's 3,2,1 and sometimes it's 1,3,2)
This is illustrated in the example below:
ID_PERSON MAN:
1 - JACK - Choose: 1,3,2 (Mary, Emily, Lilly)
2 - TOM - Choose: 1,2,3 (Mary, Lilly, Emily)
3 - HARRY - Choose: 2,1 (Lilly, Mary)
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 1 | 1 | 1 | 3 | 2 |
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 2 | 2 | 1 | 2 | 3 |
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 3 | 3 | 2 | 1 | NULL(empty) |
ID_PERSON WOMAN:
1 - Mary - Choose: 3,1,2 (Harry, Tom, Jack)
2 - Lilly - Choose: 1,2 (Jack, Harry)
3 - Emily - Choose: 1,2,3 (Jack, Tom, Harry)
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 1 | 1 | 3 | 1 | 2 |
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 2 | 2 | 1 | NULL(empty) | 2 |
| ID | ID_PERSON | ID_CHOOSE | ID_CHOOSE2 | ID_CHOOSE3 |
| 3 | 3 | 1 | 2 | 3 |
Match:
JACK Matched with: Mary, Emily, Lilly
TOM Matched with: Mary, Emily
Harry Matched with: Lilly, Harry
Thanks
Here is an alternative schéma.
See dbFiddle link below.
SELECT * FROM users;
SELECT * FROM choices;
id | name | gender
--: | :------- | :-----
101 | KATE B | F
102 | ALEX S | F
203 | TOM W | M
204 | MARK K | M
205 | DENNIS A | M
chid | chooser | choice
---: | ------: | -----:
1 | 203 | 101
2 | 203 | 102
3 | 204 | 102
4 | 204 | 101
5 | 101 | 203
6 | 101 | 204
7 | 102 | 204
8 | 102 | 205
select
m.id, m.name man,
f.id, f.name woman
from
choices c
join choices s
on c.chooser = s.choice
left join users m on m.id = c.chooser
left join users f on f.id = c.choice
where s.chooser = c.choice
and m.gender = 'M'
and f.gender = 'F';
id | man | id | woman
--: | :----- | --: | :-----
203 | TOM W | 101 | KATE B
204 | MARK K | 101 | KATE B
204 | MARK K | 102 | ALEX S
db<>fiddle here

Get values from 3 tables into a summarized table SQL

I've tried other solutions I've found here but I don't get the correct information back.
I have a table with many different records. A list of names with a status of active. Then I have another table which holds information for each name with a ticket number and then an assignment 'Assigned' and 'Feedback'. Not all names have a ticket.
Then 1 more table that holds a number of hours that goes towards that ticket number.
I want a summary of this information for each name whether there is info there or not. So I started with a subquery here is what I have.
select z.name as 'Name', round(coalesce(sum(x.Hours),0),2) as "Assigned",
round(coalesce(sum(y.Hours),0),2) as "Feedback" from
(select name from namelist where status = 'Active') as z
left join
(select e.realname as "Name", b.id as "Ticket", b.status as "Status", c.value -
COALESCE(sum(a.Hours),0) as "Hours" from user_table e
join ticket_table b ON b.handler_id = e.id
join custom_table c ON c.bug_id = b.id AND c.field_id = 7
left custom_table d ON d.bug_id = b.id AND d.field_id = 15
left hours_table a ON a.Ticket = b.id
where (b.status = 50)
Group By b.id
ORDER BY `Name` ASC, `Status` DESC) x on z.Name= x.Name
left JOIN
(select e.realname as "Name", b.id as "Ticket", b.status as "Status", c.value -
COALESCE(sum(a.Hours),0) as "Hours" from user_table e
join ticket_table b ON b.handler_id = e.id
join custom_table c ON c.bug_id = b.id AND c.field_id = 7
left custom_table d ON d.bug_id = b.id AND d.field_id = 15
left hours_table a ON a.Ticket = b.id
where (b.status = 20)
Group By b.id
ORDER BY `Name` ASC, `Status` DESC) y on z.Name= y.Name
Group by Name
I've changed some of the names around but this is the basic idea. b.status = 50 means Assigned, and 20 means Feedback. Those joins create a table that looks like this:
---------------------------------------------------------------------------------------------------
| Name | Ticket | Status| Hours ((value from custom_table)-(sum from hours table based on ticket))|
| Joe | 234 | 50 | 20 |
| Joe | 235 | 50 | 30 |
| Joe | 236 | 50 | 40 |
| John | 233 | 50 | 10 |
| John | 237 | 50 | 20 |
| John | 238 | 50 | 20 |
---------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------
| Name | Ticket | Status| Hours ((value from custom_table)-(sum from hours table based on ticket))|
| Joe | 134 | 20 | 60 |
| Joe | 135 | 20 | 30 |
| Joe | 136 | 20 | 40 |
| John | 133 | 20 | 70 |
| John | 137 | 20 | 20 |
| John | 138 | 20 | 20 |
---------------------------------------------------------------------------------------------------
-----------------
| Name | Status |
| Joe | Active|
| John | Active|
| Mary | Active|
| Tom | Active|
| John |Inactive|
-----------------
Desired result:
----------------------------
| Name | Assigned| Feedback|
| Joe | 90 | 130 |
| John | 50 | 110 |
| Mary | 0 | 0 |
| Tom | 0 | 0 |
----------------------------
Now the Hours table is c.value which is a 1 to 1 relation subtract sum(hours) from hours table 1 to many relationship.
If I take out one of the joins, the table works. When I put them together like this, the numbers are incorrect. I can get the assigned correct if I only use that join. I can get the feedback numbers correct if I only use the feeback join. However it doesn't work when trying to get either from them. Let me know if you need more info I'll try my best to provide.
Example results:
----------------------------
| Name | Assigned| Feedback|
| Joe | 392 | 145 |
| John | 125 | 94 |
| Mary | 0 | 0 |
| Tom | 0 | 0 |
----------------------------
If I just use the table with status 50.
----------------------------
| Name | Assigned|
| Joe | 90 |
| John | 50 |
| Mary | 0 |
| Tom | 0 |
----------------------------
If I just use the table with status 20.
----------------------------
| Name | Assigned|
| Joe | 130 |
| John | 110 |
| Mary | 0 |
| Tom | 0 |
----------------------------
Don't worry about the custom tables so much, there is a reason they are there but aren't a part of my question. The biggest thing is simply getting the c.value from there, the other join to that table is only for another status, but not relevant to what I'm trying to accomplish.
The two Left Joins seem identical to me apart from the status (unless I am missing something)
Have you tried using a single left join and then an aggregation with a Case statement i.e.
SELECT Name,
SUM(CASE WHEN Status = 50 THEN Hours ELSE 0 END) AS Assigned,
SUM(CASE WHEN Status = 20 THEN Hours ELSE 0 END) AS Feedback
FROM table
GROUP BY Name
I appreciate the answer here is over-simplified but it's more a suggestions since I don't know the content of all the tables mentioned in the query
SQL FIDDLE Example (apologies using SQL SERVER but logic in MySQL is the same for this simple example)
http://sqlfiddle.com/#!18/f77e4/5/0
The output matches the desired results but the issue might lie with another table.
This assumes a single table but the logic should work the same using a left join

how to get count of records from 2 tables based on value in a particular column from 1st table using MySQL

These are the tables I am fetching count from
register
+----+-------------+--------+
| id | empSignupId | cityId |
+----+-------------+--------+
| 42 | 4 | 1 |
| 47 | 3 | 1 |
| 48 | 11 | 1 |
| 54 | 20 | 1 |
| 55 | 21 | 2 |
| 56 | 22 | 2 |
+----+-------------+--------+
guest_list
+-----+------------+-------------+
| id | guestName | empSignupId |
+-----+------------+-------------+
| 103 | Mallica SS | 3 |
| 104 | Kavya | 3 |
| 108 | Vinay BR | 11 |
| 109 | Akash MS | 11 |
+-----+------------+-------------+
cities
+----+---------------+
| id | cityName |
+----+---------------+
| 1 | Bengaluru |
| 2 | Chennai |
| 3 | Sydney |
| 4 | New York City |
| 5 | Shanghai |
| 6 | Chicago |
+----+---------------+
I need to fetch the count of people registered from particular city which includes people, their guests, if guests are not present also it should show the count of people.
This is what I tried
SELECT COUNT(gl.id) + COUNT(rfs.id), ct.cityName, rfs.cityId
FROM register rfs
INNER JOIN cities ct ON ct.id=rfs.cityId
INNER JOIN guest_list gl ON gl.empSignupId = rfs.empSignupId
GROUP BY rfs.cityId;
+-------------------------------+-----------+--------+
| COUNT(gl.id) + COUNT(rfs.id) | cityName | cityId |
+-------------------------------+-----------+--------+
| 8 | Bengaluru | 1 |
+-------------------------------+-----------+--------+
I also need the count of people from other cities to be displayed, since there are no guests from some cities its not returning that count.
Please help me figure this out, I am still new to MySQL.. any help is greatly appreciated.
You fist need to aggregate guest_list table in order to get the number of records per empSignupId:
SELECT empSignupId, COUNT(empSignupId) AS countGuest
FROM guest_list gl
GROUP BY empSignupId
Output:
empSignupId countGuest
----------------------
3 2
11 2
Now you have to use a LEFT JOIN to the derived table above in order to also get the number of records for each city:
SELECT COALESCE(SUM(countGuest), 0) + COUNT(rfs.id), ct.cityName, rfs.cityId
FROM register rfs
INNER JOIN cities ct ON ct.id=rfs.cityId
LEFT JOIN (
SELECT empSignupId, COUNT(empSignupId) AS countGuest
FROM guest_list gl
GROUP BY empSignupId
) gl ON gl.empSignupId = rfs.empSignupId
GROUP BY rfs.cityId;
Output:
COALESCE(SUM(countGuest), 0) + COUNT(rfs.id) cityName cityId
------------------------------------------------------------------
8 Bengaluru 1
2 Chennai 2
Using LEFT JOIN instead of INNER JOIN guarantees that we also get cities without guests.
Demo here
Note: If you also want to get cities without registrations then you need to place the cities table first and use a LEFT JOIN to register.
Use LEFT JOINS and add count(distinct r.empSignupId) + count(distinct g.id):
select
c.id as cityId,
c.cityName,
count(distinct r.empSignupId) + count(distinct g.id) as people_count
from cities c
left join register r on r.cityId = c.id
left join guest_list g on g.empSignupId = r.empSignupId
group by c.id;
The result would be:
| cityId | cityName | people_count |
|--------|---------------|--------------|
| 1 | Bengaluru | 8 |
| 2 | Chennai | 2 |
| 3 | Sydney | 0 |
| 4 | New York City | 0 |
| 5 | Shanghai | 0 |
| 6 | Chicago | 0 |
Demo: http://rextester.com/OTBH14189
If you don't need the rows with 0, change the first LEFT JOIN to an inner JOIN.

solve mysql query

Today I have been asked a question by an interviewer that stated
we have three tables named as table A, B, and C.
Those tables are like this
A B C
------------------ -------------------------- ----------------------------
| ID | ProjectID | | ID | LocationID | aID | | ID | points | LocationID |
------------------ -------------------------- ----------------------------
| 1 | 15 | | 1 | 131 | 1 | | 1 | 123333 | 131 |
| 2 | 15 | | 2 | 132 | 1 | | 2 | 123223 | 132 |
| 3 | 15 | | 3 | 133 | 1 | | 3 | 522 | 211 |
| 4 | 12 | | 4 | 134 | 2 | | 4 | 25 | 136 |
------------------ | 5 | 136 | 2 | | 5 | 25 | 133 |
| 6 | 137 | 3 | | 6 | 25 | 134 |
| 7 | 138 | 1 | | 7 | 25 | 135 |
-------------------------- ----------------------------
now he told me to write a query that sums the points of those locations whose project is 15.
First i wrote the query to get ID's from table A like this
SELECT ID from A where projectID = 15
then i pass this result in table b query just like this
SELECT LocationID FROM B WHERE aID IN ( SELECT ID from A where projectID = 15 )
Then i calculate the sum of these locations just like this
SELECT SUM(points) from C where LocationID IN(SELECT LocationID FROM B WHERE aID IN ( SELECT ID from A where projectID = 15))
My Result is fine and query is correct. But he rejected my answer by saying that this nested IN Clause will slow down the whole process as when we have thousands of records.
Then he gave me another chance to review my answer but i couldn't figure it out.
Is there anyway to optimize this or is there some other way to do the same.
Any help? Thanks
Try this it may solve your problem.
Select SUM(C.points) FROM C JOIN B ON C.LocationID = B.LocationID JOIN A ON B.aID = A.ID where A.ProjectID = 15 GROUPBY A.ProjectID
Try with this....i hope it will work
select sum(c.points) as sum_points
from A a,B b,C c where
a.ID=b.aID and
b.LocationID=c.LocationID
and a.projectID=15

Big Mysql Query (Join counts in the result and modify a string)

I am brand new to mysql so please excuse my level of knowledge here, and feel free to direct me in a better direction if what I am doing is out of date.
I am pulling information from a database to fill in a php page.
My tables:
Server:
|ServerID (int) | ArticleID (int) | locationID (int) |
| 1 | 46 | 55 |
| 2 | 11 | 81 |
| 3 | 81 | 46 |
| 4 | 55 | 11 |
| 5 | 81 | 99 |
| 5 | 11 | 52 |
Article:
|ArticleID (int) | Name (varchar) | Typ (int) |
| 46 | domain | 0 |
| 81 | root-server | 1 |
| 55 | vserver | 2 |
| 11 | root-server2 | 1 |
Location:
|LocationID (int) | location (varchar) |
| 46 | 1-5-15-2 |
| 81 | 1-5-14-2 |
| 55 | 2-25-1-9 |
| 11 | 21-2-5-8 |
| 99 | 17-2-5-8 |
| 52 | 1-8-5-8 |
Result:
|location (int) | name (varchar) | count (int) |
| 1 | root-server | 1 |
| 1 | root-server2 | 2 |
| 17 | root-server | 1 |
The location in the result is the first number block of the location in the location table (1-5-15-2 -> 1, 1-8-5-8 -> 1, 21-2-5-8 -> 21, 17-2-5-8 -> 17).
The count is the sum of all servers with the same name and the same first location block.
Do anyone think its possible to get this result in only one query?
Thanks for any answer!
Check this
SELECT SUBSTRING_INDEX(location, '-', 1) as LID,Article.Name,Count(*) as Count
from Location join Server
on Server.locationID=Location.locationID
join Article on Article.ArticleID=Server.ArticleID
group by LID,Article.ArticleID ;
DEMO
Please give this a shot:
SELECT
s.locationID as id, a.name, count(*) as count
FROM
`Server` s
LEFT JOIN
`Article` a ON s.ArticleID = a.ArticleID
GROUP BY s.locationID, a.name
something like this should work
select
s.location_id as location, a.name, count(location) as count
from
server as s, article as a
where
s.articleID = a.articleID
group by location, a.name