Query Reports from comma separated JSON mysql field - php

this is my first question. Apologies if I don't conform with the required rules. I have tried my best and
Problem--
I have a problem with a project am working on. It is medical based. I have these three tables that I need to get reports from
DB structure--
diseaseTable which has columns
diseaseID | diseaseName
diseaseID stores an integer i.e 1, 3, 4,..
dieseaseName stores name of disease e.g Hypertension, Malaria, ..e.tc
I also have patientsTable which has these key columns
patientID - which is an autoincrement int
patientName - name of patient
created_at - date and time of registration of the patient
Lastly there is treatment table which contains
treatmentID -autoincrement int
patient - contains the id of the patient which is a foreing key of patientsTable
created_at and updated_at for timestamps
diseaseTreated (which is my column of interest) Stores values in json comma separated fields.
I have already checked all results from https://stackoverflow.com/search?q=mysql+comma+separated+fields but none seems to solve my problem. I have checked on ways to restructure mysql schema but i would have too many tables to store the diseases diagnised from each patient. In addition one patient might be diagnosed with multiple diseases and a disease can be added any time. I might have almost 100 of them. If ID of Hypertension is 1 and arthritis is 2 a patient with both will have the
diseaseTreated field with "1","2" in the treatment table
Solution Expected--
I need to get reports for first time a specific disease has been treated for the first time or second time and more i.e revisit for treatment of the same disease (if i wanted to know how many cases of hypertension are for the first time, and how many cases of hypertension are recurrent)
Example - New cases report
Disease | New Cases
Hypertension | 4
Example - Recurrent cases report
Disease | New Cases
Hypertension | 1
Arthritis | 2
Am using Laravel I have attempted a lot of solutions but this is what i find too close to get what I need: Others I have attempted was looping through different array results i have queried from the database by using joins but I havent posted them because they have yielded nothing important.
$diseaseID
$countedDiseaseCases = DB::select('select * from treatmentTable where JSON_CONTAINS(diseases, \'[\"'.$diseaseID.'\"]\')')
This one would get me the rows with a specific disease and by using php count() on the result I get an int as 3 which is OK but I haven't got to put it as I expect filter and differentiate new and recurrent cases.
Thanks,
Any suggestion will be highly appreciated

If the deseaseTreated column is json, couldn't you add an isNew key and when the patient comes in for a second visit, flip that key to false or something similar?
Now when you want to make the report you can get counts on all of them.
//example deseaseTreated value
{
"deseases": [
{
"deseaseID":"Value",
"isNew":Boolean,
...
},
{...}
]
}
$records = DB::select('select * from treatment');
$diseases = DB::select('select * from diseases');
foreach ($diseases as $disease) {
$disease['count_new'] = 0;
}
foreach ($records as $record) {
$json = json_decode($record['diseaseTreated']);
foreach ($json['diseases'] as disease_index) {
if ($disease_index['isNew']) {
foreach ($diseases as &$d) {
if ($d['diseaseID'] == $disease_index['diseaseID']) {
$d['count_new']++;
}
}
}
}

Related

SQL Join statement into a single field in a HTML Table using PHP

so i'm creating a schedule system and i'm trying to display the information for the appointment and also, userID's for those who have booked a possition.
I have created a table for Students, Classes and ClassAssosication where the ClassID and UserID are PK And FK from the other tables. I have created a join statement =
SELECT classes.ClassName, students.UserID
FROM classassociation
JOIN students
ON classassociation.UserID = Students.UserID
JOIN classes
ON classassociation.ClassID = classes.ClassID
WHERE classassociation.ClassID = 1;
Where it retrieves the UserID and ClassName for those who have booked a place for Class with the ID = 1 .
I am trying to create a PHP/HTML table where in a field for example, Monday 9AM, i can print out the join statement, for example, Methodology, 1 ,2 (user Id's).
Day | 9AM | 10AM
Monday | Methodology, 1,2 |
Tuesday | |
I need this done for multiple classes but trying to attempt one for now. I am unsure of how to do this, so any help will be appreciated. Thank you.
Your query as written will return a row for each ClassName/UserId pair - if there are two people in the methodology class there will be two rows in your query result.
You can choose to combine them in a loop in php, or you can alter your query to group things together. If you think of it as "I want to group all the UserIds for a given classname", it suggest how to use the GROUP BY clause in your SQL:
SELECT classes.ClassName, students.UserID
FROM classassociation
JOIN students
ON classassociation.UserID = Students.UserID
JOIN classes
ON classassociation.ClassID = classes.ClassID
WHERE classassociation.ClassID = 1
GROUP BY ClassName;
Now there will be one row for each ClassName, but you still need to tell MYSQL how to handle the multiple UserIds for each row. In your case, you want to have the UserIds joined together in a comma-separated list. Happily, there's a special section in the manual just for the functions you use with GROUP BY. In this case GROUP_CONCAT gives the desired result:
SELECT classes.ClassName, GROUP_CONCAT(students.UserID SEPARATOR ',') AS UersIds
FROM classassociation
JOIN students
ON classassociation.UserID = Students.UserID
JOIN classes
ON classassociation.ClassID = classes.ClassID
WHERE classassociation.ClassID = 1
GROUP BY ClassName;
Now you will get a result set with one row for each ClassName, with two columns:
ClassName | UserIds
---------------------
Methodology | 1,2
Then you can write a simple php loop to take each row in the query and generate a table row in your html.
If you look at the manual for GROUP_CONCAT, you can see that you can also set the order that the userIds are grouped in, which might be useful.
The php loop is pretty simple once you have your query results; you just need to create a table and then add a row for each result:
/*assume your mysql query has returned an array of objects named $result */
// in your html document, create your table with its header
print '<table><thead><tr><th>Class Name</th><th>UserIds</th></tr></thead>';
print '<tbody>';
// now loop through your query results and put stuff into the table
// of course you can monkey around with the table format and what goes in each cell
foreach($result as $row) {
print '<tr><td>'.$row->ClassName.'</td><td>'.$row->UserIds.'</td></tr>';
}
print '</tbody></table>';
That's a very simple example, but hopefully demystifies the process somewhat.
Your particular case looks like the query is actually to get the contents of one cell in your table; you can either do a much grander query that groups by time and day of week, or you can do little queries and store the results in nested arrays that model how you want your table to look. The html output is structurally the same, but you would have a php loop for each table cell.
To have table with each cell showing the result of a separate query, consider something like the following pseudocode:
$days = array('Monday', 'Tuesday',...'Friday');
$times = array('9', '10'...);
$weekResults = array();
foreach($days as $day) {
$weekResults[$day] = array();
foreach($times as $time) {
$weekResults[$day][$time] = doQuery($day, $time);
}
}
// now you have nested arrays with a result set for each table cell.
// Rendering the table, you just use the same loops:
renderTableHeader(); // all the <table><thead> stuff
foreach($weekResults as $day) {
print '<tr>'
foreach($day as $time) {
print '<td>';
// output the data from your query like above
// if the formatting is complex, you can even put another table inside the <td>.
print '</td>';
}
print '</tr>';
}
renderTableFooter(); // close tbody and table tags
Hope that helps!

using mysql stored-function with select query

let me explain my purpose first, i have an vehicle booking application where, visitor will add start date and end date of his journey, in the database there is list of drivers with there availability (available_from_date and available_to_date) which is kind of duration during which they are operating, there is an field for exclude_dates for some specific dates when they are not working.
the application needs to find a list of vehicles which are available during the journey dates entered by the user.
for example user enters he want to go from place A to B during 13th sept, 2014 to 17th sept, 2014
then database needs to return a list of taxi which are available during this period and must not have any exclude date within this period.
Now i have stored the exclude_dates in comma separated format in table (i could have created a separate table but then it would take much more time for a query to execute)
I was trying to create a mysql function which would be called within the actual search query and would return true if there is some there is some excluded date present within the duration and false if not.
these are the queries that i have written
SELECT id, exclude_dates
FROM `taxi_route`
WHERE status = 1
AND `to_city` = 'Surat'
AND `from_city` = 'Ahmedabad'
AND `trip_type` = 2
AND `available_from_date` <= '2014-09-13'
AND available_to_date >= '2014-09-17'
AND STR_TO_DATE((SELECT `split`(exclude_dates, ',', 1)),'%d-%m-%Y')
NOT BETWEEN STR_TO_DATE('13-09-2014','%d-%m-%Y')
AND STR_TO_DATE('17-09-2014','%d-%m-%Y')
Split is a function i have created in mysql to separate the dates present in comma format
DELIMITER $$
CREATE FUNCTION split( str VARCHAR(500), delchar VARCHAR(2), x INT )
RETURNS VARCHAR(500)
BEGIN
RETURN SUBSTR(SUBSTRING_INDEX(str, delchar, x),
LENGTH(SUBSTRING_INDEX(str, delchar, x-1))+IF(x > 1, 2, 1));
END$$
DELIMITER ;
this works fine as far as i pass 1 in split(exclude_dates, ',', 1) , but if the exclude_dates have more then one date then this will not work
can someone please suggest or guide, how this can be accomplished.
snapshot of database is here http://i.imgur.com/JaI8MSx.png
Your query is most likely going to take more time to execute than defining a separate table for exclusion dates. It's not a good practice using comma separated list inside a column for searching purposes, this is against normalization rules.
You should define your tables separately, (e.g. taxi, taxi_route, taxi_route_exclusion, route_exclusion) and later add necessary indexes to make your searches more efficient.
Example:
taxi
---------
id
country
***
***
***
taxi_route
-------------------
id
taxi_id
available_from_date
available_to_date
from_city
to_city
route_exclusion
---------------
id
taxi_id
exclusion_date
And also add a relation table between taxi_route and route_exclusion tables to represent many-to-many relationship. Later define foreign keys on taxi_route_route_exclusion table to point taxi_route and route_exclusion tables.
taxi_route_route_exclusion
--------------------------
taxi_route_id
route_eclusion_id
Define foreign keys like:
taxi_route.taxi_id -> taxi.id
taxi_route_route_exclusion.taxi_route_id -> taxi_route.id
taxi_route_route_exclusion.route_exclusion_id -> route_exclusion.id
Define indexes like:
taxi: IX1 (status, trip_type)
taxi_route: IX1(to_city, from_city, available_from_date, available_to_date)
Your final query should look like this:
SELECT tr.id, re.exclusion_date
FROM `taxi_route` tr JOIN `taxi_route_route_exclusion` trre
ON tr.id = trre.taxi_route_id
JOIN `route_exclusion` re
ON re.id = trre.route_exclusion_id
JOIN `taxi` t
ON t.id = tr.id
WHERE
t.status = 1
AND t.trip_type = 2
AND tr.to_city = 'Surat'
AND tr.from_city = 'Ahmedabad'
AND tr.available_from_date <= '2014-09-13'
AND tr.available_to_date >= '2014-09-17'

How to split row into multiple rows from the MySQL?

I have a MySQL data table, in which I have more than 2 columns. First column has a unique value clinical trial value whereas second column has disease information. There are, in most of the cases, more than 2 disease names in one cell for a single id. I want to spilt those rows which cell contains two or more than two diseases. There is a pattern for searching also, i.e. small character is immediately followed by capital character., e.g. MalariaDengueTuberculosis like this. Suppose for these three diseases there is unique id, it should show like the following:
NCT-ID disease
4534343654 Maleria
4534343654 Dengue
4534343654 Tubercoulsosis
If you want to store one or more data in one String column, you could use JSON data formatting.
It is not very clear to me what you're trying to achieve. Since you're using PHP you can try to read the tables using PDO and print the results. For example:
$sql = 'SELECT diseases FROM diseases_info_table';
foreach($pdo->query($sql) as $row) {
$diseases = preg_split('/(?=[A-Z])/', $row['diseases']);
$sql2 = 'SELECT * FROM diseases WHERE disease_name IN ("' . implode('","', $diseases) . '")';
foreach ($pdo->query($sql2) as $row) {
print $row['NCT-ID '] . "\t";
print $row['disease'] . "\t";
}
}
But this way you're generating a lot of queries. If it is possible for you to rethink the database structure than I would recommend doing that.
To answer your question:
If you insist on multi data in one column you can use php explode:
$diseases = explode("|", $row['disease']); //Changing | to whatever separates your diseases.
$diseases is now an array of your diseases which you can do:
foreach ($diseases as $disease)
{
echo $disease;
}
However
Personally I would normalise your database now before trying to hack around solutions. Use an ID against your table and then have a diseases table to link to it. ie:
Main table
NCT-ID
4534343654
5768788544
3i33i3i078
Disease Table
disease_id nct_id disease
1 4534343654 Broken Wind
2 4534343654 Chronic Nosehair
3 4534343654 Corrugated Ankles
4 5768788544 Discrete Itching
5 3i33i3i078 Gastric Ejections
6 3i33i3i078 Bloaty Head
This allows multiple diseases against one nct-id. disease_id would be the primary key.

Computing an average from different tables

I am sorry for a verlo long question, just trying to explain in details. My formatting is not very good, sorry for that as well. I had a PHP/ MySQL App that essentially was not truly relational as I had one large table for all student scores. Among other things, I was able to calculate the average score for each subject, such that the average appeared alongside a student's score. Now I have since split the table up, to have a number of tables which I am successfully querying and creating School Report Cards as before. The hardship is that I can no longer calculate the avaerages for any subject.
Since I had one table with 5 subjects and each of the subjects had 2 tests, I queried for data and calculated the average as follows:
The one table (Columns):
id date name exam_no term term year eng_mid eng_end mat_mid mat_end phy_mid phy_end bio_mid bio_end che_mid che_end
The one query:
$query = "SELECT * FROM pupils_records2
WHERE grade='$grade' && class='$class' && year = '$year' && term ='$term'";
$result = mysqli_query($dbc, $query);
if (mysqli_num_rows($result) > 0) {
$num_rows=mysqli_num_rows($result);
while($row = mysqli_fetch_array($result)){
//English
$eng_pupils1{$row['fname']} = $row['eng_mid'];
$eng_pupils2{$row['fname']} = $row['eng_end'];
$mid=(array_values($eng_pupils1));
$end=(array_values($eng_pupils2));
$add = function($a, $b) { return $a + $b;};
$eng_total = array_map($add, $mid, $end);
foreach ($eng_total as $key => $value){
if ($value==''){
unset ($eng_total[$key]);
}
}
$eng_no=count($eng_total);
$eng_ave=array_sum($eng_total)/$eng_no;
$eng_ave=round($eng_ave,1);
//Mathematics
$mat_pupils1{$row['fname']} = $row['mat_mid'];
$mat_pupils2{$row['fname']} = $row['mat_end'];
$mid=(array_values($mat_pupils1));
$end=(array_values($mat_pupils2));
$add = function($a, $b) { return $a + $b;};
$mat_total = array_map($add, $mid, $end);
foreach ($mat_total as $key => $value){
if ($value==''){
unset ($mat_total[$key]);
}
}
print_r($mat_total);
$mat_no=count($mat_total);
echo '<br />';
print_r($mat_no);
$mat_ave=array_sum($mat_total)/$mat_no;
$mat_ave=round($mat_ave,1);
}
}
//Biology
etc
I split the table into separate tables and have names in a separate table, not needed for calculating avaerages, so I will not show it here. Each subject table tajkes the following form:
id date exam_no term year grade class test*
*Test would be eng_mid or eng_end or mat_mid etc.
Because I had only one query which returned 10 rows (5 subjects each with two tests: e.g. eng_mid (English Mit exam), eng_end (english end of term test), I was able to capture all rows in one call and pack each subject into an array, and then work out the class average, with the help of array_map. It may not be elegant, but it worked very well. Now, I have each test in it's own table.
I was trying to write a joint so as to get a signle resultset but the query fails. The columns as like:
I know that the database design is not anything to be proud off, but coming from a huge single table, this is a massive step (worthy a pat on the shoulder).
What I wish to do is to be able to query all my data and calculate class averages (about 30 students in each class). I tried to use separate queries but I ran into a wall, in that previously I would use the WHILE conditional as shown after the query for it to pull all rows and create an array from which I could get desired results. Now several queries just makes me confused as to how I can archieve the same results since a join is not working. Also I am having a separate $row variable, and that throws me further off balance!
Is it even possible to do averages as I did on my infamous one table (from the dark side) or is my table design so messed up, what I want just isn't humanly possible?
Please any help will be deeply appreciated.
Try using union. It would be something like
select grade, test from math
union all
select grade, test from english
union all
....
Also, in my opinion, better design would be to have table exams something like that (warning, pseudo-DML):
id int primary key,
student_id int foreign key students
subject_id int foreign key subjects
exam_type_id int foreign key exam_types
grade int(????)
exam_types table would be just midterm and final, but you'll be able to easily support more types in future, if required.
subjects table will store all kinds of subjects you have (at this time there will be only five of them: math, eng, phy, etc.
The averaging query would be as simple as (yes, you can actually do aggregation in the query itself)
select student_id, avg(grade)
from exams
group by student_id

What's the most efficient way to pull the data from mysql so that it is formatted as follows:

I have a table that has patient information (name, dob, ssn, etc.) and a table that has lists of medications that they take. (aspirin, claritin, etc.) The tables are related by a unique id from the patient table. So, it's easy enough to pull all of Mary Smith's medications.
But, what I need to do is to show a paginated list of patients that shows their name, other stuff from the patient table and has a column with a line-separated list of their medications. Roughly, this:
If I do a simple left join, I get 3 repeated rows of Mary Smith with one medication per row.
The patient table can have thousands of records, so I don't want to do a query to get all the patients and then loop through and get their meds. And, because it's paginated based on patient, I can't figure out how to get the correct number of patients for the page, along with all their medications.
(The patients/medications thing is just a rough example of the data; so please don't suggest restructuring how the data is stored.)
GROUP_CONCAT to the rescue!
SELECT patients.first_name, patients.last_name, GROUP_CONCAT(prescriptions.medication SEPARATOR ", ") AS meds FROM patients LEFT JOIN prescriptions ON prescriptions.patient_id = patients.id GROUP BY patients.id;
You've got a few choices.
rowspan clauses with one drug per cell per user. You'd need to run two SQL queries to precalculate how big each user's span would have to be, or suck the query results into PHP and do the counting there.
Simple state machine - start a new row each time the user changes, then just keep adding more drug names seperated with <br /> while the user's name stays constant.
The second one's probably easiest:
$previous_name = null;
$first = true;
echo "<table";
while($row = mysql_fetch_assoc($results)) {
if ($row['name'] <> $previous_name) {
if (!$first) {
echo "</td></tr>"; // end previous row, if it's not the first row we've output
$first = false;
}
echo "<tr><td>$row[name]</td><td>"
$previous_name = $row['name'];
}
echo "$row['drug']<br />";
}
echo "</td></tr></table>";
I think what you are looking for is referred to a 'concation of subquery'.
Check http://forums.mysql.com/read.php?20,157425,157796#msg-157796 and http://mysql.bigresource.com/SELECT-CONCAT-Subquery-S5cIpzqO.html

Categories