Reputation: 9464
Let's say I have a parent and child database, and the child keeps a sort of running transcript of things that happen to the parent:
create table patient (
fullname text not null,
admission_number integer primary key
);
create table history (
note text not null,
doctor text not null,
admission_number integer references patient (admission_number)
);
(Just an example, I'm not doing a medical application).
history
is going to have many records for the same admission_number
:
admission_number doctor note
------------------------------------
3456 Johnson Took blood pressure
7828 Johnson EKG 120, temp 99.2
3456 Nichols Drew blood
9001 Damien Discharged patient
7828 Damien Discharged patient with Rx
So, my question is, how would I build a query that let me do and/or/not searches of the note field for patient
records, like, for example, if I wanted to find every patient whose history contained "blood pressure" and "discharged".
Right now I'm been doing a select
on history
that groups by admission_number
, combining all the notes with a group_concat(note)
and doing my search in the having
, thus:
select * from history
group by admission_number
having group_concat(note) like '%blood pressure%'
and group_concat(note) like '%discharged';
This works, but it makes certain elaborations very complicated -- for example, I'd like to be able to ask things like "every patient whose history contains "blood pressure" and whose history with Dr. Damien says "discharged," and building qualifications like this on top of my basic query is very messy.
Is there any better way of phrasing my basic query?
Upvotes: 2
Views: 385
Reputation: 180210
This is similar to your EXISTS
method, but computes the subqueries differently.
This might or might not be faster, depending on how your tables and indexes are organized, and on the queries' selectivity.
SELECT *
FROM patient
WHERE admission_number IN (SELECT admission_number
FROM history
WHERE note LIKE '%blood pressure%')
AND admission_number IN (SELECT admission_number
FROM history
WHERE note LIKE '%discharged%'
AND doctor = 'Damien')
Alternatively, you could use a compound subquery (computing the intersection once is likely to be faster than executing IN
twice for every record):
SELECT *
FROM patient
WHERE admission_number IN (SELECT admission_number
FROM history
WHERE note LIKE '%blood pressure%'
INTERSECT
SELECT admission_number
FROM history
WHERE note LIKE '%discharged%'
AND doctor = 'Damien')
Upvotes: 1
Reputation: 9464
I have something -- using EXISTS
to construct these is a bit cleaner:
select * from patients where
exists (
select 1 from history where
history.admission_number == patients.admission_number
AND
history.note LIKE '%blood pressure%'
)
AND
exists (
select 1 from history where
history.admission_number == patients.admission_number
AND
history.note LIKE '%discharged%'
AND
history.doctor == 'Damien'
);
That's much better, now I can construct really fine-grained predicates.
Upvotes: 1
Reputation: 3112
Why don't you use a JOIN
operation?
e.g. considering, the patient table contains the following data:
INSERT INTO patient VALUES('Bob', 3456);
INSERT INTO patient VALUES('Mary', 7828);
INSERT INTO patient VALUES('Lucy', 9001);
Running the query:
SELECT DISTINCT p.fullname, p.admission_number FROM patient p
INNER JOIN history h ON p.admission_number = h.admission_number
WHERE note LIKE '%blood pressure%' OR note LIKE '%Discharged%';
gets you:
fullname = Bob
admission_number = 3456
fullname = Lucy
admission_number = 9001
fullname = Mary
admission_number = 7828
And running the following query:
SELECT DISTINCT p.fullname, p.admission_number FROM patient p
INNER JOIN history h ON p.admission_number = h.admission_number
WHERE note LIKE '%blood pressure%';
gets you:
fullname = Bob
admission_number = 3456
Upvotes: 1