Creating new table which contains unique rows with conditions ; MySQL - mysql

I have a table with the following columns:
SessionID - contains actions within a session (1-10 rows per SessionID let's say)
ActionName - is the name of the action
Time - time action occurred
I need to return a new table, whose columns are the same, and contains only 1 row per SessionID IF the action's name is either "a" or "b".
That is, my new table should have a 1 row per SessionID which had the action "a" or "b".
I tried:
CREATE TABLE U_SessionID (
SELECT DISTINCT(SessionID) AS SessionID FROM test1)
;
I copy the unique sessions to a new table
SELECT U_SessionID.SessionID, test1.ActionName, test1.SessionID
FROM test1
INNER JOIN U_SessionID ON (SELECT SessionID
FROM test1
WHERE U_SessionID.SessionID = test1.SessionID AND (ActionName =
"a" OR ActionName = "b")
ORDER BY Time DESC
LIMIT 1);
But this code causes MySQL workbench to crash (timeout performing query), and I have no idea if it even works.
Sample data:
Can you think of a lighter query to run this?
Maybe a better approach would be:
take all rows with action "a" or action "b":
SELECT * FROM test1 WHERE ActionName = "a" OR ActionName = "b";
drop duplicates based on the SessionID only (no matter the time order)
Ideas for that?

Is the time unique for a session ID?
If so:-
SELECT U_SessionID.SessionID, test1.ActionName, test1.SessionID
FROM test1
INNER JOIN
(
SELECT SessionID
MAX(Time) AS MaxTime
FROM test1
WHERE ActionName IN ("a", "b")
GROUP BY SessionID
) sub0
ON test1.SessionID = sub0.SessionID
INNER JOIN U_SessionID
ON sub0.SessionID = U_SessionID.SessionID
AND sub0.MaxTime = U_SessionID.Time
If time isn't unique, but assuming the table has a unique column called id:-
SELECT U_SessionID.SessionID, test1.ActionName, test1.SessionID
FROM test1
INNER JOIN
(
SELECT SessionID
SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY Time DESC), ',', 1) AS MaxId
FROM test1
WHERE ActionName IN ("a", "b")
GROUP BY SessionID
) sub0
ON test1.SessionID = sub0.SessionID
INNER JOIN U_SessionID
ON sub0.SessionID = U_SessionID.SessionID
AND sub0.MaxId = U_SessionID.id
EDIT
If you just want a random record for each session id then you could (ab)use the GROUP BY clause. I really do not like this idea as while it probably does work, it might well not work depending on the configuration of your MySQL database, and even if it does work someone might do an update that stops it working. Even then if it still works there is no garuntee that it will bring back a single real record, rather than a single rows whose columns are a mix of those from different rows.
So put here for completeness and to give you an option but I strongly advise against using it
SELECT U_SessionID.SessionID, test1.ActionName, test1.SessionID
FROM test1
INNER JOIN U_SessionID ON U_SessionID.SessionID = test1.SessionID
WHERE ActionName IN ("a" , "b")
GROUP BY U_SessionID.SessionID
If you want a simple solution that is more future proof then use my 2nd solution and remove the ORDER BY clause within the GROUP_CONCAT

You can change you query like below. Your subquery is unnecessary here.
SELECT U_SessionID.SessionID, test1.ActionName, test1.SessionID
FROM test1
INNER JOIN U_SessionID ON U_SessionID.SessionID = test1.SessionID
WHERE ActionName IN ("a" , "b")
ORDER BY `Time` DESC
LIMIT 1;

Related

MySQL: Fetching non-null values using multiple joins

I am running a few queries which I want to convert to a single query using joins
My first query is
1) SELECT * FROM ACT_TABLE1 where node='5bbcdded' order by Instance_ID desc;
The output of the above query is as below
ID Instance_ID NODE
2326600581 23266005612 5bbcdded1
2326524592 23265245712 5bbcdded2
2326523503 23265234213 5bbcdded3
2326523004 23265229614 5bbcdded4
2) Now, I grab topmost Instance_ID and run another select query as follows
SELECT * FROM ACT_TABLE2 where TOP_INST_ID = '23266005612';
Here, there might be a situation where select query returns a null value from above query. In that case, I grab second topmost Instance_ID and run same select query as follows
SELECT * FROM ACT_TABLE2 where TOP_INST_ID = '23265245712';
The output of the above query returns only single row as below
ID NEXT_ID TOP_INSTANCE_ID
232660056 232660056 232652457
3) Now, I grab topmost NEXT_ID and run another select query as follows
SELECT * FROM ACT_TABLE3 where NEXT_ID = '232660056';
The output of the above query returns only single row as below
ID EXEP_ID NEXT_ID
232660072 232660139 232660056
4) Now, I grab topmost EXEP_ID and run another select query as follows
SELECT field2 FROM ACT_TABLE4 where ID = '232660139';
The output of the above query returns field2 which is my final result
In other words, I want to pass node='5bbcdded' in my first table so that i can fetch value of field2 from my fourth table
You can do Inner Join between all the tables, using their relationships.
Then, employ multiple level Order By clauses starting from the first table (all in Descending order, since you want topmost from all the tables). We use LIMIT 1 to get the first row after sorting, which will be topmost.
Inner Join will ensure that any non-matching rows (null in the next table) will be ignored.
Try:
SELECT t4.field2
FROM ACT_TABLE1 AS t1
INNER JOIN ACT_TABLE2 AS t2 ON t2.TOP_INST_ID = t1.Instance_ID
INNER JOIN ACT_TABLE3 AS t3 ON t3.NEXT_ID = t2.NEXT_ID
INNER JOIN ACT_TABLE4 AS t4 ON t4.ID = t3.EXEP_ID
where t1.node = '5bbcdded'
ORDER BY t1.Instance_ID DESC, t2.NEXT_ID DESC, t3.EXEP_ID DESC
LIMIT 1

SQL select with multiple conditions on the same table

I have 2 tables in MySQL, the first one has 2 columns: ID and name, the second has 3 columns: firstTableId (foreign key on the first table), key, value.
I have the following rows in table 1:
1,Bob
2,Alice
3,Fred
I have the following rows in table 2:
1,age,20
1,gender,male
2,age,20
2,gender,female
3,age,18
3,gender,male
I would like to write a select query using only the last 2 columns on the second table (key and value) that returns only Bob form the first table, but I can't seem to figure it out.
Essentially I want to select from the first table all rows where, in the second table, we have key=age and value=20 for one row, and key=gender and value=male in another row. Can anyone point me in the right direction ? Manipulating table structure is not preferred as this is a simplified example and both "key" and "value" columns in the second table can be pretty much anything, it's not actually limited to "age" and "gender".
Thanks in advance.
You can do this with a self join like this:
select
*
from
table1 t1
inner join table2 age on t1.id = age.id
inner join table2 gender on t1.id = gender.id
where
(age.`key` = 'age' and age.value = 20)
and
(gender.`key` = 'gender' and gender.value = 'male')
An additional tactic you may want to try is a PIVOT query. Mysql doesnt have anything native to support pivot's, but there are several examples of how to do them.
You can see it working in this fiddle
Use two IN clauses (or two EXISTS clauses).
select *
from table1
where id in (select firstTableId from table2 where key = 'age' and value = '20')
and id in (select firstTableId from table2 where key = 'gender' and value = 'male');
With EXISTS:
select *
from table1
where exists (select * from table2 where key = 'age' and value = '20' and firstTableId = table1.firstTableId)
and exists (select * from table2 where key = 'gender' and value = 'male' and firstTableId = table1.firstTableId);

What is faster in MySQL? WHERE sub request = 0 or IN list

I was wondering what is better in MySQL. I have a SELECT query that exclude every entry associated to a banned userID.
Currently I have a subquery clause in the WHERE statement that goes like
AND (SELECT COUNT(*)
FROM TheBlackListTable
WHERE userID = userList.ID
AND blackListedID = :userID2 ) = 0
Which will accept every userID not present in the TheBlackListTable
Would it be faster to retrieve first all Banned ID in a previous request and replace the previous clause by
AND creatorID NOT IN listOfBannedID
LEFT JOIN / IS NULL and NOT IN are fastest:
SELECT *
FROM mytable
WHERE id NOT IN
(
SELECT userId
FROM blacklist
WHERE blackListedID = :userID2
)
or
SELECT m.*
FROM mytable m
LEFT JOIN
blacklist b
ON b.userId = m.id
AND b.blackListedID = :userID2
WHERE b.userId IS NULL
NOT EXISTS yields the same plan but due to implementation flaws is marginally less efficient:
SELECT *
FROM mytable
WHERE NOT EXISTS
(
SELECT NULL
FROM blacklist b
WHERE b.userId = m.id
AND b.blacklistedId = :userID2
)
All these queries stop on the first match in blacklist (hence performing a semi-join)
The COUNT(*) solution is the least efficient, since MySQL will calculate the actual COUNT(*) rather than stopping on the first match.
However, if you have a UNIQUE index on (userId, blacklistedId), this is not much of problem as there cannot be more than one match anyway.
Use EXISTS clause to check for user not in blacklist.
Sample Query
Select * from userList
where not exists( Select 1 from TheBlackListTable where userID = userList.ID)
IN clause is used when there is fixed values or low count of values.

How do I fetch query results with multiple criteria?

I have a table like this
Table
-----
userid
fieldid
fieldvalue
where userid and fieldid are the primary key pair for this table.
I want to make an sql query that finds all users that have fieldvalue equal to something for a selected fieldid
For example, for the values
fieldid: 817
fieldvalue: 'yes'
I can have an sql query like:
select userid FROM table where (fieldid=817 AND fieldvalue='yes')
This query works fine.
However if i have a second or a third criterion, making the query
like this:
select userid
FROM table
where (fieldid=817 AND fieldvalue='yes')
AND (fieldid=818 AND fieldvalue='no')
returns an empty result but the conditions are satisfied in the
individual criterion.
Is there any way to correct this ?
update
i forgot to write down a use case (appologies)
userid, fieldid, fieldvalue
1 , 817, yes
1, 818, no
1, 825, yes
2, 817, yes
2, 818, yes
3, 829, no
for this table i want an sql query that finds the users that have the following conditions satisfied : The fieldid 817 has a value of yes and the fieldid 818 a value of no
Using the OR suggestions i had so far satisfied either the fieldid 817 to have a value of yes OR the fieldid 818 to have a value of no
I want both conditions to be satisfied. In the above the expected result will be
userid
1
and not
userid
1
2
Since userid 2 doesn't satisfy both conditions. Apologies for the confusion.
You should use an OR between your different criteria.
SELECT userid
FROM table
WHERE (fieldid=817 AND fieldvalue='yes')
OR (fieldid=818 AND fieldvalue='no')
The difference between using AND/OR
The AND operator displays a record if both the first condition and the
second condition is true.
The OR operator displays a record if either the first condition or the
second condition is true.
EDIT: Based on your comments you can do it the following way
select t1.userid
FROM temp t1
where (t1.fieldid=817 AND t1.fieldvalue='yes')
AND EXISTS (SELECT userid
FROM temp t
WHERE t.userid = t1.userid
AND fieldid=818
AND fieldvalue='no')
see a sqlfiddle with a working copy
or even this way
select t1.userid
FROM temp t1
left join temp t2
on t1.userid = t2.userid
where (t1.fieldid=817 AND t1.fieldvalue='yes')
AND t2.fieldid=818 AND t2.fieldvalue='no'
here is another sqlfiddle
if you had more fields that you wanted to join on, then you would do the following:
select t1.userid
FROM temp t1
left join temp t2
on t1.userid = t2.userid
left join temp t3
on t1.userid = t3.userid
where (t1.fieldid=817 AND t1.fieldvalue='yes')
AND (t2.fieldid=818 AND t2.fieldvalue='no')
AND (t3.fieldid=819 AND t3.fieldvalue='no')
Try using OR
select userid FROM table where (fieldid=817 AND fieldvalue='yes') OR(fieldid=818 AND fieldvalue='no')
Your query wanted the fieldid to be 817 and 818 which isn't possible.
You should use OR not AND in your WHERE clause. Check out this link for a quick-and-easy explanation.
Hi Please try OR in Place of AND
select userid FROM table where (fieldid=817 AND fieldvalue='yes') OR (fieldid=818 AND fieldvalue='no')
If you use AND then it will check if both conditions fieldid=817 AND fieldvalue='yes' ANd fieldid=818 AND fieldvalue='no' must be fulfilled but it is not possible. ANd when you will use OR operator between both conditions then it will give result having both conditions ..
thanks
you need to use it like:
select userid
FROM table
where fieldid in (817, 818) and fieldvalue in ('yes', 'no');
In addition to the many suggestions of using OR, which are entirely correct, you could also use IN (which might make the query more readable if it scales).
SELECT userid FROM table WHERE (fieldid, fieldvalue) IN (
(817, 'yes'),
(818, 'no' )
);
To find all userid that satisfy both conditions, you need to self-join table to itself:
SELECT userid
FROM table AS t1 JOIN table AS t2 USING (userid)
WHERE
(t1.fieldid = '817' AND t1.fieldvalue = 'yes')
AND (t2.fieldid = '818' AND t2.fieldvalue = 'no' )

if "Subquery returns more than 1 row" consider it NULL

I'm trying to sync store ids on newtable with the ids from the maintable here:
UPDATE newtable t SET t.store_id = (SELECT store_id FROM maintable s
WHERE t.state = s.state AND s.city = t.city AND t.name = s.name)
Whenever a subquery returns more than one row it errors out with "Subquery returns more than 1 row", but when it returns zero rows the subquery is considered to have returned nothing so the store_id on newtable remains NULL. Nothing new here, it's just how it works.
I'd like to know if it's possible to let the subquery output the same as what it does when it has no matches when it has more than one matching row.
This way I'd get the store_id synced only for ONE matching row on the main table and skipped when more than one matching row comes out in the subquery.
I think you might be looking for a HAVING clause to force the query to match exactly once:
UPDATE newtable t
SET t.store_id = (
SELECT store_id
FROM maintable s
WHERE t.state = s.state
AND s.city = t.city
AND t.name = s.name
HAVING COUNT(*) = 1
)
That should make multiple matches behave the same as no matches. The HAVING clause is applied almost at the very end of the query process; if there are no matches from the WHERE or more than one match, then COUNT(*) = 1 will fail and the inner query will return nothing but if there is exactly one row then COUNT(*) = 1 will succeed and the inner query will return that single match.
You might consider putting a LIMIT 1 in your sub-query to better achieve what you are trying to accomplish, depending on your specific needs.
Otherwise, you should be able to get creative with IF or CASE:
UPDATE newtable t SET t.store_id = (
SELECT IF(num>1, NULL, storeid) FROM (
SELECT COUNT(*) AS num, storeid FROM maintable s WHERE t.state=s.state AND s.city=t.city AND t.name=s.name
)
)
Untested, but should get you in the ballpark.
UPDATE newtable t SET t.store_id = IFNULL((SELECT store_id FROM maintable s
WHERE t.state = s.state AND s.city = t.city AND t.name = s.name HAVING COUNT(*) = 1), t.store_id)
IFNULL(use_this_value_if_not_null,value_if_first_isnull)