MySQL multiple left join issues - mysql

I have two tables that I am trying to query, and I can only get about half the information I need. The two tables are:
client_skills_new:
+----+-----------+------------+----------+-------------+
| id | client_id | job_sector | job_type | job_name |
+----+-----------+------------+----------+-------------+
| 79 | 24 | 3 | 39 | Accountant |
+----+-----------+------------+----------+-------------+
| 80 | 25 | 3 | 115 | Broker |
+----+-----------+------------+----------+-------------+
| 81 | 24 | 5 | 241 | Shop Worker |
+----+-----------+------------+----------+-------------+
and
job_sectors:
+-----+--------------------------+---------------+
| id | name | job_sector_id |
+-----+--------------------------+---------------+
| 3 | Accounting & Finance | 0 |
+-----+--------------------------+---------------+
| 115 | Brokerage | 3 |
+-----+--------------------------+---------------+
| 22 | Sales & Retail | 0 |
+-----+--------------------------+---------------+
The job sectors table actually contains job sectors and job types in one column (name). job_sector id links the two (ie brokerage is a subheading of accounting and finance - job_sector_id = id).
In the client_skills_new table the numbers stored under job_sector and job_type relate to the id column of job_sectors. What I am trying to do is write a query which will join the two to give me the textual value in the job_sectors table related to the job_sector and job_type integers in client_skills_new.
So far I have a query as follows:
SELECT client_skills_new.job_sector, job_sectors.id, job_sectors.name
FROM job_sectors
LEFT JOIN client_skills_new
ON client_skills_new.job_sector = job_sectors.id
WHERE client_id='$client_id';
From this, I get results as follows:
+------------+----+--------------------------+
| job_sector | id | name |
+------------+----+--------------------------+
| 3 | 3 | Accounting & Finance |
+------------+----+--------------------------+
| 22 | 22 | Sales & Retail |
+------------+----+--------------------------+
Which given id of 24 from my top table gives me about half of what I need. I'd like the query to also include the text name corresponding to job_type. I'm not quite sure what I need to add to finish the query. I tried a second left join but this kept erroring.
The output I'm looking for, given each id in client_skills_new, is as follows:
+----+--------------------------+-----------+----------+
| id | job_sector | job_type | job_name |
+----+--------------------------+-----------+----------+
| 80 | Accounting & Finance | Brokerage | Broker |
+----+--------------------------+-----------+----------+

Yes, you need 2 LEFT joins but the other way around:
SELECT c.id,
js.name AS job_sector,
jt.name AS job_type,
c.job_name
FROM client_skills_new AS c
LEFT JOIN job_sectors AS js
ON c.job_sector = js.id
LEFT JOIN job_sectors AS jt
ON c.job_type = jt.id
WHERE c.client_id='$client_id' ;

The problem is the where clause. It is "undoing" the left outer join, because the values in one table are NULL when there is no match.
The fix is to move the logic to the on clause:
SELECT client_skills_new.job_sector, job_sectors.id, job_sectors.name
FROM job_sectors LEFT JOIN
client_skills_new
ON client_skills_new.job_sector = job_sectors.id and
client_id='$client_id';

Related

Combining three SQL queries into one

I got working code from three queries but I would like to combine them into one or two. Basically I am checking if a provided phone number exists in table contacts or leads as well as if it exists as a secondary number in customfieldsvalues (not all leads have a customfield value though). I am using a CRM system based on CodeIgniter.
What I want to do (non-correct/hypothetical query):
SELECT * FROM contacts OR leads WHERE phonenumber = replace(X, '-', '')
OR leads.id = customvaluefields.relid AND cfields.fieldid = 41 AND cfields.value = X
Tables
table : contacts
+-------+----------------+----------------+
| id | firstname | phonenumber |
+-------+----------------+----------------+
| 1 | John | 214-444-1234 |
| 2 | Mary | 555-111-1234 |
+-------+----------------+----------------+
table : leads
+-------+-----------+---------------------+
| id | name | phonenumber |
+-------+-----------+---------------------+
| 1 | John | 214-444-1234 |
| 2 | Mary | 555-111-1234 |
+-------+-----------+---------------------+
table : customvaluefields
+-------+-----------+-------------+-----------+
| id | relid | fieldid | value |
+-------+-----------+-------------+-----------+
| 1 | 1 | 41 | 222333444 |
| 2 | 1 | 20 | Management|
| 3 | 2 | 41 | 333444555 |
+-------+-----------+-------------+-----------+
If I understand what you are trying to, maybe UNION ALL would work. This is something to get you started:
SELECT C.ID, C.FirstName, C.Phonenumber
FROM Contacts C
JOIN CustomValueField CVF
ON c.ID = CVF.RelID AND
CVF.ID = 41
AND REPLACE(Phonenumber,'-','') = cvf.Value
UNION ALL
SELECT L.ID, L.FirstName, L.Phonenumber
FROM Leads L
JOIN CustomValueField CVF
ON L.ID = CVF.RelID AND
CVF.ID = 41
AND REPLACE(Phonenumber,'-','') = cvf.Value
I'm joining the contacts and leads tables to CustomeValueField in each query and then UNION them together along with the WHERE clause in each. I'm sure it's not 100% correct for what you need, but should get you headed to a solution. Here is more information: https://dev.mysql.com/doc/refman/8.0/en/union.html

SQL Self join table, defaulting to null and removing duplicates

I have an SQL model (MySQL v8.0.20) that describes a physical card with 1 or 2 faces. Because card faces can appear on multiple physical cards, I've modeled this as a many-many relationship using the join table cardFaces.
I'm now trying to get a list of distinct front facing cards. That is if a card has only 1 face, it would be represented once, if it has 2 faces, it would be represented twice (once for each side front facing).
Input
The join table currently looks like this (simplified for question):
SELECT * FROM cardFaces;
+----+--------+--------+
| id | cardId | faceId |
+----+--------+--------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | B | 3 |
+----+--------+--------+
Expected Output
The result I'm expecting to achieve is this:
+--------+-------------+------------+
| cardId | frontFaceId | backFaceId |
+--------+-------------+------------+
| A | 1 | NULL |
| B | 2 | 3 |
| B | 3 | 2 |
+--------+-------------+------------+
Current Output
I've only gotten so far as self-joining and removing duplicates, but I can't figure out how to introduce NULL as the backFaceId for cards with only 1 face.
SELECT frontFace.cardId, frontFace.faceId frontFaceId, backFace.faceId backFaceId
FROM cardFaces frontFace
LEFT JOIN cardFaces backFace
ON frontFace.cardId = backFace.cardId
WHERE backFace.id != frontFace.id;
+--------+-------------+------------+
| cardId | frontFaceId | backFaceId |
+--------+-------------+------------+
| B | 2 | 3 |
| B | 3 | 2 |
+--------+-------------+------------+
Move the where condition to the on clause:
SELECT frontFace.cardId, frontFace.faceId frontFaceId, backFace.faceId backFaceId
FROM cardFaces frontFace LEFT JOIN
cardFaces backFace
ON frontFace.cardId = backFace.cardId AND
backFace.id <> frontFace.id;
NULLs fail almost all comparisons, including <>, turning the outer join into an inner join.

MYSQL selecting new column based on multiple joins

I'm still working through some kinks with MySQL so any help will be appreciated.
I have 3 tables -- equipment, states, zones.
equipment:
+---------------+------+------------+
| current_state | id | ...columns |
+---------------+------+------------+
states:
+----------+-------------+
| state | zone_id |
+----------+-------------+
zones:
+-----+------+
| id | zone |
+-----+------+
In equipment, there is one current_state per row.
In states, there is one zone_id per row.
In zones, there is one zone per row.
I would like to JOIN the three tables as a subquery select statement (not even sure if that's a thing) and have the output return as 1 alias'd column among the other columns I'm selecting
+--------------+-------------+
| current_zone | ....columns |
+--------------+-------------+
A sample expected output is:
+------------+-------------+--------+------------------+--------------+---------+
| c_id | g_id | e_id | equipment_type | impressionId | email |
+------------+-------------+--------+------------------+--------------+---------+
| 1234 | ABC1234 | 0001 | VEST | 2032 |ab#yc.com|
| 1234 | 1234ABC | 0001 | SHIRT | 4372 |ab#yc.com|
| 1234 | DCBA123 | 0001 | CAN | 4372 |ab#yc.com|
| 1234 | DCBA321 | 0001 | JACKET | ab#yc.com |ab#yw.com|
| 4567 | abc321d | 0002 | SHIRT | 2032 |db#yw.com|
| 4567 | cba123d | 0002 | CAN | 4372 |db#yw.com|
| 4567 | def4rg4 | 0002 | JEANS | 3210 |db#yw.com|
+------------+-------------+--------+------------------+--------------+---------+
The current query has multiple joins already referring to the zones and states table in order to determine a different value:
SELECT equipment.*,
...
FROM equipment
LEFT JOIN c on equipment.c_id = c.id
LEFT JOIN g on equipment.g_id = g.id
LEFT JOIN states on g.state = states.state
LEFT JOIN zones on zones.id = states.zone_id
Essentially, what I want to do is create a subquery in order to create a new column based on the results of the three joins, something like this:
SELECT equipment.*,
(SELECT
equipment.current_state
FROM equipment
LEFT JOIN equipment.current_state = states.state
LEFT JOIN zones.id = states.zone_id
) as current_zone,
...
This is even possible? Am I trying to select a new column in the wrong place?
Thanks to #TheImpaler I was able to clear up my Scalar Subquery. In my eyes, I thought I had to create another join based on the properties I wanted when in reality all I had to do was create a conditional scalar subquery:
SELECT equipment.*,
(SELECT zones.zone
FROM zones
WHERE equipment.current_state = states.state
AND zones.id = states.zone_id
) as current_zone,
...

How to join tables with SQL query and take number of tied columns?

I'm having BookTable in database (with foregin hey LibID):
| BookID | BookName | BookPrice | LibID |
-------------------------------------------
| 1 | Book_1 | 200 | 1 |
| 2 | Book_2 | 100 | 1 |
| 3 | Book_3 | 300 | 2 |
| 4 | Book_4 | 150 | 4 |
and also LibraryTable:
| LibID | LibName | LibLocation |
-----------------------------------
| 1 | Lib_1 | Loc_1 |
| 2 | Lib_2 | Loc_2 |
| 3 | Lib_3 | Loc_3 |
| 4 | Lib_4 | Loc_4 |
I need to write SQL query that will return be the info about the library and number of books for that library:
| LibID | LibName | NumberOfBooks|
------------------------------------
| 1 | Lib_1 | 2 |
| 2 | Lib_2 | 1 |
| 3 | Lib_3 | 0 |
| 4 | Lib_4 | 1 |
It should be one SQL query, probably with nested queries or joins.. Not sure how the query should look like:
SELECT L.LibID AS LibID, L.LibName AS LibName, COUNT(B) AS NumberOfBooks
FROM LibraryTable L, BookTable B
WHERE L.LibID = B.LibID
Will that work?
No, this query will not work. COUNT aggregates data, so you must explicitely tell the DBMS for which group of data you want the count. In your case this is the library (you want one result record per library).
COUNT's parameter is a column, not a table, so change this to * (i.e. count records) or a certain column (e.g. LibID).
The join syntax you are using is valid, but deprecated. Use explicit joins instead. In your case an outer join would even show libraries that have no books at all, if such is possible.
select l.libid, l.libname, count(b.libid) as numberofbooks
from librarytable l
left outer join booktable b on b.libid = l.libid
group by l.libid;
You could also do all this without a join at all and get the book count in a subquery instead. Then you wouldn't have to aggregate. That's way simpler and more readable in my opinion.
select
l.libid,
l.libname,
(select count(*) booktable b where b.libid = l.libid) as numberofbooks
from librarytable l;
SELECT lt.LibID AS LibID, lt.LibName AS LibName, count(*) AS NumberOfBooks
FROM BookTable AS bt
LEFT JOIN LibraryTable AS lt ON bt.LibID = lt.LibID
GROUP BY bt.LibID

SQL table join with multiple duplicates - returning count less than X within a date range

My head is spinning. I have been struggling for the past two days to come up with a MySQL query that joins two tables. I've run into several complications.
The first table, we'll call it log, has a log of people which have "signed in". Each entry is timestamped with datetime. It consists of a student_id and timestamp. It looks something like this
-------------------------------------
| student_id | timestamp |
| 1234 | 2014-02-26 21:50:27 |
| 2345 | 2014-02-26 21:54:54 |
| 1234 | 2014-03-03 19:18:18 |
| .....etc. |
-------------------------------------
My second table, we'll call it students has the information on each student.
--------------------------------------------------------
| student_id | name | homeroom |
| 1234 | Charles Reinmuth | Swatosh |
| 2345 | Kathryn Mo | Green |
| 6789 | Emily Salt | Clayborne |
| .....etc. |
--------------------------------------------------------
I want to return students, within a certain datetime range, that have fewer than X entries in the log. INCLUDING students with no entries within that datetime range. My current query works well. But only returns students with >0 entries in the log. I've tried LEFT OUTER and RIGHT OUTER but to no avail. Here is my current query:
SELECT students.name, students.homeroom, COUNT(1) AS cnt
FROM students
INNER JOIN log ON log.student_id = students.student_id
WHERE log.`datetime` between '2014-02-25 00:00:00' and '2014-03-04 00:00:00'
GROUP BY log.student_id HAVING COUNT(1) < 3
This will return, in the case of the example above:
--------------------------------------------------
| Name | Homeroom | Count |
| Charles Reinmuth | Swatosh | 2 |
| Kathryn Mo | Green | 1 |
| .....etc. |
--------------------------------------------------
This is what I am trying to accomplish:
--------------------------------------------------
| Name | Homeroom | Count |
| Charles Reinmuth | Swatosh | 2 |
| Kathryn Mo | Green | 1 |
| Emily Salt | Clayborne | 0 |
| .....etc. |
--------------------------------------------------
To get rows with no matches in the second table, you need to use LEFT JOIN.
SELECT students.name, students.homeroom, COUNT(log.student_id) AS cnt
FROM students
LEFT JOIN log
ON log.student_id = students.student_id
AND log.`datetime` between '2014-02-25 00:00:00' and '2014-03-04 00:00:00'
GROUP BY students.student_id
HAVING cnt < 3
Note the other changes I made:
COUNT(log.student_id) - Otherwise, you'll count the row from the students table, even though there's no log row. COUNT(column) doesn't count null values, which is what you get when there's no match.
The log.datetime test must be moved into the ON clause, because it will be NULL for students with no rows in log.
I changed the HAVING clause to use the cnt alias, to avoid having to repeat COUNT(log.student_id).