Query in MySQL to Find Distinct Status Based on Primary Key - mysql

My question is regarding a MySQL query that I am trying to write. I have written some psuedo-code to help illustrate what query I am trying to write:
SELECT *
FROM persons AS p
INNER JOIN person_info AS pi
ON p.person_id = pi.person_id
WHERE status MAY INCLUDE lost, missing, or found
WHAT person_id has no instances of the found status
I'd like to know for each person_id (which can have multiple statuses), which do not have any instance of the status "found." I'm not concerned with just the records of lost and missing. I want to find the unique cases where there is no "found" status based on each unique, distinct person_id.

If I'm understand correctly, one option is to use not in:
select *
from persons
where personid not in (
select personid
from person_info
where status = 'found'
)
This will return all records from the persons table that don't have a matching record in the person_info table with status = 'found'.
Alternatively you can use left join/null check. Not exists can work, but may be slower with mysql. There are some potential issues with null checks as well. Depends on desired results at that point.

This is as far as I took it #sgeddes. In writing it I realized it just makes peoples eyes glaze over.
SQL NOT IN () danger
create table mStatus
( id int auto_increment primary key,
status varchar(10) not null
);
insert mStatus (status) values ('single'),('married'),('divorced'),('widow');
create table people
( id int auto_increment primary key,
fullName varchar(100) not null,
status varchar(10) null
);
Chunk1:
truncate table people;
insert people (fullName,status) values ('John Henry','single');
select * from mstatus where status not in (select status from people);
** 3 rows, as expected **
Chunk2:
truncate table people;
insert people (fullName,status) values ('John Henry','single'),('Kim Billings',null);
select * from mstatus where status not in (select status from people);
no rows, huh?
Obviously this is 'incorrect'. It arises from SQL's use of three-valued logic,
driven by the existence of NULL, a non-value indicating missing (or UNKNOWN) information.
With NOT IN, Chunk2 it is translated like this:
status NOT IN ('married', 'divorced', 'widowed', NULL)
This is equivalent to:
NOT(status='single' OR status='married' OR status='widowed' OR status=NULL)
The expression "status=NULL" evaluates to UNKNOWN and, according to the rules of three-valued logic,
NOT UNKNOWN also evaluates to UNKNOWN. As a result, all rows are filtered out and the query returns an empty set.
Possible solutions include:
select s.status
from mstatus s
left join people p
on p.status=s.status
where p.status is null
or use not exists

Related

MYSQL: select values when found in join, placeholder otherwise

I have a MYSQL database with three tables: MEETS, SWIMMERS, SWIMS, which I use to store a swim team's results.
What I am trying to do is write a select query which, for a given swimmer and event, generates a column which has one entry for each meet in the database - if the swimmer actually swam the event at the meet, select the time, if not, select a placeholder. Basically, I'm trying to de-raggedize my data:
I'm able to join the tables successfully, but what's below only returns the Meets where the swimmer HAS swum the event, not all meets. (I realize this is kind of a silly thing to need, but the graphing control I'm trying to feed it into is really finicky about what it will accept)
edit: Attempting to do better with providing a miniminal example.
Here are the CREATE and INSERT queries for the three tables:
CREATE TABLE MEETS ( M_ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT );
CREATE TABLE SWIMMERS ( SwimmerId INTEGER PRIMARY KEY AUTOINCREMENT, FirstName TEXT, LastName TEXT );
CREATE TABLE SWIMS ( SwimID INTEGER PRIMARY KEY AUTOINCREMENT, SwimmerID INTEGER, MeetID INTEGER, Event TEXT, TimeSec REAL );
INSERT into MEETS values (null,'Meet1');
INSERT into MEETS values (null,'Meet2');
INSERT into MEETS values (null,'Meet3');
INSERT into SWIMMERS values(null,'Fred','Barnes');
INSERT into SWIMS values(null,1,1,'50 Free',30.95);
INSERT into SWIMS values(null,1,2,'100 Free',66.25);
INSERT into SWIMS values(null,1,4,'50 Free',29.33);
Here's my test query for a particular swimmer and event:
SELECT B.M_ID , A.TimeSec
FROM SWIMS AS A LEFT JOIN MEETS AS B ON B.M_ID = A.MeetId
WHERE A.SwimmerID = 1 and A.Event = '50 Free'
this results in:
M_ID
TimeSec
1
30.95
3
29.33
Desired result:
M_ID
TimeSec
1
30.95
2
0 (or NULL)
3
29.33
I think the problem is that the where clause is false on the missing row, but I'm not sure how to solve it. Maybe a union, but I can't figure out how to structure it. Any help greatly appreciated!
If you need all meets you need to fetch from meets then join the others.
SELECT A.M_ID , B.TimeSec
FROM MEETS AS A
LEFT JOIN SWIMS AS B ON A.M_ID = B.MeetId AND B.SwimmerID = 1 and B.Event = '50 Free'

What's wrong with my INSERT ~ SELECT ~ ON DUPLICATE KEY query on MySql?

Hello, everybody.
I've made a query that use INSERT .. UPDATE .. ON DUPLICATE.
But query that I made didn't work because syntax problem and I don't know why!
Here's what I tried to do.
What I try to do is to give three 'magic_potion' to every account.
First, I made two tables.
CREATE TABLE account(
account_no INT AUTO_INCREMENT,
account_id VARCHAR(32) NOT NULL,
account_pw VARCHAR(40) NOT NULL,
PRIMARY KEY(account_no)
);
CREATE TABLE item(
item_no INT AUTO_INCREMENT,
account_id VARCHAR(32) NOT NULL,
item_name VARCHAR(32) NOT NULL,
item_count SMALLINT NOT NULL,
item_status SMALLINT NOT NULL,
PRIMARY KEY(item_no)
);
Second, I put three account for test.
INSERT INTO account (account_id, account_pw) VALUES ('James', MD5('James')), ('Andy', MD5('James')), ('Angela', MD5('James'));
Third, I gave magic_potion to every account.
INSERT
INTO item
SELECT NULL, A.account_id, item_name, item_count, item_status
FROM account A
CROSS JOIN (SELECT 'magic_potion' AS item_name, 3 AS item_count, 1 AS item_status) B;
fourth, I put another account.
INSERT INTO account (account_id, account_pw) VALUES ('Judy', MD5('Judy')), ('Tom', MD5('Tom'));
fifth, Now, I want to give(=INSERT) two 'magic_potion' to newly added account and want to add(=UPDATE) one 'magic_potion' to previous account. So I made below query.
INSERT
INTO item
SELECT NULL, A.account_id, item_name, item_count, item_status
FROM account A
CROSS JOIN (SELECT 'magic_potion' AS item_name, 3 AS item_count, 1 AS item_status) B
ON DUPLICATE KEY UPDATE item_name = 'magic_potion', item_count = item_count + 1, item_status = 1;
But this query didn't work. System message said,
Error code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'KEY UPDATE item_name = 'magic_potion', item_count = item_count + 1, item_status ' at line 6
I don't know what's wrong with my query.
Please, help me.
Thank you!
The syntax error is easy -- [CROSS] JOIN can take an optional ON clause, so the parser thinks ON DUPLICATE ... is such a clause. To avoid the syntax error, you need to enclose the SELECT in brackets:
INSERT
INTO item (
SELECT NULL, A.account_id, item_name, item_count, item_status
FROM account A
CROSS JOIN (SELECT 'magic_potion' AS item_name, 3 AS item_count, 1 AS item_status) B
) ON DUPLICATE KEY UPDATE item_name = 'magic_potion', item_count = item_count + 1, item_status = 1;
For a note, CROSS is redundant here.
Then you'll hit a context problem (ambiguous item_count). You don't need to solve it yet, because the query won't do what you expect anyway. It will never reach the ON DUPLICATE KEY UPDATE clause, because your only unique key is the primary key item_no, and you are inserting NULL into it, which means it will be always generated by auto-increment -- that is, you'll keep inserting rows every time.
There are different solutions for this, depending on what exactly you want to achieve -- you can modify the query, or you can add a UNIQUE KEY on whichever field or combination is supposed to be unique in that table (maybe it's account_id, or maybe it's a combination account_id + item_name -- it's unclear from the data sample).

DELETE FROM X WHERE IN (..) is not deleting rows

I have an issue where I'm filtering a table by a bunch of different values. There's about 30 different filters on this table and since I'm still a novice with MySQL I have it done in a stored procedure executing multiple DELETE queries from a temporary table to filter. This example is only going to show the filter that I'm having issues from, which is a DELETE FROM table WHERE value IN () query.
Here's a test Schisma:
CREATE TABLE accounts (
user_id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(40) NOT NULL,
PRIMARY KEY(user_id)
);
CREATE TABLE blocked (
user_id INT(11) NOT NULL,
other_id INT(11) NOT NULL,
);
INSERT INTO accounts (name) VALUES ('Chris'), ('Andy');
INSERT INTO blocked (user_id, other_id) VALUES (1, 2);
The queries create two tables: the accounts table containing two rows, and the blocked table containing one row where user_id 1 has user_id 2 blocked.
Here's the query that's causing us some problem (Please note that the queries are actually more complex than displayed, but the DELETE query is 100% the same, and the issue persists through the test example provided):
BEGIN
#user_in input is a int(11) value bassed in the CALL FUNCTION(ID).
CREATE TEMPORARY TABLE IF NOT EXISTS filtered AS (SELECT * FROM accounts);
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.user_id = user_in);
SELECT * FROM filtered;
END
This query should delete the row with the user_id field of 2, as in the blocked table the only row is (1, 2).
Running the SELECT query directly providing the user_id returns the other_id of 2.
SELECT other_id FROM blocked WHERE blocked.other_id = 2;
However, the stored procedure returns both rows, instead of just one. Why?
NOTE: The above query is to show what is returned when the query SELECT other_id FROM blocked WHERE blocked.user_id = user_in, another example would be SELECT other_id FROM blocked WHERE blocked.user_id = 1 granted user_in is set to 1. Both of these queries will return a set of (2) which would make the delete query look like DELETE FROM filtered WHERE user_id IN (2). This is not working, for whatever reason.
To get a simple select of that users use next query
SELECT * FROM accounts WHERE accounts.user_id NOT IN (SELECT distinct blocked.other_id from blocked)
To do it with one single select without deleting rows from temporary table use next query:
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS filtered AS (SELECT * FROM accounts WHERE accounts.user_id NOT IN (SELECT distinct blocked.other_id from blocked));
SELECT * from filtered;
END
No need for select all in temporary table first and then delete specific rows.
Hope it helps
EDIT:
I'v read the question and still a bit confused about you problem. But i checked this solution and it works perfectly so i don't understand what is problem with this. In your procedure you have
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.user_id = user_in);
and after that you say that
SELECT other_id FROM blocked WHERE blocked.other_id = 2;
And i can say that blocked.other_id and blocked.user_id are two different columns.
No disrespect but amateur mistake to mix up columns. :)
The problem here is with this statement:
DELETE FROM filtered WHERE user_id IN (SELECT other_id FROM blocked WHERE blocked.other_id = user_id);
Try changing it to this:
DELETE FROM filtered WHERE user_id
IN (SELECT other_id FROM blocked);
Reason being that the blocked table has both a other_id and a user_id column. So where you are attempting to join out to the filtered table you are in fact comparing the other_id and user_id columns in the blocked table only. Which are not equal. So no delete happens.

Update with Subquery never completes

I'm currently working on a project with a MySQL Db of more than 8 million rows. I have been provided with a part of it to test some queries on it. It has around 20 columns out of which 5 are of use to me. Namely: First_Name, Last_Name, Address_Line1, Address_Line2, Address_Line3, RefundID
I have to create a unique but random RefundID for each row, that is not the problem. The problem is to create same RefundID for those rows whose First_Name, Last_Name, Address_Line1, Address_Line2, Address_Line3 as same.
This is my first real work related to MySQL with such large row count. So far I have created these queries:
-- Creating Teporary Table --
CREATE temporary table tempT (SELECT tt.First_Name, count(tt.Address_Line1) as
a1, count(tt.Address_Line2) as a2, count(tt.Address_Line3) as a3, tt.RefundID
FROM `tempTable` tt GROUP BY First_Name HAVING a1 >= 2 AND a2 >= 2 AND a3 >= 2);
-- Updating Rows with First_Name from tempT --
UPDATE `tempTable` SET RefundID = FLOOR(RAND()*POW(10,11))
WHERE First_Name IN (SELECT First_Name FROM tempT WHERE First_Name is not NULL);
This update query keeps on running but never ends, tempT has more than 30K rows. This query will then be run on the main DB with more than 800K rows.
Can someone help me out with this?
Regards
The solutions that seem obvious to me....
Don't use a random value - use a hash:
UPDATE yourtable
SET refundid = MD5('some static salt', First_Name
, Last_Name, Address_Line1, Address_Line2, Address_Line3)
The problem is that if you are using an integer value for the refundId then there's a good chance of getting a collision (hint CONV(SUBSTR(MD5(...),1,16),16,10) to get a SIGNED BIGINT). But you didn't say what the type of the field was, nor how strict the 'unique' requirement was. It does carry out the update in a single pass though.
An alternate approach which creates a densely packed seguence of numbers is to create a temporary table with the unique values from the original table and a random value. Order by the random value and set a monotonically increasing refundId - then use this as a look up table or update the original table:
SELECT DISTINCT First_Name
, Last_Name, Address_Line1, Address_Line2, Address_Line3
INTO temptable
FROM yourtable;
set #counter=-1;
UPDATE temptable t SET t,refundId=(#counter:=#counter + 1)
ORDER BY r.randomvalue;
There are other solutions too - but the more efficient ones rely on having multiple copies of the data and/or using a procedural language.
Try using the following:
UPDATE `tempTable` x SET RefundID = FLOOR(RAND()*POW(10,11))
WHERE exists (SELECT 1 FROM tempT y WHERE First_Name is not NULL and x.First_Name=y.First_Name);
In MySQL, it is often more efficient to use join with update than to filter through the where clause using a subquery. The following might perform better:
UPDATE `tempTable` join
(SELECT distinct First_Name
FROM tempT
WHERE First_Name is not NULL
) fn
on temptable.First_Name = fn.First_Name
SET RefundID = FLOOR(RAND()*POW(10,11));

How to select some default value if there is no such column in a table?

For example, I have a table that doesn't have a column "type". But I need to have my sql query having that column. When I run the query I get an error:
SELECT t.foo, t.boo, t.type FROM tabl AS t;
Unknown column 't.type' in 'field list'
I need something like ternary operator. I tried these solutions but they both do not work:
SELECT f.foo, f.boo, IF(f.type IS NULL, 'x', f.type) AS type FROM tabl AS f
SELECT f.foo, f.boo, (CASE WHEN f.type IS NULL THEN "x" ELSE f.type) AS type FROM tabl AS f
Is there a possibility to implement such a query?
Use something like this. Assume you want to join 2 tables rows and one is missing the column:
SELECT t.foo, t.boo, t.type FROM tabl1 as t1
UNION
SELECT t.foo, t.boo, NULL as type FROM tabl2 AS t2;
You can replace NULL with a string "" or whatever you application desires.
Unfortunately, that is not the way columns work. If you need to introspect your table to determine if it have this column, then you might try using data in the information_schema to get at this. Overall sounds like a weird approach to me. Why not just create all the tables with this column?
I think you mean that you have a table with entries ... Some of your entries has got no "type" column filled in. To have default value, you need to change your table. You can change it using either phpmyadmin (set default value) or through SQL code.
This would be something on these lines:
CREATE TABLE Persons
(
P_Id int NOT NULL,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255) DEFAULT 'London'
)
This sets each entry's city to be London by default