mysql SELECT EXISTS on multiple tables - mysql

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

Related

sql calculate present students in class on multiple rows

in barcodes table i have two column witch they are enter, exit like with student attendance record,
in this table when student entered to class, i add new row in table and enter value is 1 and exit is 0 like with login
after exiting from classroom i check latest row of this student and if enter is 1 then i add new row in table with 1 value for exit like with logout
now i want to calculate counting all students witch enter column is 1 and exit value is 0 to get all present students in class
SAMPLE DATA:
in this DB-FIDDLE i have sample data and into that i have 2 present student that session_id of them is 1, they are 123451,123452 students, but my sql command as second part is incorrect and that return one present student:
(
select count(*)
from barcodes b
where b.session_id = s.id
group by session_id, barcode
having sum(exit) = 0
) as present
for example:
select s.id, s.session_name, s.session_type, s.date_time,
(
select count(*)
from barcodes b where b.session_id = s.id
) as barcode_count ,
(
select count(*)
from barcodes b
where b.session_id = s.id
group by session_id, barcode
having sum(exit) = 0
) as present
from sessions s;
If I get what you want to do right, you can add another, outer aggregation.
...
(SELECT sum(x.count)
FROM (SELECT count(*) count
FROM barcodes b
WHERE b.session_id = s.id
GROUP BY b.session_id,
b.barcode
HAVING sum(b.enter) <> 0
AND sum(b.exit) = 0) x) present
...
But I think there's a much simpler way to get what you want by just taking the sum of enter minus exit for the session. Since any row with exit = 1 also comes with enter = 1, we need to double the exit before subtraction though.
...
(SELECT sum(b.enter - 2 * b.exit)
FROM barcodes b
WHERE b.session_id = s.id) present
...
If you can trust the data, something like this could work:
select
s.id,
s.session_name,
s.session_type,
s.date_time,
count(*) as barcode_count,
sum(exit = 0) - sum(exit = 1) as present
from sessions s
left join barcodes b on b.session_id = s.id
group by s.id, s.session_name, s.session_type, s.date_time
db-fiddle
You can read sum(exit = 0) - sum(exit = 1) as (number of entries) - (number of exits). If 4 students entered a class and 2 left the class, I would expect 2 students (4 - 2) to be still in the class.

SQL select rows that have one value but not another

I have a table in SQL which will contain multiple rows for one id, as below
accountid Productname
1 GL
1 IP
1 MI
2 GL
2 IP
2 PA
3 MI
3 CP
3 IP
4 GL
4 CP
4 CI
I want to be able to select all accounts which have certain products but not other. For example all that have IP or GL but not MI, using the sample table above this would return accounts 2 and 4.
SELECT ccx_accountidname
FROM (
SELECT ccx_accountidname, ccx_productname
FROM Filteredccx_leadresearch
WHERE ccx_productname IN ('GL','IP')
AND ccx_accountidname IS NOT NULL
) AS T
WHERE ccx_productname NOT IN ('MI')
ORDER BY ccx_accountidname
and
SELECT DISTINCT LR1.ccx_accountidname
FROM Filteredccx_leadresearch LR1
LEFT JOIN Filteredccx_leadresearch LR2 ON LR1.ccx_accountid = LR2.ccx_accountid
AND LR2.ccx_productname IN ('GL', 'IP')
WHERE LR1.ccx_productname NOT IN ('MI')
AND LR1.ccx_accountidname IS NOT NULL
ORDER BY LR1.ccx_accountidname
Both give basically the same results, is there any way this can be done?
Thanks in advance for any help
Could you try this:
SELECT DISTINCT T1.Accountidname FROM TheTableThatContainsAccountnames as T1
JOIN AccountProductsTable as T2 on T1.AccountId=T2.AccountId
WHERE T2.ProductName = 'ProductYouWant'
AND T2.ProductName = 'AnOtherProductYouWant'
According to your post, all you really need is a simple query with the correct and logic. You want all accounts with Product name GL or IP but not in MI. This will do it without any other joins.
SELECT ccx_accountidname
FROM Filteredccx_leadresearch
WHERE
ccx_productname in ('GL','IP')
and ccx_productname not in ('MI')
EDIT
This will get you the account, though I doubt it will work in your overall solution. It's just hard to tell without seeing your complete dataset. This could be done with parameters too.
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
DROP TABLE #TempTable
IF OBJECT_ID('tempdb..#TempTableTwo') IS NOT NULL
DROP TABLE #TempTableTwo
create table #TempTable (accountid int, productname char(2))
insert into #TempTable (accountid,productname) values
(1,'GL'),
(1,'IP'),
(1,'MI'),
(2,'GL'),
(2,'IP'),
(2,'MA')
select distinct
t1.accountid,
1 as T
into #TempTableTwo
from
#TempTable t1
where
productname in ('GL','IP')
union all
select distinct
t1.accountid,
-1 as T
from
#TempTable t1
where
productname in ('MI')
select
accountid
from #TempTableTwo
group by accountid
having sum(T) > 0
I might be late for the game, but this should do the trick, if anyone is trying to solve a similar problem. I renamed your table and it's columns:
Filteredccx_leadresearch -> l_search
ccx_accountidname -> a_name
ccx_productname -> p_name
And here's the SQL:
(SELECT DISTINCT t1.a_name
FROM l_search t1
JOIN l_search t2 ON t1.a_name = t2.a_name
WHERE t1.p_name = 'IP'
OR t2.p_name = 'GL')
MINUS
(SELECT DISTINCT t1.a_name
FROM l_search t1
JOIN l_search t2 ON t1.a_name = t2.a_name
WHERE ((t1.p_name = 'IP'OR t1.p_name = 'GL') AND t2.p_name = 'MI')
OR
(t1.p_name = 'MI' AND (t1.p_name = 'IP' OR t1.p_name = 'GL')));
First set:
cross product of table on itself with same IDs, get account IDs which have a product 'IP' or 'GL'.
Second set:
cross product of table on itself with same IDs, get account IDs which have p_name ('IP' OR 'GL') on first cross property AND 'MI' on second.
Also, get those IDs, which have the same but the other way around: p_name 'MI' on first cross property AND ('IP' OR 'GL') on second.
And finally subtract the second from the first.
Here is a simple way to include the accounts that match either IP or GL and exclude those accounts if they have an record for MI without using a subquery.
This is assuming t1 is a table that has unique account numbers in accountid and t2 is the table you have shown above that has accountid and Productname columns.
SELECT DISTINCT
t1.accountid
FROM t1
LEFT JOIN t2 AS t2_match
ON t1.accountid = t2_match.accountid
AND
(
t2_match.Productname = 'IP'
OR t2_match.Productname = 'GL'
)
LEFT JOIN t2 AS t2_not_match
ON t1.accountid = t2_not_match.accountid
AND t2_not_match.Productname = 'MI'
WHERE
t2_match.accountid IS NOT NULL
AND t2_not_match.accountid IS NULL
This is really late, but it might help some one.
I'll focus only on using the columns we have on the table we are shown (won't combine it with other tables we were not given).
Since the only table in the example is not clearly named, I'll call it some_table
SELECT t.accountidname, t.productname
FROM some_table t
WHERE t.productname IN ('GL','IP')
AND t.accountidname NOT IN (
SELECT accountidname
FROM some_table
WHERE productname = 'MI'
);
The idea here is to:
Select all accountid and productname that have productname either GL or IP (3rd line)
Select all accountid that have a productname MI and remove them from the values we already have (4th line onwards)
With this values, filtering or combining it with other tables should be rather trivial.
You might want to replace the SELECT with SELECT DISTINCT if the combinations of accountid and productname could be repeated in the table.

MySQL query joining 3 tables and counting

I've been stuck on this problem for far too long.
I have to merge 3 tables and do some counting of distinct values.
I have 3 tables
1.User_me
profileId( String )
responded( int 1 or 0)
2.Profiles
profileId ( String )
idLocation ( int )
3.lookup_location
id ( int )
location (String )
I can join User_me and Profiles ON User_me.profileId = Profiles.profileId
I can join Profiles and lookup_location ON Profiles.idLocation = lookup_location.id
Under Profiles I need to count the number of distinct values for idLocation where User_me.profileId = Profiles.profileId
I also need to count the number of Profiles.idLocation that have User_me.responded = 1
I have this:
SELECT lookup.location, count(*) as total
FROM User_me user
JOIN Profiles
ON user.profileId= profiles.profileId
JOIN lookup_location lookup
ON profiles.idLocation = lookup.id
GROUP BY profiles.idLocation
but I still need to have the column giving me the count where User_me.responded = 1
Something like:
SELECT lookup.location, count(*) as total, count(*) responded
If I'm understanding your question correctly, you can you a case statement in the count aggregate:
SELECT lookup.location, count(*) as total,
count(case when user.responded = 1 then 1 end) as responded
FROM User_me user
JOIN Profiles
ON user.profileId= profiles.profileId
JOIN lookup_location lookup
ON profiles.idLocation = lookup.id
GROUP BY profiles.idLocation
Since you're using MySQL, you can also use something like sum(user.responded = 1).

SQL Query / Slow

I have the below SQL code, this is from a MySQL database. Now it gives me the results I expect, however the query is slow and I think I should speed this query up before going any further.
The table agentstatusinformation has:
PKEY (Primary Key), userid (integer), agentstate (integer)
The table axpuser contains the users name:
PKEY (Primary Key) <-- this is the key for userid, loginid (usersname)
select distinct (select loginid from axpuser where axpuser.pkey = age.userid),
case
when agentstate = 1 then 'Ready'
when agentstate = 3 then 'Pause'
end as state
from agentstatusinformation age
where (userid, pkey) in
(select userid, max(pkey) from agentstatusinformation group by userid)
I am sure this can be improved upon, but I cannot see the wood for the trees.
Many thanks.
Not precisely certain this is what you want, but I think its close:
Select loginid, case when c.agentstate=1 Then 'Ready'
when c.agentstate=3 then 'Pause'
end state
from axpuser a
join (select userid, max(pkey) pkey
from agentstatusinformation
group by userid ) b
on a.userid=b.userid
join agentstatusinformation c
and b.pkey=c.pkey
This eliminates the subselect in the initial SELECT clause, and joins against the grouped stats information table. Hope this helps.
The problem with your query are your nested selects. In particular, the subquery in the IN clause is problematic in MySQL. It gets called for every row filtered by the where clause.
The following fixes this:
select distinct (select loginid from axpuser where axpuser.pkey = age.userid),
case
when agentstate = 1 then 'Ready'
when agentstate = 3 then 'Pause'
end as state
from agentstatusinformation age
where exists (select userid, max(pkey)
from agentstatusinformation a2
where a2.userid = age.userid
group by userid
having age.pkey = max(pkey))
You can make this run faster by creating an index on agenstatusinfromation(userid, pkey).
The nested select should not be causing a problem, as long as there is an index on axpuser.pkey. However, I think it is better form to put this in the FROM clause as a join:
select distinct axpuser.loginid,
case
when agentstate = 1 then 'Ready'
when agentstate = 3 then 'Pause'
end as state
from agentstatusinformation age left outer join
axpuser
on axpuser.key = age.userid
where exists (select userid, max(pkey)
from agentstatusinformation a2
where a2.userid = age.userid
group by userid
having age.pkey = max(pkey)
)
select ax.loginid,
case
when age.agentstate = 1 then 'Ready'
when age.agentstate = 3 then 'Pause'
end as state
from
agentstatusinformation age
join
axpuser ax
on age.userid = ax.userid and age.pkey=(select max(pkey) from agentstatusinformation group by userid)

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.