How can I join a table with itself with NULL values - mysql

I have this table (test):
+----+---------+-----+---+
| ID | Name | A | B |
+----+---------+-----+---+
| 1 | Steve | 200 | 0 |
| 2 | Steve | 200 | 1 |
| 5 | James | 90 | 0 |
| 4 | James | 50 | 1 |
| 3 | Warrick | 100 | 1 |
+----+---------+-----+---+
and this SQL query:
SELECT one.Name as Name, one.A as one_value, zero.A as zero_value
FROM test one LEFT JOIN test zero ON one.Name = zero.Name AND one.A <> zero.A
WHERE zero.B = 0 AND one.B = 1
which returns:
+-------+-----------+------------+
| Name | one_value | zero_value |
+-------+-----------+------------+
| James | 50 | 90 |
+-------+-----------+------------+
But what I want is when a record exists only with B = 1 that it is included in the response with a NULL value or something in the zero_value column, like this:
+---------+-----------+------------+
| Name | one_value | zero_value |
+---------+-----------+------------+
| James | 50 | 90 |
| Warrick | 100 | NULL |
+---------+-----------+------------+
How can I do this?
Edit:
I worked it out:
SELECT one.Name, one.A, zero.A
FROM test one LEFT JOIN test zero ON one.Name = zero.Name AND ( zero.B = 0 OR zero.B is NULL )
WHERE ( one.A <> zero.A OR zero.A is null )

Because of the left join the value of zero.B may be NULL, so you need to extend the WHERE condition:
WHERE one.B=1 AND (zero.B IS NULL OR zero.B = 0)
Update
You should also move the score condition down into WHERE:
WHERE one.B=1 AND (zero.B IS NULL OR zero.B = 0)
AND (zero.A IS NULL OR one.A <> zero.A)

How about this
select Name ,
(case when B= 0 Then A else null) as zero_value,
(case when B= 1 Then A else null) as one_value
from test

LEFT JOIN is a good thing here, this is what you want :
SELECT
one.Name
,one.A as one_value
, zero.A as zero_value
FROM test one
LEFT JOIN test zero
on one.Name = zero.Name
and zero.B = 0
where one.B = 1
Perhaps you want to handle when record exists only with B = 0 that it is included in the response with a NULL value in the one_value column in same time :
SELECT
test.Name
, one.A as one_value
, zero.A as zero_value
FROM
( SELECT Name
FROM test
GROUP BY Name) test
LEFT JOIN test one
on test.Name = one.Name
and one.B = 1
LEFT JOIN test zero
on test.Name = zero.Name
and zero.B = 0
Here is a demo for both queries :)

http://www.sqlfiddle.com/#!2/6332a/7/0
Try thiz query that give exact result for u
SELECT Test.nme,
one.A AS one_value,
zero.A AS zero_value
FROM(SELECT name AS nme FROM test WHERE name NOT in( SELECT one.Name FROM test one LEFT JOIN test zero ON (one.Name = zero.Name AND one.A=zero.A)
WHERE zero.B=0 AND one.B=1)GROUP BY name)Test
LEFT JOIN test one ON test.nme=one.Name AND one.B=1
LEFT JOIN test zero ON test.nme=zero.Name AND zero.B=0;

Related

multiple tables select, Subquery returned more than 1 value. SQL

I have two tables:
Employees
id | fullName | birth | speciality
1 | A A A | 01/01/1980 | Manager
2 | B B B | 01/01/1980 | Developer
3 | C C C | 01/01/1980 | User
EmployeesStatus
ID | status | dateChange
1 | 1 | 01/01/2010
2 | 1 | 01/01/2013
3 | 1 | 01/01/2015
3 | 2 | 01/01/2016
and I want to seletect the following data
ID | Full name | Bith date | speciality | Date hired | Date fired
Result has to be:
ID | Full name | Bith date | speciality | Date hired | Date fired
1 | A A A | 01/01/1980 | Manager | 01/01/2010 | null
2 | B B B | 01/01/1980 | Developer | 01/01/2013 | null
3 | C C C | 01/01/1980 | User | 01/01/2015 | 01/01/2016
3 | C C C |01/01/1980 | User | 01/01/2017 | null
my code:
SELECT Employees.id , Employees.fullName, Employees.birth, Employees.speciality,
(SELECT dateChange FROM EmployeesStatus WHERE status=1 AND id=Employees.id) datehired,
(SELECT dateChange FROM EmployeesStatus WHERE status=2 AND id=Employees.id) datefired FROM Employees
hase as result the following message:
Msg 512, Level 16, State 1, Line 1 Subquery returned more than 1
value. This is not permitted when the subquery follows =, !=, <, <= ,
, >= or when the subquery is used as an expression.
Any thoughts?
You should use a join instead of an = based on subquery
SELECT
Employees.id
, Employees.fullName
, Employees.birth
, Employees.speciality
, e1.dateChange as datehired, e2.dateChange as datefired
FROM Employees
INNER JOIN EmployeesStatus es1 on e1.status=1 AND e1.id=Employees.id
LEFT JOIN EmployeesStatus es2 on e2.status=2 AND e2.id=Employees.id
Or you could use in clause instead of = on subquery
In addition to #scaisEdge answer:
SELECT
Employees.id,
Employees.fullName,
Employees.birth,
Employees.speciality
e1.dateChange as datehired,
MIN(e2.dateChange) as datefired
FROM Employees
INNER JOIN EmployeesStatus es1 on e1.status=1 AND e1.id=Employees.id
LEFT JOIN EmployeesStatus es2 on e2.status=2 AND e2.id=Employees.id
AND e2.dateChange > e1.dateChange
GROUP BY Employees.id,
Employees.fullName,
Employees.birth,
Employees.speciality,
e1.dateChange
Try following:
SELECT E.ID,E.FULLNAME,E.BIRTH,E.SPECIALITY,ED.DATE_HIRED,ED.DATE_FIRED
FROM EMPLOYEES E,
(SELECT ID,
MAX(CASE WHEN STATUS=1 THEN DATECHANGE ELSE NULL END)DATE_HIRED,
MAX(CASE WHEN STATUS=2 THEN DATECHANGE ELSE NULL END)DATE_FIRED
FROM EMPLOYEESSTATUS
GROUP BY ID)ED
WHERE E.ID=ED.ID
This query will be faster in term of performance and will give you same result.
try this
SELECT e.id ,
e.fullName,
e.birth,
e.speciality,
CASE WHEN t1.status = 1 then max(t1.dateChange) else null end as "Date Hired",
CASE WHEN t2.status = 2 then max(t2.dateChange) else null end as "Date Fired",
FROM Employees e
LEFT JOIN EmployeesStatus t1 on t1.id = e.id and t1.status = 1
LEFT JOIN EmployeesStatus t2 on t2.id = e.id and t1.status = 2
Hope this works..

How to make two entries of second table as two columns for a select query from first table?

I want two display the result of the second table 'e_value', wich are two records (from only one column), as two columns for the select query from first table 'e_order_item'.
Also I am displaying many order items using a parameter 'collect_id',
so I want to display each two values of the table 'e_value' using to the order item id displayed on the select query.
for example, I have this on the tables
+-------------------------------+
| e_order_item |
+-------------------------------+
| oi_id oi_price oi_collect_id |
| 1 100 2 |
| 2 30 2 |
| 3 55 3 |
| 4 70 4 |
| 5 220 2 |
| 6 300 2 |
+-------------------------------+
+----------------------------+
| e_value |
+----------------------------+
| v_id v_value v_oi_id |
| 1 name1 1 |
| 2 surname1 1 |
| 3 name2 2 |
| 4 surname2 2 |
| 5 name3 5 |
| 6 surname3 5 |
+----------------------------+
I want to select the order_items that have collect_id = 2, and I want the result to be like this
+--------------------------------------------------+
| |
+--------------------------------------------------+
| Result |
| oi_id oi_price oi_collect_id name surname |
| 1 100 2 name1 surname1 |
| 2 30 2 name2 surname2 |
| 5 220 2 name3 surname3 |
| 6 300 2 null null |
| |
+--------------------------------------------------+
Here's the query:
SELECT
t.oi_id,
t.oi_price,
t.oi_collect_id,
LEFT (
GROUP_CONCAT(t.v_value),
IF (
LOCATE(',',GROUP_CONCAT(t.v_value)) = 0,
LENGTH(GROUP_CONCAT(t.v_value)),
LOCATE(',', GROUP_CONCAT(t.v_value)) - 1
)
) 'Name',
RIGHT (
GROUP_CONCAT(t.v_value),
LENGTH(GROUP_CONCAT(t.v_value)) -
IF (
LOCATE(',',GROUP_CONCAT(t.v_value)) = 0,
LENGTH(GROUP_CONCAT(t.v_value)),
LOCATE(',',GROUP_CONCAT(t.v_value))
)
) Surname
FROM
(
SELECT
*
FROM e_order_item
LEFT JOIN e_value ON e_order_item.oi_id = e_value.v_oi_id
WHERE e_order_item.oi_collect_id = 2
ORDER BY oi_id, v_id
) t
GROUP BY t.oi_id;
DEMO HERE
Note:
The following example illustrates how we can get the first string and second string from a comma separated string.
SET #str := 'A,BCDEFGHIJKL';
SELECT
LEFT(#str,IF(LOCATE(',',#str) = 0, LENGTH(#str),LOCATE(',',#str)-1)) AS StringBeforeComma,
RIGHT(#str,LENGTH(#str)-IF(LOCATE(',',#str)=0,LENGTH(#str),LOCATE(',',#str))) AS StringAfterComma
Result:
StringBeforeComma StringAfterComma
A BCDEFGHIJKL
You have to go for pivoting to get the desired result.
select oi_id, oi_price, oi_collect_id
, max(name) as name
, max(surname) as surname
from (
select
i.oi_id, i.oi_price, i.oi_collect_id
, case when #prevVal <> (#currVal:=v.v_oi_id)
then v.v_value
else null
end as name
, case when #prevVal = #currVal
then v.v_value
else null
end as surname
, #prevVal:=#currVal as temp_currVal
from e_order_item i
left join e_value v on v.v_oi_id = i.oi_id,
(select #prevVal:=-1, #currVal:=-1) as inits
where i.oi_collect_id=2
) as main_data
group by oi_id, oi_price, oi_collect_id
order by 1;
This is tested and run successfully...and give output as you want...
There are two subqueries:
1.First will give all result having collect_id = 2...
1.SELECT tab1.oi_id, tab1.oi_price, tab1.oi_collect_id
from(
SELECT oi_id, oi_price, oi_collect_id
from e_order_item
where oi_collect_id = 2
) as tab1;
2.This query will give you name, surname and id in different columns..
2.(SELECT e.v_value as name, surname, id
from (
select t1.v_value as surname, t1.v_oi_id as id from e_value as t1
group by t1.v_oi_id
)join e_value as e on id = e.v_oi_id and surname <> e.v_value
) as tab2 on tab1.oi_id = tab2.id;
Now left join these two query to get our desired result as:
SELECT tab1.oi_id, tab1.oi_price, tab1.oi_collect_id, name, surname
from(
SELECT oi_id, oi_price, oi_collect_id
from e_order_item
where oi_collect_id = 2
) as tab1 left join
(SELECT e.v_value as name, surname, id
from (
select t1.v_value as surname, t1.v_oi_id as id from e_value as t1
group by t1.v_oi_id
)join e_value as e on id = e.v_oi_id and surname <> e.v_value
) as tab2 on tab1.oi_id = tab2.id
order by tab1.oi_id asc; // to print in ascending order..
Why we use left join..You can use this link http://www.w3schools.com/sql/sql_join_left.asp to understand properly...
If this solution is helpful then let me know...

Select rows according row type in another table

This is my database structure.
Member Table(Type - S: Student, T: Teacher)
+---+----+----+----+
|idx|type| id | pw |
+---+----+----+----+
| 1 | S | A | .. |
| 2 | S | B | .. |
| 3 | T | C | .. |
| 4 | T | D | .. |
| 5 | S | E | .. |
| 6 | S | F | .. |
+---+----+----+----+
Student Table
+---+-----+-----+------+
|idx|grade|class|number|
+---+-----+-----+------+
| 3 | 3 | 8 | 29 |
| 4 | 2 | 10 | 13 |
+---+-----+-----+------+
Teacher Table
+---+-------+
|idx|enabled|
+---+-------+
| 3 | N |
| 4 | N |
+---+-------+
I want to get member info with specific info according to member type.
my code is
$result = query("SELECT * FROM `member` WHERE `id` = '...' AND `pw` = '...'");
$member_info = fetch_obj($result);
if ($member_info->type === 'T') {
$result = query("SELECT * FROM `teacher` WHERE `idx` = '...'");
}
else {
$result = query("SELECT * FROM `student` WHERE `idx` = '...'");
}
$specific_info = fetch_obj($result);
But, I want to get all data in just one query request.
like:
SET #type = SELECT `type` FROM `member` WHERE `member`.`idx` = '2';
SELECT
CASE #type
WHEN 'S' THEN `member`.*, `student`.*
WHEN 'T' THEN `member`.*, `teacher`.*
END
FROM
CASE #type
WHEN 'S' THEN `member`, `student`
WHEN 'T' THEN `member`, `teacher`
END
WHERE
`member`.`idx` = '2' AND
CASE #type
WHEN 'S' THEN
`student`.`idx` = `member`.`idx`
WHEN 'T' THEN
`teacher`.`idx` = `member`.`idx`
END
;
How can I do?
Since it appears in your data the Idx column in your member table could represent both that of a student AND a teacher, I would do a double left-join. Something like (and I try to never use select * )
select
m.idx,
m.type,
m.id,
m.pw,
case when s.idx IS NULL then 0 else 1 end as IsStudent,
s.grade,
s.class,
s.number,
case when t.idx IS NULL then 0 else 1 end as IsTeacher,
t.enabled
from
Member m
LEFT JOIN Student s
on m.idx = s.idx
LEFT JOIN Teacher t
on m.idx = t.idx
where
m.idx = 2
Then, in your result set, you will have two "flag" (0 or 1) columns IsStudent and IsTeacher so you could conditionally use the other columns IF they are so available.
If the student and teacher tables have the same columns then the easiest solution is probably to UNION 2 queries together, working on the basis that one of the unioned queries will not return anything.
Something like this, although I would specify the columns I wanted returned and not use SELECT *
SELECT `member`.*, `student`.*
FROM `member`
INNER JOIN `student`
ON `student`.`idx` = `member`.`idx`
WHERE `member`.`idx` = '2'
AND member.type = 'S'
UNION
SELECT `member`.*, `teacher`.*
FROM `member`
INNER JOIN `teacher`
ON `teacher`.`idx` = `member`.`idx`
WHERE `member`.`idx` = '2'
AND member.type = 'T'

"Faking" a sub-table of results from 3 tables, in SQL

If I have a table of cases:
CASE_NUMBER | CASE_ID | STATUS | SUBJECT |
----------------------------------------------------------------
3108 | 123456 | Closed_Billable | Something Interesting
3109 | 325124 | Closed_Billable | Broken printer
3110 | 432432 | Open_Assigned | Email not working
And a table of calls:
PARENT_ID | STATUS | DUR(H) | DUR(M) | SUBJECT
---------------------------------------------------------------
123456 | Held | 1 | 30 | Initial discussion
123456 | Cancelled | 0 | 0 | Walk user through
123456 | Held | 0 | 45 | Remote debug session
325124 | Held | 1 | 0 | Consultation
325124 | Held | 1 | 15 | Needs assessment
432432 | Held | 1 | 30 | Support call
And a table of meetings:
PARENT_ID | STATUS | DUR(H) | DUR(M) | SUBJECT
-------------------------------------------------------
123456 | Held | 3 | 15 | On-site work
325124 | Held | 2 | 0 | Un-jam printer
432432 | Held | 1 | 0 | Reconnect network
How do I do a select with these parameters (this is not working code, obviously):
SELECT cases.case_number, cases.subject, calls.subject, meetings.subject
WHERE cases.status="Closed_Billable" AND (calls.status="Held" OR meetings.status="Held)
LEFT JOIN cases
ON cases.case_id = calls.parent_id
LEFT JOIN cases
ON cases.case_id = meetings.parent_id
and end up with a "faked" nested table like:
CASE_NUMBER | CASE SUBJECT | # CALLS | # MEETINGS | CALL SUBJECT | MEETING SUBJECT | DURATION (H) | DURATION (M) | TOTAL
-----------------------------------------------------------------------------------------------------------------------------------------
3108 | Something Interesting | 2 | 1 | | | | | 5.5H
| | | | Initial Discussion | | 1 | 30 |
| | | | Remote Debug Session | | 0 | 45 |
| | | | | On-site work | 3 | 15 |
3109 | Broken printer | 2 | 1 | | | | | 4.25H
| | | | Consultation | | 1 | 0 |
| | | | Needs assessment | | 1 | 15 |
| | | | | Un-jam printer | 2 | 0 |
I've tried joins and subqueries the best I can figure out, but I get repeated entries - for example, each Meeting in a Case will show say 3 times, once for each Call in that case.
I'm stumped! Obviously there's other fields I'm pulling here, and doing COUNTs of Calls and Meetings, and SUMs of their durations, but I'd be happy just to show a table/sub-table like this.
Is it even possible?
Thanks,
David.
Assembling a query result in the exact format you want is .. somewhat of a pain. It can be done, but presentation stuff like that is best left to the application.
That said, this will do what you want:
select case when case_id > floor(case_id) then ''
else case_number
end case_number,
coalesce(q1.c, '') calls,
coalesce(q2.c, '') meetings,
coalesce(calls.subject, '') `call subject`,
coalesce(meetings.subject, '') `meeting subject`,
case when calls.subject is not null then calls.dhour
when meetings.subject is not null then meetings.dhour
else ''
end dhour,
case when calls.subject is not null then calls.dmin
when meetings.subject is not null then meetings.dmin
else ''
end dhour,
coalesce(q3.total, '') total
from
(
select case_number, case_id
from cases where status = 'Closed_Billable'
union select case_number, concat(case_id, '.1')
from cases where status = 'Closed_Billable'
union select case_number, concat(case_id, '.2')
from cases where status = 'Closed_Billable'
) main
left join
(select parent_id, count(*) c
from calls
where status != 'Cancelled'
group by parent_id ) q1
on q1.parent_id = case_id
left join
(select parent_id, count(*) c
from meetings
group by parent_id) q2
on q2.parent_id = case_id
left join
(select parent_id, sum(dhour + m) total
from
(select parent_id, dhour, dmin / 60 m
from calls
where status != 'Cancelled'
union all
select parent_id, dhour, dmin / 60 m
from meetings
) qq
group by parent_id
) q3
on q3.parent_id = case_id
left join calls
on concat(calls.parent_id, '.1') = main.case_id
left join meetings
on concat(meetings.parent_id, '.2') = main.case_id
order by case_id asc
Note, i've renamed your duration fields because i dislike the parenthesis in them.
We have to mangle the case_id a little bit inside the query in order to be able to get you your blank rows / fields - those are what makes the query cumbersome
There's a demo here: http://sqlfiddle.com/#!9/d59d4/21
edited code to work with different schema in comment fiddle
select case when case_id > floor(case_id) then ''
else case_number
end case_number,
coalesce(q1.c, '') calls,
coalesce(q2.c, '') meetings,
coalesce(calls.name, '') `call subject`,
coalesce(meetings.name, '') `meeting subject`,
case when calls.name is not null then calls.duration_hours
when meetings.name is not null then meetings.duration_hours
else ''
end duration_hours,
case when calls.name is not null then calls.duration_minutes
when meetings.name is not null then meetings.duration_minutes
else ''
end duration_hours,
coalesce(q3.total, '') total
from
(
select case_number, id as case_id
from cases where status = 'Closed_Billable'
union select case_number, concat(id, '.1') as case_id
from cases where status = 'Closed_Billable'
union select case_number, concat(id, '.2') as case_id
from cases where status = 'Closed_Billable'
) main
left join
(select parent_id, count(*) c
from calls
where status != 'Cancelled'
group by parent_id ) q1
on q1.parent_id = case_id
left join
(select parent_id, count(*) c
from meetings
group by parent_id) q2
on q2.parent_id = case_id
left join
(select parent_id, sum(duration_hours + m) total
from
(select parent_id, duration_hours, duration_minutes / 60 m
from calls
where status != 'Cancelled'
union all
select parent_id, duration_hours, duration_minutes / 60 m
from meetings
) qq
group by parent_id
) q3
on q3.parent_id = case_id
left join calls
on concat(calls.parent_id, '.1') = main.case_id
left join meetings
on concat(meetings.parent_id, '.2') = main.case_id
order by case_id asc
You can't really get final results like that without some seriously ugly "wrapper" queries, of this sort:
SET #prevCaseNum := 'blahdyblahnowaythisshouldmatchanything';
SET #prevCaseSub := 'seeabovetonotmatchanything';
SELECT IF(#prevCaseNum = CASE_NUMBER, '', CASE_NUMBER) AS CASE_NUMBER
, IF(#prevCaseNum = CASE_NUMBER AND #prevCaseSubject = CASE_SUBJECT, '', CASE_SUBJECT) AS CASE_SUBJECT
, etc.....
, #prevCaseNum := CASE_NUMBER AS prevCaseNum
, #prevCaseSubject = CASE_SUBJECT AS prevCaseSub
, etc....
FROM ( [the real query] ORDER BY CASE_NUMBER, etc....) AS trq
;
And then wrap all that with another select to strip the prevCase fields.
And even this still won't give you the blanks you want on the "upper right".

Left Join with Find in set

I have tables as follows :
TABLE A
+-----+---------------+-------------+
| ID | DNR_DETAIL_ID | DESCRIPTION |
+-----+---------------+-------------+
| 1 | 1 | DESC A |
+-----+---------------+-------------+
| 2 | 2 | DESC B |
+-----+---------------+-------------+
| 3 | 3 | DESC C |
+-----+---------------+-------------+
TABLE B
+--------+---------------+
| DNR_ID | DNR_DETAIL_ID |
+------------------------+
| 1 | 1,2 |
+--------+---------------+
| 2 | 3 |
+--------+---------------+
As you can see, DNR_DETAIL_ID columns are common in both tables. What I want to do, left joining both tables with field values ( null or not )
THE RESULT SHOULD BE (IF DNR_ID = 1) :
+-------------+---------+
| DESCRIPTION | CHECKED |
+-------------+---------+
| DESC A | 1 |
+-------------+---------+
| DESC B | 1 |
+-------------+---------+
| DESC C | 0 |
+-------------+---------+
try this:
SELECT TA.description AS DESCRIPTION, CASE WHEN TB.checked IS NOT NULL THEN 1 ELSE 0 END AS CHECKED
FROM
(
select distinct description from TableA
) TA left join
(
SELECT description, 'checked' FROM TableA where dnt_detail_id in (
select dnr_detail_id from TableB where dnr_id = 1
)
)TB ON TB.description = TA.description
Try this using FIND_IN_SET()
SELECT
A.Description,
CASE WHEN B.DNR_ID IS NOT NULL THEN 1 ELSE 0 END as Checked
FROM A
LEFT JOIN B
ON FIND_IN_SET(A.DNR_DETAIL_ID, B.DNR_DETAIL_ID)
AND B.DNR_ID=1
SQLFiddle demo
SELECT a.DESCRIPTION,
CASE WHEN b.DNR_ID IS NOT NULL THEN 0 ELSE 1 END as CHECKED
FROM table_a a
LEFT JOIN table_b b
ON FIND_IN_SET(a.DNR_DETAIL_ID, b.DNR_DETAIL_ID)
Demo on sqlfiddle
Thank you so much guys. I have tried all of your suggestions but none of them work. Interesting thing is that code works well in sqlfiddle ( same schema and values ) but not working in local environment! Here is the query that working in local.
/**
* DNR_DETAIL_DESC IS TABLE A
* DNR_LIST IS TABLE B
*/
SELECT A.DNR_DETAIL_DESC,
CASE WHEN B.DNR_ID IS NOT NULL THEN 1 ELSE 0 END AS CHECKED
FROM MD_DNR_DETAIL A
LEFT JOIN (SELECT * FROM DNR_LIST WHERE DNR_ID = 1) AS B
ON FIND_IN_SET(A.DNR_DETAILT_ID, B.DNR_DETAIL_ID)
You can write it many ways, but here is the best way:
SELECT
MD_DNR_DETAIL.DNR_DETAIL_DESC as DESCRIPTION,
CASE WHEN DNR_LIST.DNR_ID IS NOT NULL THEN 1 ELSE 0 END AS CHECKED
FROM MD_DNR_DETAIL
LEFT JOIN DNR_LIST
ON FIND_IN_SET(MD_DNR_DETAIL.DNR_DETAILT_ID, DNR_LIST.DNR_DETAIL_ID)