How to find NOT NULL columns by union? - mysql

I have three table as:
1.table person
id, others
1, x
2, y
3, z
4, w
2.table followup, FOREIGN KEY (person_id) REFERENCES person (id)
id, person_id, ftime, details
1, 1, '2018-01-12', 'json_1'
2, 1, '2018-04-21', 'json_2'
3, 2, '2017-12-16', 'json_3'
4, 2, '2018-01-17', NULL
5, 3, '2018-06-02', 'json_5'
6, 4, '2018-01-19', NULL
3.table followup_track, FOREIGN KEY (fid) REFERENCES followup (id)
id, fid, ftime, details
1, 1, '2018-01-27', 't_json_1'
2, 2, '2018-05-07', 't_json_2'
3, 5, '2018-06-17', 't_json_3'
Now I want find all the last ftime of every person, and the details IS NOT NULL in followup and/or followup_track.
the result what I want to get is (here the pid is person's id):
pid, ftime, details
1, '2018-05-07', 't_json_2'
2, '2017-12-16', 'json_3'
3, '2018-06-17', 't_json_3'
Because there is no detalis != NULL for person.id = 4, so the result no need for pid=4.
Because the last time of person.id = 1 is '2018-05-07', so need that column.
I create a view like:
CREATE VIEW view_full_flup AS
SELECT
p.id AS pid, fp.ftime, fp.details
FROM
((followup_track fp
LEFT JOIN followup ON (fp.fid = followup.id))
LEFT JOIN person p ON (followup.person_id = p.id))
WHERE
fp.details IS NOT NULL
UNION
SELECT
f.person_id AS pid, f.ftime, f.details
FROM
followup f
WHERE
f.details IS NOT NULL
Then, I use sql:
SELECT *, MAX(`ftime`) FROM view_full_flup GROUP BY pid;
Is my solution right please? The details can not make index, and it is slow. How to do this right please?

You can’t select star group by(well, you might be able to in MySQL depending on how it’s set up but it’s not a great habit to get into for portability of sql skills), you have to specify a list of columns in your select and either group them (put the column name in the group by) or aggregate them (pass the column name into an aggregate function)
It feels like you’ve got your left join logic backwards- person is the only table you know you have records in, so it should be on the left side of the join.. the other tables are potentially recordless
We’re this my query I’d write it something more like:
select
Pid,
Max(case when ft.ftime > f.ftime then ft.ftime else f.ftime end) maxft
From
Person p
Left join (select * from followup where details is not null) f on f.person_id = p.id
Left join (select * from follup_track where details is not null) ft on ft.fid = f.id
Group by pid
We just join the set of tables once, having already filtered for records where either table’s details are present and then get the max date from either table
Note that this query can return null dates, if you have records where the date column is null even though details is filled in. If those are undesirable, filter hem with a HAVING or wrap the whole thing in another select and filter using a WHERE
Ps; as it stands, this query doesn’t seem particularly useful because though you know the most recent date you don’t have any other data. If it’s all you wanted to know (I wasn’t able to tell from your original post because the description and the query disagreed in what they said they wanted) then great, but if you wanted the other info, maybe using mysql 8’s new analytic queries would better suit:
Select * from
(
select
Pid,
Row_number over(partition by pid order by (case when ft.ftime > f.ftime then ft.ftime else f.ftime end) desc) rown
From
Person p
Left join (select * from followup where details is not null) f on f.person_id = p.id
Left join (select * from follup_track where details is not null) ft on ft.fid = f.id
) a where rown = 1

Related

MySQL Join needs to show all records from LEFT table

I am wondering if someone can help.
Please see below:
SELECT id,name FROM words;
id , name
1 , Still
2 , Sparkling
3 , Fizzy
SELECT * from translation;
words_id, lang_id,translation
1, 1, AStill
1, 2, BStill
2, 1, ASparkling
I needed result like below:
id , name , lang_id, translation
1,Still, 1, AStill
2, Sparkling, 1, ASparkling
3, Fizzy, NULL , NULL
The query i have tried
SELECT id,name,lang_id,translation FROM words LEFT JOIN translation ON words_id=id AND lang_id=1;
http://sqlfiddle.com/#!9/f544b/3
Many thanks
Your condition lang_id=1 turns your LEFT JOIN into INNER JOIN. The reason is when the condition is not satisfied (ie no entry in translation table), it will not display it. If you want to display everything on the left table, remove the condition lang_id=1.
SELECT id,name,lang_id,translation
FROM words
LEFT JOIN translation ON words_id=id
If you need the condition, you can put it in sub-query.
SELECT id,name,lang_id,translation
FROM words w
LEFT JOIN (
SELECT *
FROM translation
WHERE lang_id = 1
) t ON t.word_id = w.id

Left outer join with multiple conditions - MYSQL

I'm running into some problems with a very simple query. I figure that it must be because of an incorrect assumption about how SQL queries work.
I'm trying to write a simple LEFT OUTER JOIN query using the following tables:
tmtrip_viewer( tmtrip_id, tmuser_id ) -> FKs: (tmtrip.id, tmuser.id)
Values: ( 6, 2 )
( 6, 3 )
( 7, 4 )
tmtrip( id, ...)
Values: (1, ...)
(2, ...)
(3, ...)
(4, ...)
tmuser(id, username, ...)
Values: (1, user1)
(2, user2)
(3, user3)
(4, user4)
What I want to do is:
Display alls id from tmuser table given the following conditions:
- That the id != '1'
- That the id is NOT in table tmtrip_viewer where tmtrip_viewer.tmtrip_id = 7.
Basically, I want to get all the users that are not viewing the tmtrip with tmtrip_id = 7 (except the logged in user ..id='1').
I have formulated the following query, but it does not behave as desired:
SELECT a.`id`, a.`username` FROM
`tmuser` a LEFT OUTER JOIN `tmtrip_viewer` b
ON a.`id` = b.`tmuser_id` AND b.`tmtrip_id` = '7'
WHERE a.id <> '1'
Why is this not working? What would be the right way to do this?
Add AND b.tmtrip_id IS NULL to your WHERE. Your query is getting all tmusers and their "trip 7" info if they have any; this will reduce the results to only the ones that had no "trip 7" info.
I think this should do what you want.
It would show one record for each user that doesn't have ID = 1 and also doesn't have a record in tm_tripviewer with tmtrip_id = 7.
SELECT id, username
FROM tmuser
WHERE id != 1
AND id NOT IN
(SELECT id FROM tmtrip_viewer WHERE tmtrip_id = 7)

mysql SELECT EXISTS on multiple tables

Have tables: person,person_ip
Both tables have pid column as a primary key, in table person there is column state_id, in table person_ip there is column ip.
Want to discover if specified IP address is assigned to person with state_id is not equal to 2. But always got result 1, even if state_id is 0, 1 or 2. Always got 0 only if ip address is not listed at all. What am I doing wrong?
SELECT EXISTS (
SELECT person_ip.PID
FROM person_ip,person
WHERE person.PID=person_ip.PID
AND person.state_id NOT IN (2)
AND person_ip.ip='10.11.12.13'
)
this seems like a simple join.. unless i'm missing something
select person.*
from person
inner join person_ip
on person.pid = person_ip.pid
where person.state_id <> 2
and person_ip.ip_address = '10.0.0.1'
If you want to exclude the ip_address if it has been assigned to any user with state = 2, even if it has also been assigned to a user without state = 2, then try:
select max(i)
from (
select *
from (
select 1 as i
from dual
where not exists (
select 1
from person p
inner join person_ip pi
on p.pid = pi.pid
where p.state_id = 2
and pi.ip_address = '10.0.0.1'
)
) q
union
select 0
) qq
(dual is a system table that can be used as a sort of stub table)
here's a fiddle showing both versions
update after some actual sleep
Okay, so the above query is a little.. out there. Back in the real world, this one is probably more appropriate:
select count(case when p1.state_id = 2 then 1 end)
from person p1
inner join person_ip pi1
on p1.pid = pi1.pid
where pi1.ip_address = '10.0.0.1'
group by pi1.ip_address;
This will return 1 or more if your ip_address has been used by someone with a state_id of 2, and 0 if it has never been used by someone with a state_id of 2.
It will return nothing if the ip has never been used.
this fiddle has all three of the above queries.
SELECT IF(COUNT(*)>0,1,0)
FROM person
INNER JOIN person_ip
ON person.pid = person_ip.pid
AND person_ip.ip_address = '10.0.0.1'
WHERE person.state_id <> 2

LEFT JOIN - fetching all data from left table with no matches in the right one

In my project, I have two tables like this:
parameters (
id PRIMARY KEY,
name
)
and
parameters_offeritems (
id_offeritem,
id_parameter,
value,
PRIMARY KEY (id_offeritem, id_parameter)
)
I'm not showing structure of offeritems table, because it's not necessary.
Some sample data:
INSERT INTO parameters (id, name) VALUES
(1, 'first parameter'), (2, 'second parameter'), (3, 'third parameter')
INSERT INTO parameters_offeritems (id_offeritem, id_parameter, value) VALUES
(123, 1, 'something'), (123, 2, 'something else'), (321, 2, 'anything')
Now my question is - how to fetch (for given offer ID) list of all existing parameters, and moreover, if for the given offer ID there are some parameters set, I want to fetch their value in one query.
So far, I made query like this:
SELECT p.*, p_o.value FROM parameters p LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter WHERE id_offeritem = OFFER_ID OR id_offeritem IS NULL
But it fetches only those parameters, for which there are no existing records in parameters_offeritems table, or parameters, for which value are set only for the current offer.
To get all parameters, plus the value of any parameters set for a specific Offer Item, you need to move the Offer ID logic into the join like this (see below).
SELECT p.*, p_o.value
FROM parameters p
LEFT JOIN parameters_offeritems p_o
ON p.id = p_o.id_parameter
AND id_offeritem = OFFER_ID;
If you have logic in your WHERE clause referring to fields in a table you are doing a LEFT JOIN on, you effectively change your JOIN to an INNER JOIN (unless you are checking for a NULL).
The magic word you're looking for is OUTER JOIN. Jeff Atwood did a nice Venn-diagram explanation here.
Your query was almost perfect, just WHERE in wrong pace:
SELECT p.*, p_o.value FROM parameters p
LEFT JOIN (
SELECT * FROM parameters_offeritems
WHERE id_offeritem = OFFER_ID) as p_o
ON p.id = p_o.id_parameter

MySQL Querying database for time periods not already assigned

I have a rather complex-seeming query that will form the basis for an online classroom scheduling tool. My challenge is to develop a method to identify which classes a user is signed up for in the st_schedule table, then deduce from the overall table of classes, st_classes, which other classes are available that don't conflict with the user's current classes.
For example, if a user has an entry in st_schedule assigning them to a class from 8:00am to 9:00am, they would be ineligible for any class whose time fell between 8:00am and 9:00am. A class that ran 7:15am - 8:15am would make the user ineligible. I store the start times and end times of classes in the database separately for comparison purposes. It's important that this be as flexible as possible, so the concept of "blocking" times and assigning times to blocks is not a possibility.
Here are excerpts from the tables:
table st_classes (holds class information)
id
start_time
end_time
table st_schedule (holds schedule information)
id
user_id
class_id
I certainly could do this in a series of loops server-side, but I have to think that there's a MySQL method that can do this type of operation in one fell swoop.
You want to join the two tables together to represent the user's classes, and then find unregistered classes where the start time and end time do not fall between the start and end time of the user's classes.
Something like this. Completely off the cuff and untested:
SELECT
*
FROM
st_schedule s
INNER JOIN st_classes c ON c.id = s.class_id
INNER JOIN st_classes all_classes
ON all_classes.start_time NOT BETWEEN c.start_time AND c.end_time
AND all_classes.end_time NOT BETWEEN c.start_time AND c.end_time
WHERE
s.user_id = 1
Edit: Try #2
I only have a moment to look at this. I think I reversed the second join clauses. The all_classes alias represents the full list of classes, where the "c" alias represents the classes that the student is signed up for.
SELECT DISTINCT
all_classes.*
FROM
st_schedule s
INNER JOIN st_classes c ON c.id = s.class_id
INNER JOIN st_classes all_classes
ON c.start_time NOT BETWEEN all_classes.start_time AND all_classes.end_time
AND c.end_time NOT BETWEEN all_classes.start_time AND all_classes.end_time
WHERE
s.user_id = 1
This is using table variables in mssql but the sql selects should translate over to mysql
First the sample data
DECLARE #st_classes TABLE
(
ID INT NOT NULL,
Title VARCHAR(40) NOT NULL,
StartTime DATETIME NOT NULL,
EndTime DATETIME NOT NULL
)
DECLARE #st_schedule TABLE
(
ID INT NOT NULL,
UserID INT NOT NULL,
ClassID INT NOT NULL
)
INSERT INTO #st_classes (ID, Title, StartTime, EndTime)
SELECT 1,'Class1','08:00:00','09:30:00' UNION
SELECT 2,'Class2','09:30:00','11:30:00' UNION
SELECT 3,'Class3','11:30:00','16:00:00' UNION
SELECT 4,'Class4','16:00:00','17:30:00' UNION
SELECT 5,'Class5','09:00:00','11:45:00' UNION
SELECT 6,'Class6','07:00:00','18:00:00'
INSERT INTO #st_schedule(ID, UserID, ClassID)
SELECT 1,1,1 UNION
SELECT 2,1,2 UNION
SELECT 3,2,6
Next a bit of sql to confirm the tables join OK (selecting scheduled courses for user with an ID of 1) - Returns class 1 and 2
SELECT *
FROM #st_schedule AS S INNER JOIN
#st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
Now we need to select all the ID of the courses where they overlap time wise with the users scheduled ones (including the scheduled ones) - Returns 1,2,5,6
SELECT AC.ID
FROM #st_classes AS AC
INNER JOIN ( SELECT C.StartTime,
C.EndTime
FROM #st_schedule AS S
INNER JOIN #st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
) AS UC ON ( AC.StartTime < DATEADD(ss, -1, UC.EndTime)
AND DATEADD(ss, -1, UC.EndTime) > UC.StartTime
)
GROUP BY AC.ID
Now we need to select all courses where the Course ID is not in our list of overlapping course IDs. - Returns course 3 and 4
SELECT *
FROM #st_classes
WHERE ID NOT IN (
SELECT AC.ID
FROM #st_classes AS AC
INNER JOIN ( SELECT C.StartTime,
C.EndTime
FROM #st_schedule AS S
INNER JOIN #st_classes AS C ON S.ClassID = C.ID
WHERE S.UserID = 1
) AS UC ON ( AC.StartTime < DATEADD(ss, -1, UC.EndTime)
AND DATEADD(ss, -1, UC.EndTime) > UC.StartTime
)
GROUP BY AC.ID )
Change the user ID filter to 2 and you should not get any returned as the course assigned to that user overlaps all courses.