SQL WHERE clause logic mismatch - mysql

I have a database table with the following information
+----+--------+--------+--------+
| id | status | action | reason |
+----+--------+--------+--------+
| 1 | CN | WAPP | BDEC |
| 2 | CN | DENY | PREF |
| 3 | AP | APPL | MI |
| 4 | AP | MATR | AUTO |
+----+--------+--------+--------+
A MySQL query to select data from this table has been written with a specific filter to exclude all records matching CN-WAPP-BDEC.
SELECT *
FROM `table`
WHERE (`status` != 'CN' AND `action` != 'WAPP' AND `reason` != 'BDEC');
However, it only returns records 3 and 4, record #2 is also excluded.
If I change the query to:
SELECT *
FROM `table`
WHERE CONCAT(`status`, `action`, `reason`) != 'CNWAPPBDEC';
Only then, do I get the expected result: records 2, 3, and 4.
As far as I can tell, the queries should both do the exact same thing, but they obviously aren't, and I'm confused as to why that is. Any insight would be appreciated.

Record 2 is excluded because status = CN, which makes the first condition status != 'CN' false.
Try this:
SELECT *
FROM `table`
WHERE (NOT (`status` == 'CN' AND `action` == 'WAPP' AND `reason` == 'BDEC'));

I think you want OR, not AND:
SELECT t.*
FROM `table` t
WHERE `status` <> 'CN' OR `action` <> 'WAPP' OR `reason` <> 'BDEC';
You can also phrase this using tuples:
WHERE (status, action, reason) not in ( ('CN', 'WAPP', 'BDEC') );

Related

SQL select with conditions to find other record with better value

I have the following table with some data:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
CREATE TABLE `activities` (
`id` int(10) UNSIGNED NOT NULL,
`project_id` int(10) UNSIGNED NOT NULL,
`user_id` int(10) UNSIGNED NOT NULL,
`task_hour` double(8,2) NOT NULL,
`validated` tinyint(1) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `activities` (`id`, `project_id`, `user_id`, `task_hour`, `validated`) VALUES
(1, 1, 1, 10.00, 1),
(2, 1, 1, 20.00, 0),
(3, 2, 1, 5.00, 1),
(4, 3, 1, 30.00, 0);
When I do a SELECT user_id,project_id,task_hour,validated FROM activities, here is what I get:
| user_id | project_id | task_hour | validated |
|---------|------------|-----------|-----------|
| 1 | 1 | 10 | true |
| 1 | 1 | 20 | false |
| 1 | 2 | 5 | true |
| 1 | 3 | 30 | false |
I would like to get the following result from a select:
| user_id | task_hour_total |
|---------|-----------------|
| 1 | 45 |
This result comes from the sum of task_hour for user 1 with the condition that the task_hour can be added only if validated is true OR in case validated is false, that there is not a record in the table for the same user_id and project_id with validated is true.
So the reasoning for each line would be:
| user_id | project_id | task_hour | validated |
|---------|------------|-----------|-----------|
| 1 | 1 | 10 | true | -> include in the sum because validated is true
| 1 | 1 | 20 | false | -> do not include in the sum because validated is false and there is the first record which has same user_id, same project_id and validated is true
| 1 | 2 | 5 | true | -> include in the sum because validated is true
| 1 | 3 | 30 | false | -> include in the sum because validated is false and there is no record in this table for user_id 1 and project_id 3 where validated is true
I have tried the following but it tells me that this is not the right structure in mysql. This is a first test to get a column to say if it found another record in the db with validated = true for same user_id and project_id:
select #u = user_id, #p = project_id,task_hour,validated
case when (select count(*) from activities where user_id = #u and project_id = #p and validated = true) > 1 then 'validated found' end as found
from activities
Thank you if you can help me on this one...
This would be very easy in standard SQL where you'd rank the records with ROW_NUMBER, but MySQL doesn't support this standard function. The ranking is simple: per user_id and project_id you want the better record. Better means validated true is preferred to false.
In MySQL true is 1 and false is 0. So you want the maximum validated per user_id and project_id. You can use an IN clause for this.
select user_id, sum(task_hour) as task_hour_total
from activities
where (user_id, project_id, validated) in
(
select user_id, project_id, max(validated)
from activities
group by user_id, project_id
)
group by user_id;
Still a simple query. The difference to the ROW_NUMBER method is that records must be read twice.
Ok, I found a way to do it. It is not very elegant but it works:
SELECT user_id,sum(task_hour)
FROM
(SELECT * FROM activities a1 WHERE a1.project_id NOT IN (SELECT project_id FROM activities as a2 WHERE validated = 1)
UNION SELECT * FROM activities WHERE validated = 1)
AS temp_table
GROUP BY user_id
If anyone knows a better solution than this, don't hesitate otherwise,I will stay with this long and complex select.
I found it simple to do writing the next query. I hope it help you.
SELECT user_id,
SUM(task_hour)
FROM activities
WHERE validated = 1
OR project_id NOT IN (SELECT project_id
FROM activities
WHERE validated = 1)
GROUP BY user_id;

SQL - Retrieving latest message for a CASE WHEN statement

I have a problem here trying to get one of my CASE WHEN statement to query each row for something called is_op as it's returning the same number for all rows. Here is the code:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid` FROM `bulletin_message` WHERE `bid` = 1 ORDER BY `mid` ASC LIMIT 1) = 5 THEN 1 ELSE 0 END AS `is_op`,
CASE WHEN `bulletin_message`.`uid` = 5 THEN 1 ELSE 0 END AS `is_me`
FROM`bulletin_message`
WHERE `bid` = 1
GROUP BY `mid`
ORDER BY `mid` ASC
As you can see I'm trying to select messages with the condition bid must equal to 1 and uid must equal to 5. While is_me returns the correct value for each row, is_op isn't reflecting the correct value for all the rows at all. It displays 1 at the result of the statement, rather than showing if a user is an OP or not based on the oldest value of mid or created_at. I don't think I am correctly querying each row like is_me statement.
This is all the data of the table:
mid = message uid; bid = bulletin/thread uid; uid = user uid
| mid | bid | uid | message | created_at |
---------------------------------------------------
| 3 | 1 | 5 | ... | ... |
| 5 | 1 | 6 | ... | ... |
| 6 | 2 | 7 | ... | ... |
| 9 | 1 | 5 | ... | ... |
| 10 | 1 | 7 | ... | ... |
| 11 | 1 | 6 | ... | ... |
What can be done to improve this line of code so that it can query each row? Thank you!
Edit: OP is Original Poster, sorry for not clarifying that! It's usually the person who post the first in each bid.
The problem is your subquery is based on a fixed predicate, ``bid= 1, so it is bound to return the same value for all rows.
Something like this would make more sense:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid`
FROM `bulletin_message` AS t2
WHERE t1.`bid` = t2.`bid`
ORDER BY `mid` ASC LIMIT 1) = t1.`uid`
THEN 1
ELSE 0
END AS `is_op`
FROM`bulletin_message` AS t1
ORDER BY `mid` ASC
The subquery is correlated using bid field: it returns the OP of the current thread.

How can I efficiently store a 2-way "like" system similar to Tinder?

On Tinder, when 2 members like each other, they are a "match" and are able to communicate. If only one member likes another, then it's not a match.
I'm trying to store this "Like" system in MySQL but can't figure out the best way to do it that's efficient. This is my setup right now.
mysql> desc likes_likes;
+--------------+----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| from_user_id | int(11) | NO | MUL | NULL | |
| to_user_id | int(11) | NO | MUL | NULL | |
| value | int(11) | NO | | NULL | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | YES | | NULL | |
+--------------+----------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
To find my matches, I would query something like...
SELECT to_user_id FROM likes_likes WHERE from_user_id = my_id AND value = 1 AND .... I don't know how to join the same table from here.
How do I perform the query on this table? If it's not efficient, what's a better structure to store this model?
1 is like, 0 is not like. Those are the only 2 values.
SELECT A.from_user_id AS userA, B.from_user_id AS userB
FROM likes_likes A
JOIN likes_likes B
ON A.from_user_id = B.to_user_id
AND A.to_user_id = B.from_user_id
AND A.id <> B.id
WHERE A.value = 1
AND B.value = 1
To find matches you can use a regular join with alias:
SELECT l1.from_user_id user1, l2.from_user_id user2
FROM likes_likes l1
INNER JOIN likes_likes l2 ON
l2.from_user_id = l1.to_user_id AND
l1.to_user_id = l2.from_user_id AND
l1.value = 1 AND l2.value = 1
The first condition checks whether the person user1 has liked or not liked user2 and that user2 has liked at least one other person.
The second condition completes the check so that we now have two persons who have expressed an opinion about each other.
The last two checks make sure that they both like each other :)
Here's a way using group by least(),greatest() to get each unique pair of users into a group and then checking if there are 2 rows per group
select least(from_user_id,to_user_id), greatest(from_user_id,to_user_id)
from likes_likes
where value = 1
-- and my_id in (from_user_id,to_user_id)
group by least(from_user_id,to_user_id), greatest(from_user_id,to_user_id)
having count(*) = 2
If it's possible to have multiple likes from the same user to another user (i.e. user 'A' likes user 'B' twice) then use having count(distinct from_user_id) = 2
Do you actually need value? If there is no row there is no like. From this query you should get 1 for a match and 0 for no mutual match.
SELECT
COUNT(*)
FROM
likes_like i_like_you
JOIN likes_like you_like_me ON i_like_you.to_user_id = you_like_me.from_user_id
WHERE
i_like_you.from_user_id = #my_id
AND you_like_me.from_user_id = #your_id
Is there any reason for id? It seems like the pair (from_user_id, to_user_id) should be UNIQUE, hence could be the 'natural' PRIMARY KEY.
I have yet to see any good argument for needing value.
So the table has shrunk to
CREATE TABLE likes_likes (
from_user_id ...,
to_user_id ...,
created_at ...,
updated_at ...,
PRIMARY KEY(from_user_id, to_user_id), -- serves as the necessary INDEX.
) ENGINE=InnoDB;
SELECT A.from_user_id AS userA,
B.from_user_id AS userB
FROM likes_likes A
JOIN likes_likes B
ON A.from_user_id = B.to_user_id
AND A.to_user_id = B.from_user_id
(I'm assuming you disallow a person liking himself.)

How to get single row when foreign key is similar

I have a table tbl_issue with columns
> serial_no.
Issue_no. (f.k)
From_Section
To_Section
+-----+---------------+-------------+-------------+
| id | issue no | from section| to_section |
+-----+---------------+-------------+-------------+
| 1 | 223 | MFA | N/A |
| 2 | 223 | N/A | LOG |
+----------+----------+-------------+--------------+
When I query the table on issue no. I get two rows, can anyone kindly help how can I get a single record and no 'N/A'
For the example you gave this would work:
WITH combined AS
(
SELECT i.issue_no,
CASE WHEN i.from_section = 'N/A' THEN i2.from_section ELSE i.from_section END from_section,
CASE WHEN i.to_section = 'N/A' THEN i2.to_section ELSE i.to_section END to_section
FROM dbo.tbl_issue i
INNER JOIN dbo.tbl_issue i2
ON i2.issue_no = i.issue_no
)
SELECT DISTINCT *
FROM combined c
WHERE c.from_section <> 'N/A' AND c.to_section <> 'N/A'
This is supposing that the 'N/A' is does not mean NULL...If you meant NULL replace "= 'N/A'" with IS NULL and replace "<> 'N/A'" with IS NOT NULL

how to group by with a sql subqueries

I can't think clearly at the moment, I want to return counts by station_id, an example of output would be:
station 1 has 3 fb post, 6 linkedin posts, 5 email posts
station 2 has 3 fb post, 6 linkedin posts, 5 email posts
So I need to group by the station id, my table structure is
CREATE TABLE IF NOT EXISTS `posts` (
`post_id` bigint(11) NOT NULL auto_increment,
`station_id` varchar(25) NOT NULL,
`user_id` varchar(25) NOT NULL,
`dated` datetime NOT NULL,
`type` enum('fb','linkedin','email') NOT NULL,
PRIMARY KEY (`post_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=x ;
The query I have so far is returning station 0 as having 2 linkedin posts when it has one (2 in the db tho)
SELECT Station_id, (select count(*) FROM posts WHERE type = 'linkedin') AS linkedin_count, (select count(*) FROM posts WHERE type = 'fb') AS fb_count, (select count(*) FROM posts WHERE type = 'email') AS email_count FROM `posts` GROUP BY station_id;
Or, the fastest way, avoiding joins and subselects to get it in the exact format you want:
SELECT
station_id,
SUM(CASE WHEN type = 'linkedin' THEN 1 ELSE 0 END) AS 'linkedin',
SUM(CASE WHEN type = 'fb' THEN 1 ELSE 0 END) AS 'fb',
SUM(CASE WHEN type = 'email' THEN 1 ELSE 0 END) AS 'email'
FROM posts
GROUP BY station_id;
Outputs:
+------------+----------+------+-------+
| station_id | linkedin | fb | email |
+------------+----------+------+-------+
| 1 | 3 | 2 | 5 |
| 2 | 2 | 0 | 1 |
+------------+----------+------+-------+
You may also want to put an index on there to speed it up
ALTER TABLE posts ADD INDEX (station_id, type);
Explain output:
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
| 1 | SIMPLE | posts | index | NULL | station_id | 28 | NULL | 13 | Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-------------+
As implied by gnif's answer, having three correlated sub_queries has a performance over-head. Depending on the DBMS you're using, it could perform similarly to having a self join three times.
gnif's methodology ensures that the table is only parsed once, without the need for joins, correlated sub_queries, etc.
The immediately obvious down-side of gnif's answer is that you don't ever get records for 0's. If there are no fb types, you just don't get a record. If that is not an issue, I'd go with his answer. If it is an issue, however, here is a version with similar methodology to gnif, but matching your output format...
SELECT
station_id,
SUM(CASE WHEN type = 'linkedin' THEN 1 ELSE 0 END) AS linkedin_count,
SUM(CASE WHEN type = 'fb' THEN 1 ELSE 0 END) AS fb_count,
SUM(CASE WHEN type = 'email' THEN 1 ELSE 0 END) AS email_count
FROM
posts
GROUP BY
station_id
Give this a go:
SELECT station_id, type, count(*) FROM posts GROUP BY station_id, type
The output format will be a little different to what your attempting to get, but it should provide the statistics your trying to retrieve. Also since its a single query it is much faster.
-- Edit, added example result set
+------------+----------+----------+
| station_id | type | count(*) |
+------------+----------+----------+
| 1 | fb | 2 |
| 1 | linkedin | 3 |
| 1 | email | 5 |
| 2 | linkedin | 2 |
| 2 | email | 1 |
+------------+----------+----------+
try this:
SELECT p.Station_id,
(select count(*) FROM posts WHERE type = 'linkedin' and station_id=p.station_id) AS linkedin_count,
(select count(*) FROM posts WHERE type = 'fb' and station_id=p.station_id) AS fb_count,
(select count(*) FROM posts WHERE type = 'email' and station_id=p.station_id) AS email_count
FROM `posts` p GROUP BY station_id