Advanced query with joined tables and multiple values from GET parameter - php

First of all I'm going to explain what I want to happen in the end so if you can't help me you don't waste your time reading all of this :)
I'm creating an API in which it outputs possible illnesses that a user could have, based on the symptoms they submitted.
I want the user to input something, this goes into the url as "symptom=...
like: .../nameofmyapi/data?symptoms=headache+dizzyness. I retrieve the data from the GET parameter, but I have absolutely no idea on how to output the name of the illness.
My database looks like this:
illness
illnessId (int(11), AUTO_INCREMENT)
illnessName(varchar(255))
symptom
symptomId (int(11), AUTO_INCREMENT)
symptomName (varchar(255))
illness_symptom
illnessId (from the illness table)
symptomId (from the symptom table)
This database design makes sure you dont get multiple values in 1 row and don't have the same values BUT like I said, I don't know how to "search" or "filter" illnesses based on values from the other table. Let alone having multiple symptoms to check on... seems impossible!
I've got this query though, so I can retrieve everything when the user did not input anything:
"SELECT illness.illnessId, illness.illnessName, symptom.symptomName FROM illness_symptom
JOIN illness
ON illness.illnessId = illness_symptom.illnessId
JOIN symptom
ON symptom.symptomId = illness_symptom.symptomId";
Which gives me this:
image
Like I said, I only want to output "Whiplash" when the url looks like this:/nameofmyapi/data?symptoms=dizzyness+headache
Help would be appreciated!

First off all: To only output 'Whiplash' is not going to work because your other illness is also connected to the symptom 'dizzyness'.
I've created a database with your structure (only renamed the fields to have more default convention. Would also look at your table names to pluralfy them) and came up with this query:
SELECT i.id, i.name
FROM illness_symptom AS ils
JOIN illness AS i ON i.id = ils.illness_id
JOIN symptom AS s ON s.id = ils.symptom_id
WHERE s.name IN ('dizzyness', 'headache')
GROUP BY i.name
The IN part searches through your table and filters out all the other results with different symptoms. You could convert the result from your GET query to a comma separated list used in the query.
The GROUP BY part makes sure you won't get any double results. Off course you need to make sure a result only exists in your DB once (no double records with different case like 'whiplaSh').
Hope this helps!

Related

MYSQL Database Design/SELECT Statement assistance

I'm hoping someone may be able to provide some advice regarding a database schema I have created and a SELECT statement that I am using to query the database.
I am attempting to create a database of very old newspaper articles from the 1800's, storing such things as the date, title and full text of the article, an image of the article, the name of the newspaper the article came from, names of locations mentioned in the article and individuals mentioned within the article.
Basically below is the current structure I've created with tbArticle being the main table focus ("test" is the name of the database). I've normalised the name of the newspaper, image info, location info and individuals into their own tables and because it is assumed there will be many articles to many individuals, I've added a link table (lktbArticleIndividuals) of sorts between tbArticle & tbIndividual;
The reason for creating the database is to obviously make a focused set of newspaper articles searchable and store them in a logical format.
My issue or question is this ...
All I want to do is display a list of all the articles in the database, obviously including data from the other tables other than tbArticle and to do this I am using this SELECT query;
SELECT *
FROM tbArticle a
, tbLocation l
, tbNewspapers n
, tbIndividual i
, lktbArticleIndividuals ai
, tbImage m
WHERE a.idLocation = l.idLocation
and a.idNewspaper = n.idNewspaper
and a.idArticle = ai.idArticle
and ai.idIndividual = i.idIndividual
and a.idImage = m.idImage;
Which does what I want ... except ... if more than one individual is listed as being in an article, then two (or more) instances of the whole article are returned with the only difference being the different individual's names being displayed.
If possible, I want to just list each article ONCE, but iterate through the two or more individuals to include them. Can this be done?
If I were to query the database in say PHP I suspect what I might have to do is some sort of loop within a loop to achieve the results I want, but this doesn't seem very efficient to me!!
Does any of this make sense to anyone?!
Instead of SELECT *, you could name the columns you're interested in, and for things such as individuals, use GROUP_CONCAT() to add them all into one field, and at the end of your query, use GROUP BY a.idArticle to limit each article to one row per article.
Assuming you just want the first_name of each individual you could use a group by with a GROUP_CONCAT.
SELECT *,
GROUP_CONCAT(i.firstname)
FROM tbArticle a
, tbLocation l
, tbNewspapers n
, tbIndividual i
, lktbArticleIndividuals ai
, tbImage m
WHERE a.idLocation = l.idLocation
and a.idNewspaper = n.idNewspaper
and a.idArticle = ai.idArticle
and ai.idIndividual = i.idIndividual
and a.idImage = m.idImage;
GROUP BY a.idArticle
However, if you want to get many details of each individual I would encourage you to do two separate queries: one for the articles and another one to get the individuals of each article.

Find the underlying field from a query/view field

Why?
I am trying to dynamically find where foreign keys points. For this I search in information_schema.KEY_COLUMN_USAGE. It works fine for tables, but not for views.
Views are referenced in information_schema.VIEWS, the view_definition field exposes the query.
I think that this is the only place I will find information about where view fields comes from, right?
Then, I would search for my field name between the SELECT and the FROM. If it is an alias, get the table field name and the table name (and resolve the table if it is an alias).
Last complication, the view can refer to another view, then the code will have to be recursive.
Let's take an example (view name is vw_mandates_articles):
select ma.*, a.id_articles_unit, a.id_articles_category from mandates_articles ma
left join articles a on ma.id_article = a.id
The way it is stored in the VIEWS table is:
select `ma`.`id` AS `id`,
`ma`.`id_mandate` AS `id_mandate`,
`ma`.`id_article` AS `id_article`,
`ma`.`unit_price` AS `unit_price`,
`ma`.`description` AS `description`,
`a`.`id_articles_unit` AS `id_articles_unit`,
`a`.`id_articles_category` AS `id_articles_category`
from (`ste`.`mandates_articles` `ma`
left join `ste`.`articles` `a` on((`ma`.`id_article` = `a`.`id`)))
my inputs are:
the view name (vw_mandates_articles)
the field name (id_articles_category)
the expected output:
the field table (ste.articles)
the field name (id_articles_category) //could be same as input but not necessarily
I am not asking someone to write it for me, I just want to validate the approach before digging.
Any thoughts? Good/bad approach, alternatives?
Thanks in advance for your lights
Yes. Views only have fields stored in the query in the information_schema.VIEWS table.
No there's no better way than exploding etc. in the query...
I wouldn't recommend to make recursive views. What's sure is that it'll be slow (mysql will have to store the temporary result(s) on the hard disk what's really not improving performance).
Even if it isn't best practice, I'd tend to increase redundancy and get the data by using one single query (with maximal 1 subselect).

MySQL Query to determine Value based on 3 Variables

I need to be able to pinpoint a value in a MySQL table which is defined by two variables.
On the frontend of the site, there is a form which accepts a variety of fields. For this example let’s focus on these two:
Account Number
Account Name
I have developed a script which will use an ajax script to check the “Account Number” once entered and if it finds a match will display the “Account Name” when the user tabs out of the field.
The difficulty is to find a single result from the format of the database tables. For example:
”SELECT * FROM example_table WHERE name=’$accountnumber’”
Provides a list of all the values that equal the account number, but does not provide any record of the account name.
”SELECT * FROM example_table WHERE name=’$accountname’”
Provides a list of all the values that equal the account names, but does not provide any record of the account number.
The $record value is the only common thread between $accountnumber and $accountname.
So all in all, I need assistance creating the loop which can first take the $accountnumber value to find the $record value associated with that number. Secondly it will take the determined $record value and match it to the $accountname value. There is only one $accountnumber and $accountname value per unique $record value.
UPDATED: There have been several good comments on this question. To help provide more background, there is only one table. The best discriminator available seems to be the title value. Here is a link to the table snippet to view in greater detail:
https://docs.google.com/file/d/0By2lFlhEzILjbE1uT1hkVURmczA/edit?usp=sharing
So ultimately in this sample, a user would type 246802 and the result that is filtered out would be Fred’s Account.
Sounds like these are in the same table? Is there any discriminator to tell you whether name holds an accountnumber or accountname?
In any even, with the following assumptions you could try an ugly self join:
There are only two records with the record ID you want
these are multiple columns in the same table holding different information in the ambiguous column names
there is no better way to discriminate the record type
If so, something like this self-join should get you started:
SELECT t2.name as accountnumber from example_table as t1
INNER JOIN example_table as t2 on t1.recordID=t2.recordID
WHERE t1.name='$accountname'
EDIT Note - if my assumptions are correct and if this is data you are inheriting, I feel for you and you should look to improve it's structure. If you are designing it like this, you may want to think about it some more first.
EDIT 2
You probably want to put an index on the name column (this is the discriminator I would used based on your example).
Your query can be something like this:
SELECT t1.value as accountnumber,t2.value as accountName from example_table as t1
INNER JOIN example_table as t2 on t1.record=t2.record
WHERE t1.name='accountNumber' and t2.name='accountName'
See this SQL Fiddle: http://www.sqlfiddle.com/#!2/97c2f/1

Problem with getting specific data based on several factors in MySQL

I have a system where a user is part of a series of "runs", to each "run", can be added courses, teachers(users), classes and so on.
Each teacher(user) has chosen his/her classes & courses.
Here's a run-down of the tables I have that are relevant:
lam_run - The run in it self.
lam_run_course - Relational table that shows what runs has what courses
lam_teacher_course - Relational table that shows which teacher has which courses
lam_run_teacher - Relational table that shows what teachers are in what courses
What I want to do is show each teacher which runs that are relevant to them (based on which courses they have selected seen in lam_teacher_course) but in which they are not already participating.
Here's the MySQL code I have so far that does not work:
$query_relevant_runs = "
SELECT DISTINCT
lam_run_course.run_id
FROM
lam_teacher_course,
lam_run_course,
lam_run, lam_run_teacher
WHERE
lam_teacher_course.user_id = '1'
AND
lam_teacher_course.course_id = lam_run_course.course_id
AND
lam_run_teacher.user_id != '1'";
Instead this code shows all runs that are relevant, but it doesn't exclude the runs the user is already in..
What can I do to fix this?
Ps. Sorry for bad title, no idea what I should've called it :S
Here is a link to part of the databases (the relevant part): Link!
I think what you're looking for is:
LEFT JOIN `lam_run_teacher` `lam_run_teach_exclude`
ON `lam_run_teacher_exclude`.`user_id` = `lam_teacher_user`.`user_id`
...
WHERE `lam_run_teacher`.`user_id` IS NULL
The LEFT JOIN takes your current query, and appends the additional data to it. However, unlike the INNER JOIN you are using now (using the kinda-strange multiple-from syntax), the LEFT JOIN does not limit your resultset to just those where there is data for the righthand side. The righthand columns will be NULL. By filtering on that NULL, you can find all runs that are interesting, and for which there is not yet a relation to the teacher.
Does this help?
I'd recommend always using the normal join syntax (INNER JOIN target on target.id = source.id) - that way you're more aware of the idea that there are other kinds of join as well, and all your joins will look identical. It takes some getting used to, but definitely helps when your queries get more complex.
Also, in your cross-referencing tables, you can drop the primary key columns. If the only purpose of a table is to define a link between two tables, make the primary key consist of the two keys you've got. Unless you want to be able to related the same teacher to a run multiple times...
OK, took me way longer than it should have, but here's the complete thing:
SELECT
DISTINCT `lam_run_course`.run_id
FROM
`lam_run_course`
INNER JOIN
`lam_teacher_course`
ON `lam_teacher_course`.course_id = `lam_teacher_course`.course_id
LEFT JOIN
`lam_run_teacher` ON (`lam_run_teacher`.`run_id` = `lam_run_course`.`run_id` AND `lam_run_teacher`.`user_id` = 3)
WHERE
`lam_teacher_course`.user_id = 3
and `lam_run_teacher`.`run_id` IS NULL

A better logging design or some SQL magic?

I'm knee deep in modifying some old logging code that i didn't write and wondering what you think of it. This is an event logger written in PHP with MySQL, that logs message like:
Sarah added a user, slick101
Mike deleted a user, slick101
Bob edited a service, Payment
Broken up like so:
Sarah [user_id] added a user [message], slick101 [reference_id, reference_table_name]
Into a table like this:
log
---
id
user_id
reference_id
reference_table_name
message
Please note that the "Bob" and "Payment" in the above example messages are Id's to other tables, not the actual names. A join is needed to get the names.
It looks like the "reference _ table _ name" is for finding the proper names in the correct table, since only the reference _ id is stored. This would probably be good if somehow i could join on a table name that stored in reference_table_name, like so:
select * from log l
join {{reference_table_name}} r on r.id = l.reference_id
I think I see where he was going with this table layout - how much better to have ids for statistics instead of a storing the entire message in a single column (which would require text parsing). Now I'm wondering..
Is there a better way or is it possible to do the make-believe join somehow?
Cheers
To get the join based on the modelling, you'd be looking at a two stage process:
Get the table name from LOG for a particular message
Use dynamic SQL by constructing the actual query as a string. IE:
"SELECT l.* FROM LOG l JOIN "+ tableName +" r ON r.id = l.reference_id"
There's not a lot of value to logged deletions because there's no record to join to in order to see what was deleted.
How much history does the application need?
Do you need to know who did what to a value months/years in the past? If records are required, they should be archived & removed from the table. If you don't need all the history, consider using the following audit columns on each table:
ENTRY_USERID, NOT NULL
ENTRY_TIMESTAMP, DATE, NOT NULL
UPDATE_USERID, NOT NULL
UPDATE_TIMESTAMP, DATE, NOT NULL
These columns allow you to know who created the record & when, and who last successfully updated it and when. I'd create audit tables on a case by case basis, it just depends on what functionality the user needs.

Categories