mysql joining the same table - mysql

Given a table named RECORD in mysql with following structure:
rid(pk & AI) patientid(fk) recordTYPE(varchar) recordValue(varchar) recordTimestamp(timestamp)
1 1 temperature(℃) 37.2 2015-08-11 18:10:04
2 1 weight(kg) 65.0 2015-08-11 18:20:08
3 1 heartbeat(bpm) 66 2015-08-11 18:30:08
4 1 temperature(℃) 36.8 2015-08-11 18:32:08
You can see that for the same date, there can be multiple records for one particular type of record. e.g. temperature in the sample data :
rid patientid recordTYPE value recordtimestamp
1 1 temperature(℃) 37.2 2015-08-11 18:10:04
4 1 temperature(℃) 36.8 2015-08-11 18:32:08
In this case, we should choose the latest record. i.e. the record with rid = 4 and value = 36.8 .
Now given an input date e.g. '2015-8-11', I want to do a query to obtain something like:
date patientid temperature(℃) weight(kg) heartbeat(bpm)
2015-08-11 1 36.8 65.0 66
2015-08-11 2 36.5 80.3 70
2015-08-11 3 35.5 90.5 80
..........................................................
..........................................................
2015-08-11 4 35.5 null null
Fig. 2
In addition, you can see that for a particular date, there may not be any records of some types. In this case, the value in that column is null.
I tried the following query:
SELECT max(recordTimestamp), patientid, recordTYPE, recordValue
FROM RECORD
WHERE date(recordTimestamp) = '2015-08-11'
GROUP BY patientid, recordTYPE
The result is something like:
date patientid recordTYPE recordValue
2015-08-11 1 temperature(℃) 36.8
2015-08-11 1 weight(kg) 65.0
2015-08-11 1 heartbeat(bpm) 66
2015-08-11 2 temperature(℃) 36.5
2015-08-11 2 weight(kg) 80.3
2015-08-11 2 heartbeat(bpm) 70
2015-08-11 4 temperature(℃) 35.5
Fig. 4
The questions are:
Given this table RECORD, what is the proper mysql statement (in terms
of performance such as retrieval speed) to produce the desired result set (i.e. Fig.2)?
Will it be better (in terms of facilitating query and scalability such as adding new types of record) if the db design is changed?
e.g. Create one table for each type of record instead of putting all types of record in one table.
Any suggestion is appreciated as I'm a db novice...... Thank you.

You can try this:-
SELECT MAX(rid), patientid, recordTYPE, MAX(recordValue), recordTimestamp
FROM YOUR_TABLE
WHERE recordTimestamp = '2015/08/11'
GROUP BY patientid, recordTYPE, recordTimestamp;

Here's one way to do it. SQL Fiddle Demo
Sadly MySQL doesn't support the row_number() over (partition by ...) syntax which would have simplified this a lot.
Instead I've made excessive use of a trick discussed here: https://stackoverflow.com/a/3470355/361842
select `date`
, `patientId`
, max(case when `tRank`=1 then `temperature(℃)` else null end) `temperature(℃)`
, max(case when `wRank`=1 then `weight(kg)` else null end) `weight(kg)`
, max(case when `hRank`=1 then `heartbeat(bpm)` else null end) `heartbeat(bpm)`
from
(
select case when #p = `patientId` and #d = cast(`recordTimestamp` as date) then #x := 1 else #x := 0 end
, case when #x = 0 then #t := 0 end
, case when #x = 0 then #w := 0 end
, case when #x = 0 then #h := 0 end
, case `recordType` when 'temperature(℃)' then case #x when 1 then #t := #t + 1 else #t := 1 end else null end as `tRank`
, case `recordType` when 'weight(kg)' then case #x when 1 then #w := #w + 1 else #t := 1 end else null end as `wRank`
, case `recordType` when 'heartbeat(bpm)' then case #x when 1 then #h := #h + 1 else #t := 1 end else null end as `hRank`
, case `recordType` when 'temperature(℃)' then `recordValue` else null end as `temperature(℃)`
, case `recordType` when 'weight(kg)' then `recordValue` else null end as `weight(kg)`
, case `recordType` when 'heartbeat(bpm)' then `recordValue` else null end as `heartbeat(bpm)`
, #d := cast(`recordTimestamp` as date) as `date`
, #p := `patientId` as `patientId`
from `Record`
cross join
(
SELECT #t := 0
, #w := 0
, #h := 0
, #p := 0
, #x := 0
, #d := cast(null as date)
) x
order by `patientId`, `recordTimestamp` desc
) y
group by `date`, `patientId`
order by `date`, `patientId`
Breakdown
This says that if this is the last temperature of the day for the current grouping's partientId/date combo then return it; otherwise return null. It then takes the max of the matching values (which given all but 1 are null, gives us the one we're after).
, max(case when `tRank`=1 then `temperature(℃)` else null end)
How tRank = 1 means the last temperature of the day for a patientId/date combo is explained later.
This line says that if this record has the same patientId and date as the previous record then set x to 1; if it's a new combo set it to 0.
select case when #p = `patientId` and #d = cast(`recordTimestamp` as date) then #x := 1 else #x := 0 end
The next lines say that if we have a new patiendIt/date combo, reset the t, w and h markers to say "the next value you receive will be the one we're after".
, case when #x = 0 then #t := 0 end
The next lines split the data by recordType; returning null if this record isn't their record type, or returning a number saying what how many of this type of record we've now seen for the patientId/date combo.
, case `recordType` when 'temperature(℃)' then case #x when 1 then #t := #t + 1 else #t := 1 end else null end as `tRank`
This is similar to the above; except instead of returning a combo-counter it returns the value of the current record (or null if this is a different record type).
, case `recordType` when 'temperature(℃)' then `recordValue` else null end as `temperature(℃)`
We then record the current record's date and patientId values, so we can compare them with the next record on the next iteration.
, #d := cast(`recordTimestamp` as date) as `date`
, #p := `patientId` as `patientId`
The cross join and following subquery is just used to initialise our variables.
The (first) order by is used to ensure that comparing current and previous records is enough to tell if we're looking ata different combo (i.e. if all combos are grouped then any change is easy to spot; if the combos keep alternating we'd need to keep track of every combo we'd seen before).
recordTimestamp is sorted descending so that the first record we see on a new combo will be the last record that day; the one we're after.
The group by is used to ensure we get 1 result per combo; and the last order by just to make our output ordered.

Related

Return 1 or 0 in SQL depending on the multiple statements

If I find that some of the user exists with such a parameters, I want to get 1 otherwise 0. In the future I'll have to add more blocks. But it doesn't seem to work now. What am I doing wrong?
SELECT CAST(CASE WHEN EXISTS(SELECT 1
FROM Customers
WHERE Country = 'France' AND PostalCode%2 = 0)
OR (WHERE Country = 'Germany' AND PostalCode%2 = 0))
)
THEN 1
ELSE 0
END AS BIT)
You need two separate exists:
SELECT CAST(CASE WHEN EXISTS (SELECT 1
FROM Customers
WHERE Country = 'France' AND PostalCode%2 = 0
)
THEN 1
WHEN EXISTS (SELECT 1
FROM Customers
WHERE Country = 'Germany' AND PostalCode%2 = 0
)
THEN 1
ELSE 0
END AS BIT)
Actually, I broke this into two separate THEN clauses. This is almost equivalent to using OR, but because the logic is inside a CASE, THEN seems more natural. (The difference is that the optimizer could choose to re-arrange the OR conditions, but the THEN conditions are executed in lexical order.)
If your statements are actually this simple, you can combine them as:
SELECT CAST(CASE WHEN EXISTS (SELECT 1
FROM Customers
WHERE Country IN ('France', 'Germany') AND PostalCode%2 = 0
)
THEN 1
ELSE 0
END AS BIT)
It looks to me like you're just having issues with your bracketing:
SELECT CAST(
CASE WHEN EXISTS(
SELECT 1
FROM Customers
WHERE (Country = 'France' AND PostalCode%2 = 0)
OR (Country = 'Germany' AND PostalCode%2 = 0)
) THEN 1 ELSE 0 END AS BIT)
Building on Gordon's assumption that PostalCode%2 = 0 for all tested 'sets' of conditionals (you haven't said as much yet), you could likewise shorten this to:
SELECT CAST(
CASE WHEN EXISTS(
SELECT 1
FROM Customers
WHERE PostalCode%2 = 0
AND Country IN ('France', 'Germany')
) THEN 1 ELSE 0 END AS BIT)

How to Select same column in 1 table twice in mysql

How can i select 1 column twice using only 1 table, the problem is i have to separate 1 column into 2 return
ex: table Logs
TIMELOGS 11:00 , 12:12 , 13:00 , 17:01 , 17:05 , 17:10
TIMEMODE 0 , 0 , 0 , 1 , 1 , 1
the output should be
IN 11:00 , 12:12 , 13:00
OUT 17:01, 17:05 , 17:10
How can I combine these to queries
Select TIMELOGS as IN FROM table_logs where TIMEMODE = 0;
and
Select TIMELOGS as OUT FROM table_logs where TIMEMODE = 1;
If I understand correctly, you want to enumerate the 0s and the 1s and then group by that:
select max(case when mode = 0 then time end),
max(case when mode = 1 then time end)
from (select l.*,
(#rn := if(#m = mode, #rn + 1,
if(#m := mode, 1, 1)
)
) as rn
from logs l cross join
(select #rn := 0, #m := -1) params
order by mode, time
) l
group by rn;
To output the format you described, (suppose you're using MySQL) you could do:
select
case when TIMEMODE = 0 then 'IN' else 'OUT' end,
group_concat(TIMELOGS)
from table_logs
group by TIMEMODE

SQL Server Row totals in pivot query

I am trying to make a row in the end of the result set that shows the totals.
My query is this:
SELECT
[ ] = ISNULL(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente, 'TOTAL'),
[2016-11-01] = MAX([2016-11-01]),
[2016-11-02] = MAX([2016-11-02]),
[2016-11-03] = MAX([2016-11-03]),
[2016-11-04] = MAX([2016-11-04]),
TOTAL = COUNT([2016-11-01]) + COUNT([2016-11-02]) + COUNT([2016-11-03]) + COUNT([2016-11-04])
FROM
(
SELECT GEN_Paciente.GEN_idPaciente,COALESCE(GEN_ape_paternoPaciente, '')+' '+COALESCE(GEN_ape_maternoPaciente, '')+' '+COALESCE(GEN_nombrePaciente, '') AS nombrePaciente,HOS_fechaCategorizacion,HOS_nivel_riesgoCategorizacion+CAST(HOS_nivel_dependenciaCategorizacion AS VARCHAR) as riesgoDependencia
FROM HOS_Categorizacion
INNER JOIN HOS_Hospitalizacion
ON HOS_Hospitalizacion.HOS_idHospitalizacion = HOS_Categorizacion.HOS_idHospitalizacion
INNER JOIN GEN_Paciente
ON GEN_Paciente.GEN_idPaciente = HOS_Hospitalizacion.GEN_idPaciente
WHERE HOS_nivel_riesgoCategorizacion IS NOT NULL
) src
PIVOT
(
MAX(riesgoDependencia)
for HOS_fechaCategorizacion in ([2016-11-01],[2016-11-02],[2016-11-03],[2016-11-04])
) p
GROUP BY
ROLLUP(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente)
This gives me this result:
But as you can see the totals for the rows are right but the totals for the columns are wrong because I am using MAX instead of COUNT, but I only need COUNT in the TOTAL row, the others have to be MAX, so I wrote this query:
SELECT
[ ] = ISNULL(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente, 'TOTAL'),
[2016-11-01] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-01]) ELSE COUNT([2016-11-01]) END,
[2016-11-02] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-02]) ELSE COUNT([2016-11-02]) END,
[2016-11-03] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-03]) ELSE COUNT([2016-11-03]) END,
[2016-11-04] = CASE WHEN CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente IS NOT NULL THEN MAX([2016-11-04]) ELSE COUNT([2016-11-04]) END,
TOTAL = COUNT([2016-11-01]) + COUNT([2016-11-02]) + COUNT([2016-11-03]) + COUNT([2016-11-04])
FROM
(
SELECT GEN_Paciente.GEN_idPaciente,COALESCE(GEN_ape_paternoPaciente, '')+' '+COALESCE(GEN_ape_maternoPaciente, '')+' '+COALESCE(GEN_nombrePaciente, '') AS nombrePaciente,HOS_fechaCategorizacion,HOS_nivel_riesgoCategorizacion+CAST(HOS_nivel_dependenciaCategorizacion AS VARCHAR) as riesgoDependencia
FROM HOS_Categorizacion
INNER JOIN HOS_Hospitalizacion
ON HOS_Hospitalizacion.HOS_idHospitalizacion = HOS_Categorizacion.HOS_idHospitalizacion
INNER JOIN GEN_Paciente
ON GEN_Paciente.GEN_idPaciente = HOS_Hospitalizacion.GEN_idPaciente
WHERE HOS_nivel_riesgoCategorizacion IS NOT NULL
) src
PIVOT
(
MAX(riesgoDependencia)
for HOS_fechaCategorizacion in ([2016-11-01],[2016-11-02],[2016-11-03],[2016-11-04])
) p
GROUP BY
ROLLUP(CAST(GEN_idPaciente AS VARCHAR)+'-'+nombrePaciente)
But that is not working
Thanks for your help!!
If I understand this correctly you want to count all columns which are not null. In this case you should just look at the condition IS NULL and not at the actual value at all. Try this:
DECLARE #tbl TABLE(ID INT IDENTITY, val1 VARCHAR(100),val2 VARCHAR(100),val3 VARCHAR(100));
INSERT INTO #tbl VALUES
('row1_val1','row1_val2',NULL)
,('row2_val1','row2_val2','row2_val3')
,(NULL,'row2_val2',NULL)
,(NULL,NULL,'row2_val3')
,(NULL,NULL,NULL);
SELECT *
,CASE WHEN val1 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val2 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val3 IS NULL THEN 0 ELSE 1 END AS CountOfValNotNull
FROM #tbl
UPDATE: Add a final Totals Row
You'd need ugly fiddling with a CTE, an additional sort column, UNION ALL to add another row and a sub_select.
Use the outer-most ORDER BY to get the artificial Totals-Row to the end
hint: Use the #tbl variable from above!
WITH SortedRows AS
(
SELECT ROW_NUMBER() OVER(ORDER BY ID) AS SortColumn
,*
,CASE WHEN val1 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val2 IS NULL THEN 0 ELSE 1 END
+CASE WHEN val3 IS NULL THEN 0 ELSE 1 END AS CountOfValNotNull
FROM #tbl
)
SELECT tbl1.*
FROM
(
SELECT * FROM SortedRows
UNION ALL
SELECT 999999,0,'','','',(SELECT SUM(CountOfValNotNull) FROM SortedRows)
) AS tbl1
ORDER BY tbl1.SortColumn

Get Column Name from table where value is zero

I have a query which returns this data back from lets say DefaultersTable
Select CustomerID, RoleID FROM DefaultersTable Where DefaulterValue = 1
CustomerID, RoleID
10034 34
15481 37
Now I have got another Table "DefaultersDetails" which have individual monthly values of them,
so I do
Select * from DefaultersDetails Where CustomerID = 10034 AND RoleID = 34.
and get the data
CustomerID, RoleID, ValueForJan, ValueForFeb, ValueforMar
10034 34 45 0 32
Please note that I got this Entry in the first case only because one of the Value was 0.
Now How Can I get both the data in single Query, I want something like this
CustomerID, RoleID, ZeroValueForMonth
10034 34 ValueForFeb
15481 37 ValueForJan
I guess it can be done via temprory Tables but I am not sure how to do this
You can use COALESCE function. Try this :
SELECT d.CustomerID, d.RoleID,
COALESCE(detail.JAN,detail.FEB,detail.MAR) AS ZeroValueForMonth
FROM DefaultersTable d
INNER JOIN
(
SELECT CustomerID, RoleID,
(CASE WHEN ValueForJan <> 0 THEN NULL ELSE 'ValueForJan' END) JAN,
(CASE WHEN ValueForFeb <> 0 THEN NULL ELSE 'ValueForFeb' END) FEB,
(CASE WHEN ValueForMar <> 0 THEN NULL ELSE 'ValueForMar' END) MAR
FROM Defaultersdetail
) AS detail
ON detail.CustomerID = d.CustomerID AND detail.RoleID = d.RoleId
where d.DefaulterValue = 1
You can add other month like MAY, JUN etc on subquery like this :
(CASE WHEN ValueForApr <> 0 THEN NULL ELSE 'ValueForApr' END) APR

case within case using mysql

I have 3 columns doc_date, chem_date, st_date
all 3 columns belong to different tables:
doc
doc_date count(x)
01-02-2012 2
02-02-2012 3
chem
chem_date count(x)
04-02-2012 1
06-02-2012 0
stock
st_date count(x)
01-02-2012 1
03-02-2012 5
I want to write select clause like this
case doc_date
when '01' then count(x),
case chem_date
when '01' then count(x),
case st_date
when '01' then count(x)
end as '01',
case doc_date
when '02' then count(x),
case chem_date
when '02' then count(x),
case st_date
when '02' then count(x)
end as '02',
....up to 31 days
If some case statements have an entry on same date e.g if doc_date and st_date both exist on '01-02-2012' then their respective count should be added to the final count(x) means count(x) + count(x)
So, the answer should be:
01 02 03 04 05 06 07 08 09 10 11 12.. up to 31 days
3 3 5 1 0 count(x) value for particular date
Explanation of the output: Here, the starting value for date 01 is 2 for doc table and for stock table the value is 1. So, the final value will become addition of them which is 3
The same rule will be applicable for others.
Any suggestions how can I achieve this output in SQL? Or any other way?
Here's a pseudo code showing how it can be done:
select
case when date_format(doc_date,"%d") = '01' then sum_x else null end as '01',
case when date_format(doc_date,"%d") = '02' then sum_x else null end as '02',
--and etc
from (
select doc_date, sum(doc_x) as sum_x --sum the x value
from
(
select doc_date,doc_x --combine all 3 table stuff
from doc a
union all
select chem_date,chem_x
from chem a
union all
select st_date,st_x
from st a
) h
group by doc_date --use first col name as the union's col name
) g
limit 1,1
If the doc, chem and st are combined using joins and have different schema, you may need to do pivot, either using multiple join to itself or use case... when...:
select
case when date_format(doc_date,"%d") = '01' then sum_x else null end as '01',
case when date_format(doc_date,"%d") = '02' then sum_x else null end as '02',
--and etc
from (
select doc_date,
a_x + b_x + c_x as sum_x --sum them according to date
from (select ... from doc where ...) a
left join (select ... from chem where ...) b on a.doc_date=b.doc_date and --some join condition
left join (select ... from st where ...) c on a.doc_date=c.doc_date and --some join condition
) g
limit 1,1
I am working with an ambiguous question here, so clarify as needed,