Need help returning unique results in mySQL - mysql

I am having trouble and need some help finding a correct SQL query. Here is my code so far:
SELECT firstname, lastname, date_of_birth
FROM `data`
WHERE firstname IN (SELECT firstname
FROM `data`
WHERE diagnosis_location = 'Mayo')
AND lastname IN (SELECT lastname
FROM `data`
WHERE diagnosis_location = 'Mayo')
AND date_of_birth IN (SELECT date_of_birth
FROM `data`
WHERE diagnosis_location = 'Mayo')
AND firstname IN (SELECT firstname
FROM `data`
WHERE diagnosis_location = 'Lahey')
AND lastname IN (SELECT lastname
FROM `data`
WHERE diagnosis_location = 'Lahey')
AND date_of_birth IN (SELECT date_of_birth
FROM `data`
WHERE diagnosis_location = 'Lahey')
Yes, it's a monster of a query and probably isn't all that efficient. But what I am trying to do is only return the first name, last name, and date of birth of all patients diagnosed at both the 'Mayo' location and the 'Lahey' location. This query does return those patients, but it returns multiple rows of the same exact data.
How could I limit it so duplicates of the same exact results are trimmed out?

Use:
SELECT firstname, lastname, date_of_birth
FROM `data`
WHERE diagnosis_location IN ('Mayo', 'Lahey')
GROUP BY firstname, lastname, date_of_birth
HAVING COUNT(DISTINCT diagnosis_location) = 2

You should rather have a look at using EXISTS for something like this
Maybe try something like
SELECT DISTINCT
firstname,
lastname,
date_of_birth
FROM `data` d
WHERE EXISTS (
SELECT *
FROM `data` dE
WHERE d.firstname = dE.firstname
AND d.lastname = dE.lastname
AND d.date_of_birth = dE.date_of_birth
AND diagnosis_location ='Mayo'
)
AND EXISTS (
SELECT *
FROM `data` dE
WHERE d.firstname = dE.firstname
AND d.lastname = dE.lastname
AND d.date_of_birth = dE.date_of_birth
AND diagnosis_location = 'Lahey'
)

try this:
select `firstname`, `lastname`, `date_of_birth`
from `data`
where `diagnosis_location`='Mayo' or `diagnosis_location`='Lahey'
group by `firstname`, `lastname`, `date_of_birth`
having count(`diagnosis_location`) = 2

Related

MySQL - Return from function only if single row matches

I have a MySQL table of users as follows:
CREATE TABLE `users` (
`ID` INT NOT NULL, -- NOTE: in practice, I'm using BINARY(16) and
-- the UUID()-function to create user IDs.
-- 'INT' is only a simplification for this
-- stackoverflow question.
`FirstName` NVARCHAR(100) NOT NULL,
`LastName` NVARCHAR(100) NOT NULL,
-- ...
PRIMARY KEY (`ID`)
);
INSERT INTO `users` (`ID`, `FirstName`, `LastName`)
VALUES (0, 'Albus', 'Dumbledore'),
(1, 'Lord', 'Voldemort'),
(2, 'Harry', 'Potter'),
(3, 'Hermione', 'Granger');
I'd like to create a user-defined function which returns the ID of the row matching a FirstName and LastName combination if (and only if) the results are unique (i.e. only one row matches the query):
CREATE FUNCTION `FindUser`(`first_name` NVARCHAR(100), `last_name` NVARCHAR(100)
RETURNS INT
BEGIN
RETURN (SELECT `ID`
FROM `users`
WHERE ((first_name is NULL) OR (`FirstName` LIKE CONCAT('%', first_name, '%')))
AND ((last_name Is NULL) OR (`LastName` LIKE CONCAT('%', last_name, '%')))
LIMIT 1);
END
This works as expected on the following examples:
SELECT `FindUser`(NULL, 'potter');
-- | ID |
-- |----|
-- | 2 |
SELECT `FindUser`('obama', NULL);
-- | ID |
-- |----|
However, this does not work on SELECT FindUser(NULL, 'or');, as the token 'or' could match 0 | Albus | Dumbledore and 1 | Lord | Voldemort.
I tried the following:
SET #cnt = 0;
SET #id = NULL;
SELECT #id = u.id, #cnt = COUNT(id)
FROM users u
WHERE ...; -- same conditions as before
RETURN IF(#cnt = 1, #id, NULL);
However, that does not work, as #id and #cnt will always be overwritten by the last line.
The alternative would be to perform two queries, but that is inefficient.
How could I solve the problem most efficiently?
Providing you're using a MySql version that supports window functions a simple modification you can make is to conditionally count the number of rows:
RETURN (
SELECT CASE WHEN count(*) over() = 1 then ID ELSE null END
FROM users
WHERE (first_name is NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name Is NULL OR LastName LIKE CONCAT('%', last_name, '%'))
LIMIT 1
);
Demo Fiddle
You could use aggregation and set the condition in the HAVING clause:
CREATE FUNCTION FindUser(first_name NVARCHAR(100), last_name NVARCHAR(100))
RETURNS INT
BEGIN
RETURN (
SELECT MAX(ID)
FROM users
WHERE (first_name IS NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name IS NULL OR LastName LIKE CONCAT('%', last_name, '%'))
GROUP BY NULL -- you can omit this clause
HAVING COUNT(*) = 1
);
END;
See the demo.
I suspect that for the edge case where there is only 1 row in the table and the parameters that you pass for the function are both null you don't want the ID of that row returned.
For this case you should add one more condition in the WHERE clause to make sure that at least one of the parameters is not null:
WHERE (first_name IS NOT NULL OR last_name IS NOT NULL)
AND (first_name IS NULL OR FirstName LIKE CONCAT('%', first_name, '%'))
AND (last_name IS NULL OR LastName LIKE CONCAT('%', last_name, '%'))

SQL SELECT showing records only if the number of records is greater than N

I have a table defined like this (MySQL 5.1):
CREATE TABLE mysql_test_a (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
);
Sample dataset:
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('1', 'Marcello', 'Santucci', 'marcello#tux.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('2', 'Mike', 'Santucci', 'mike#tux.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('3', 'Anna Maria', 'Gabriele', 'anna.maria#gabriele.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('4', 'Matilde Josefa', 'Santucci', 'matilde.josefa#tux.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('5', 'Milena', 'Santucci', 'mile#tux.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('6', 'Luca', 'Pensa', 'luca#pensa.net', CURRENT_TIMESTAMP);
INSERT INTO `mysql_test_a` (`id`, `firstname`, `lastname`, `email`, `reg_date`) VALUES ('7', 'Lorenzo', 'Pensa', 'lo#pensa.net', CURRENT_TIMESTAMP);
I need to show records matching a certain criteria (lets suppose lastname = 'Santucci' ) only if the number of records is greater than a certain defined limit (lets say 2).
I tried in various way without success the most promising form was:
SELECT
id,
firstname,
lastname
FROM
mysql_test_a
WHERE
lastname = 'Santucci'
HAVING COUNT(*) > 2
It returns only the first record.
I would prefer to use something like this form because HAVING clause will enable the use of a parameter.
--- LATE UPDATE ---
I have to be more specific on the solution: I'm looking for something that do not deal with the inner SELECT and more specifically its WHERE clause because, as I pointed out, the one provided is pretty hypotetical (i.e. it can be quite different from this and much more complex). Of course I appreciate any other hint.
You can use the sub-query in your query as follows:
SELECT id, firstname, lastname
FROM mysql_test_a a
WHERE lastname = 'Santucci'
and (select count(1) from mysql_test_a b where b.lastname = a.lastname) > 2
I am guessing that your result is
1 Marcello Santucci
but you want something like this:
1 Marcello Santucci
2 Mike Santucci
4 Matilde Josefa Santucci
5 Milena Santucci
In this case, you can use this query, similar to what #Popeye suggested:
SELECT id, firstname, lastname
FROM mysql_test_a tbl
WHERE (SELECT count(*) FROM mysql_test_a sbq WHERE sbq.lastname = tbl.lastname) > 2
or this one, based on the usage of the 'in' operator
SELECT * from mysql_test_a
WHERE lastname IN (
SELECT lastname
FROM mysql_test_a
GROUP BY lastname
HAVING COUNT(lastname) >2
)
You can add 'WHERE' clauses to limit the result to 'Santucci', but I assume that a more generic answer is of interest to you.
I have also prepared a small fiddle that you can play with http://sqlfiddle.com/#!9/b1a727/16
You may be looking for this:
SELECT
*
FROM
(SELECT
id,
firstname,
lastname
FROM
mysql_test_a
WHERE
lastname = 'Santucci') a,
(SELECT
id,
firstname,
lastname
FROM
mysql_test_a
WHERE
lastname = 'Santucci'
HAVING COUNT(*) > 2) b
WHERE
a.lastname = b.lastname
If you are running MySQL 8.0, I would recommend a window count:
select id, firstname, lastname
from (
select t.*, count(*) over() as cnt
from mysql_test_a a
where lastname = 'Santucci'
) t
where cnt > 2
We can generalize this to handle multiple last names at once:
select id, firstname, lastname
from (
select t.*, count(*) over(partition by lastname) as cnt
from mysql_test_a a
) t
where cnt > 2
order by lastname
The most efficient method might be exists:
select t.*
from mysql_test_a t
where lastname = 'Santucci' and
exists (select 1
from mysql_test_a t2
where t2.lastname = t.lastname and
t2.id <> t.id
);
For performance, you want an index on mysql_test_a(lastname).

Operand Should Contain 1 Column(s), Trying to generate volunteer data

I can't seem to troubleshoot my problem.
My stored procedure:
CREATE DEFINER=`myschoolusername`#`%` PROCEDURE `generate_volunteers`(in nfolks int)
BEGIN
set #i=0;
while #i < nfolks do
insert into Volunteer(firstname, lastname, dateofbirth)
values (((floor(1+(rand()*(4-1))), "Fred", "Wang", "Fatimah", "Marcella")),
((floor(1+(rand()*(3-1))), "Kaser", "Fang", "Kumar")),
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY));
set #i = #i+1;
end while;
END
Additionally, here is my volunteer table in my MYSQL script:
drop table if exists Volunteer;
create Table Volunteer(
member_num int not null auto_increment primary key,
firstname varchar(20) not null,
lastname varchar(20) not null,
dateofbirth date not null
);
I am trying to insert 500 lines into this table, however error 1305 is coming up.
Any help is heavily appreciated, I am quite unsure of where to go from this point.
This logic doesn't do anything:
(floor(1+(rand()*(4-1))), "Fred", "Wang", "Fatimah", "Marcella"))
Although not the most efficient, this should be fine for 500 rows:
insert into Volunteer(firstname, lastname, dateofbirth)
select f.firstname, l.lastname,
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY)
from (select 'Fred' as firstname union all
select 'Wang' union all
select 'Fatimah' union all
select 'Marcella'
) f cross join
(select 'Kaser' as lastname union all
select 'Fang' union all
select 'Kumar'
) l
order by rand()
limit 1;
I think you are actually trying to write:
insert into Volunteer(firstname, lastname, dateofbirth)
select elt(floor(rand() * 4) + 1,
'Fred', 'Wang', 'Fatimah', 'Marcella'
) as firstname,
elt(floor(rand() * 3) + 1,
'Kaser', 'Fang', 'Kumar'
) as lastname,
DATE_ADD('1965-01-01', INTERVAL rand()*200000 DAY);

MySQL Rows Based On Prority Values

Lets say I have a table with four columns: FirstName, LastName, Number (not a primary key) and Status. If a there are a persons with the same First name, Last name and number, but differing status (where status is a string such as "King" or "Queen" or "Jack").
I want to retrieve all the values from the table for all columns, but if there are duplicates where there are those with the same first name, last name, and number, I want to get those with status of "King", and if there a duplicates without the "King" status then get those with "Queen" and if there is no duplicates with "King" or "Queen" than only get one of those with "Jack".
Basically, the order of priority is King, Queen, and then Jack. so, I want all values from the table but if there are duplicates only include the one with the highest priority. I did some research and it appears that SQL implementations other than MYSql provide functions such as dense_rank, but I need to implement this in MYSql and I cannot find any way how.
try using session variable:
SET #row_number:=1;
SELECT
FirstName,
LastName,
Number,
Status
FROM(
SELECT
#row_number:=
CASE
WHEN #FirstName = FirstName AND #LastName = LastName AND #Number = Number
THEN #row_number + 1
ELSE 1
END AS num,
#FirstName := FirstName as FirstName,
#LastName := LastName as LastName,
#Number := Number as Number,
Status
FROM
t1
ORDER BY
FirstName,
lastName,
Number,
CASE
WHEN STATUS = 'King' THEN '1'
WHEN STATUS = 'Queen' THEN '2'
WHEN STATUS = 'Jack' THEN '3'
END
) as ttt
WHERE num = 1;
One method of doing this involves union all:
select t.*
from t
where t.status = 'King'
union all
select t.*
from t
where t.status = 'Queen' and
not exists (select 1 from t t2 where t2.name = t.name andt2.status in ('King'))
union all
select t.*
from t
where t.status = 'Jack' and
not exists (select 1 from t t2 where t2.name = t.name and t2.status in ('King', 'Queen'));
Another method uses a correlated subquery:
select t.*
from t
where (name, field(t.status, 'King', 'Queen', 'Jack')) in
(select t2.name, max(field(t2.status, 'King', 'Queen', 'Jack'))
from t t2
where t2.status in ('King', 'Queen', 'Jack')
group by t2.name
);

Get the count and total count joining 2 tables in mysql

I have 2 tables in the MySQL database :
1.
p_code{
code varchar(10) primary key,
discount decimal(4,2) not null,
valid_till date not null,
daily int not null,
total int non null,
max_amount decimal (6, 2) not null
}
2.
p_user{
code varchar(10) not null,
email varchar(50) not null,
date date not null,
primary key (code, email, date),
foreign key (code) references p_code(code)
}
now I want to get for a code in p_code total how many times an email has been used, total how many time the email has been used today and the details of the code.
I have tried the following query :
SELECT pc.discount, pc.valid, pc.daily, pc.total, pc.max_amount, c.tcount, c.count
FROM p_code AS pc
LEFT JOIN (
SELECT t.code, t.email, t.tcount, p.count
FROM (
SELECT code, email, COUNT( email ) AS tcount
FROM p_user
GROUP BY code, email
) AS t
LEFT JOIN (
SELECT code, email, COUNT( email ) AS count
FROM p_user
WHERE `date` = CURDATE( )
GROUP BY code, email
) AS p ON ( t.code, t.email ) = ( p.code, p.email )
) AS c ON pc.code = c.code
WHERE c.email = ?
AND pc.code = ?
But the problem is that if I do not have any entry for the code and email in the table p_user, it does not return any row.
What I require that it should return all the columns from p_code and 0 and 0 for tcount and count columns.
I think you can simplifiy your query this way, and anyway you'll need to put the condition on the left joined data... in the left join.
SELECT
c.discount,
c.valid,
c.daily,
c.total,
c.max_amount,
count(u.email) as totalCount,
sum(case when u.`date` = CURDATE() then 1 else 0 end) as dailyCount
FROM p_code c
LEFT JOIN p_user u on u.code = c.code and u.email = ?
WHERE c.code = ?
GROUP BY c.discount, c.valid, c.daily, c.total, c.max_amount
You could also do, for the "filter" on email :
WHERE c.code = ? and (u.email is null or u.email = ?)
You need to use the IFNULL function.
IFNULL(expr1,expr2)
If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns
expr2.
You need to modify your query like:
SELECT pc.discount
, pc.valid
, pc.daily
, pc.total
, pc.max
, IFNULL(c.tcount, 0) AS tcount
, IFNULL(c.count, 0) as count
FROM p_code AS pc
...