Three tables - two identical, one with identifiers, MySQL - retrieve rows - mysql

I know I should probably be using a JOIN of some kind here, but I have run into problems.
The query below does seem to work - but the query takes ages to load. I am sure there must be a cleaner way to do it?
SELECT
DISTINCT SN.TermID AS id, SN.TermName AS name
FROM
wrex_termnames SN, wrex_term_registrations S, wrex_terms_summer_registrations X
WHERE
(SN.TermID = S.TermID AND S.TermPupilID = $id)
OR
(SN.TermID = X.TermID AND X.TermPupilID = $id)
ORDER BY termName DESC
The table structure is as follows:
wrex_termnames - a database with the names of the terms, i.e. 2008/09, 2009/10, 2010/11 which has fields TermID and TermName
wrex_term_registrations - a database of terms which pupils have registered for, which have fields TermID and TermPupilID
wrex_term_summer_registrations - a database of special summer terms which pupils have registered for, which also have fields TermID and TermPupilID
What I want to do is only return the names of the terms from wrex_termnames which have registrations logged against them in either wrex_term_registrations or wrex_terms_summer_registrations for the pupil I am interested in ($id).

Related

SQL IN Clause only returning rows with first match in comma separated list of IDs

I have 5 users which have a column 'shop_access' (which is a list of shop IDs eg: 1,2,3,4)
I am trying to get all users from the DB which have a shop ID (eg. 2) in their shop_access
Current Query:
SELECT * FROM users WHERE '2' IN (shop_access)
BUT, it only returns users which have shop_access starting with the number 2.
E.g
User 1 manages shops 1,2,3
User 2 manages shops 2,4,5
User 3 manages shops 1,3,4
User 4 manages shops 2,3
The only one which will be returned when running the IN Clause is User 2 and User 4.
User 1 is ignored (which it shouldn't as it has number 2 in the list) as it does not start with the number 2.
I'm not in a position to currently go back and change the way this is set up, eg convert it to JSON and handle it with PHP first, so if someone can try to make this work without having to change the column data (shop_access) that would be ideal.
A portable solution is to use like:
where concat(',', shop, ',') like '%,2,%'
Or if the value to search for is given as a parameter:
where concat(',', shop, ',') like concat('%,', ?, ',%')
Depending on your database, there may be neater options available. In MuSQL:
where find_in_set('2', shop)
That said, I would highly recommend fixing your data model. Storing CSV data in a database defeats the purpose of a relational database in many ways. You should have a separate table to store the user/shop relations, which each tuple on a separate row. Recommended reading: Is storing a delimited list in a database column really that bad?.
Also, you might want to consider using REGEXP here for an option:
SELECT *
FROM users
WHERE shop_access REGEXP '[[:<:]]2[[:>:]]';
-- [[:<:]] and [[:>:]] are word boundaries
SELECT * FROM users WHERE (shop_access = 2) OR (shop_access LIKE "2,%" OR shop_access LIKE "%,2,%" OR shop_access LIKE "%,2")

Correct approach for using related tables and possible workarounds?

I am asking this question which is to teach myself of using correct approach in a certain scenario than any how-to-code problem.
Since I am self taught student and haven't used relational tables before. With search and experiment, I have come to know the basic concept of relations and their usage but I am not sure if I am still using the correct approach while using these tables.
I do not have any official teachers so only place I can ask troubling questions is here with you guys.
For example, I have written a little code where I have 2 tables.
Table-1 is doctors which has an id (AI & Primary) and names table of varChar.
Table-2 is patient_recipts which has a doctor_name table of tinyInt
names table hold the name of the doctor
doctor_name table holds the corresponding id from doctors table
name and doctor_name are related to each other in database
Now when I need to fetch data from patient_recipts and display doctor's name, I will need to INNER JOIN doctor table, compare the doctor_name value with id in doctor table and get the name of the doctor.
The query I will use to fetch patients of a certain doctor, is something like,
$getPatList = $db->prepare("SELECT *
FROM patient_recipts
INNER JOIN doctor ON patient_recipts.doctor_name = doctor.id
WHERE dept = 'OPD' AND date_time = DATE(NOW())
ORDER BY patient_recipts.id DESC");
Now if I were to INSERT an action log entry in some other processor file, it would be something like (action and log entry),
$recipt_no = $_POST['recipt_no'];
$doctor_name = $_POST['doctor_name']; //this hold id(int) not text
$dept = $_POST['dept'];
$patient_name = $_POST['patient_name'];
$patient_tel = $_POST['patient_telephone'];
$patient_addr = $_POST['patient_address'];
$patient_age = $_POST['patient_age'];
$patient_gender = $_POST['patient_gender'];
$patient_fee = $_POST['patient_fee'];
$logged_user = $_SESSION['user_name'];
$insData = $db->prepare("
INSERT INTO patient_recipts (date_time, recipt_no, doctor_name, dept, pat_gender, pat_name, pat_tel, pat_address, pat_age, pat_fee, booked_by)
VALUES (NOW(),?,?,?,?,?,?,?,?,?,?)");
$insData->bindValue(1,$recipt_no);
$insData->bindValue(2,$doctor_name);
$insData->bindValue(3,$dept);
$insData->bindValue(4,$patient_gender);
$insData->bindValue(5,$patient_name);
$insData->bindValue(6,$patient_tel);
$insData->bindValue(7,$patient_addr);
$insData->bindValue(8,$patient_age);
$insData->bindValue(9,$patient_fee);
$insData->bindValue(10,$logged_user);
$insData->execute();
// Add Log
write_log("{$logged_user} booked OPD of patient {$patient_name} for {$doctor_name}");
OUTPUT: Ayesha booked OPD of patient Steve for 15
Now here the problem is apparent, I would need to execute the above mentioned fetch query yet again to get name of the doctor with ID comparison and bind the ID 15 to Doctor's name before calling the write_log() function.
So this is where I think my approach has been wrong altogether.
One way could be using actual doctor name in patient_recipts rather than ID
but this would i, in first place, kill the purpose of learning related tables and keys, learning design scenarios and troubleshooting.
Please help so I can understand and implement a better approach for days to come :)
Your table structure is correct, it's considered best practice to use the ID as the foreign key in other tables. If you want to include the doctor's name in the log message, you do have to do another SELECT query. A query like
SELECT name
FROM doctor
WHERE id = :doctor_id
is not very expensive.
But you can simply live with the log file only containing IDs. Look up the doctor's name later if you need to find out which doctor a particular log message is referring to.
BTW, when you use PDO, I recommend you use named placeholders (as in my example above) rather than ?. It makes the code easier to read, and if you modify the query to add or remove columns you don't have to change all the placeholder numbers.

Get stats table from a many to many relationship

I have a pivot table for a Many to Many relationship between users and collected_guitars. As you can see a "collected_guitar" is an item that references some data in foreign tables (guitar_models, finish).
My users also have some foreign data in foreign tables (hand_types and genders)
I want to get a derived table that lists data if I look for a particular model_id in "collected_guitar_user"
Let's say "Fender Stratocaster" is model id = 200, where the make is Fender (id = 1 of makes table).
The same guitar could come in a variety of finish hence the use of another table collected_guitars.
One user could have this item in his collection
Now what I want to find by looking at model_id (in this case 200) in the pivot table "collected_guitar_user" is the number of Fender Stratocasters that are collected by users that share the same genders.sex and hand_types.type as the logged in user and to see what finish they divide in (some percent of finish A and B etc...).
So a user could see that is interested in what others are buying could see some statistics for the model.
What query can derive this kind of table??
You can do aggregate counts by using the GROUP BY syntax, and CROSS JOIN to compute a percentage of the total:
SELECT make.make, models.model_name as model, finish.finish,
COUNT(1) AS number_of_users,
(COUNT(1) / u.total * 100) AS percent_owned
FROM owned_guitar, owned_guitar_users, users, models, make, finish
CROSS JOIN (SELECT COUNT(1) AS total FROM users) u
WHERE users.id = owned_guitar_users.user_id
AND owned_guitar_user.owned_guitar_id = owned_guitar.id
AND owned_guitar.model_id = models.id
AND owned_guitar.make_id = make.id
AND owned_guitar.finish_id = finish.id
GROUP BY owned_guitar.id
Please note though, that in cases where a user owns more than one guitar, the percentages will no longer necessarily sum to unity (for example, Jack and John could both own all five guitars, so each of them owns "100%" of the guitars).
I'm also a little confused by your database design. Why do you have a finish_id and make_id associated directly in the owned_guitar table as well as in the models table?

What's the difference between Rails active record's select and group?

I've been reading through tutorials on Rails' active record model operations. And I'm a little confused on the difference between .select and .group. If I wanted to get all the names of all my users in table User I believe I could do:
myUsers = User.select(:name)
so how would that be different from saying:
myUsers = User.group(:name)
thanks,
Will
The two differ like this:
User.select(:name)
is equivalent to this SQL statement
SELECT name from users;
and
User.group(:name)
is equivalent to
SELECT * from users GROUP BY name;
The difference is that with select(:name) you are taking all rows ordered by id, but only with column name. With group(:name) you are taking all rows and all columns, but ordered by column name.
User.pluck(:name) will be the fastest way to pull all the names from your db.
There is #to_sql method to check what DB query it is building. By looking at the DB query, you can confirm yourself what is going on. Look the below example :-
arup#linux-wzza:~/Rails/tv_sms_voting> rails c
Loading development environment (Rails 4.1.4)
>> Vote.group(:choice).to_sql
=> "SELECT \"votes\".* FROM \"votes\" GROUP BY choice"
>> Vote.select(:choice).to_sql
=> "SELECT \"votes\".\"choice\" FROM \"votes\""
>>
Now it is clear that Vote.select(:choice) is actually, SELECT "votes"."choice" FROM "votes", which means, select choice column from all rows of the table votes.
Vote.group(:choice) is grouping the rows of the votes table, based on the column choice and selecting all columns.
If I wanted to get all the names of all my users in table User.
Better is User.pluck(:name).

Mysql searching and sorting in one query with joined tables

I have 3 tables in Mysql 5.
Table Client: ID, Username, Password.
Table Client_Data: ID, Dataname
Table Client_Client_Data: client_id, Data_id, Value
The idea is that I can have the user of this software determine which information he wants to get from his clients. The Client_Data table would typically be filled with "First Name", "Last Name", "Address" and so on. The third table will join the tables together. An example:
Client: ID=1 Username=Bert01 Password=92382938v2nvn239
Client_Data: ID=1 Dataname=First Name
Client_Client_Data: client_id=1 data_id=1 value=Bert
This would mean that Bert01 has a first name "Bert" when joining the tables in a select query.
I'm displaying all this in a table where the columns are the DataName values (if you lost me here: the headers would be like "First Name", "Last Name" and so on). I want to be able to sort this data alphabetically for each column.
My solution was to use 2 queries. The first one would collect the data with WHERE Client_Data.Dataname = $sortBy ORDER BY Client_Client_Data.value and the second query would then collect the other data with WHERE Client.ID = 1 OR 2 OR 3 containing all of the ID's collected in the first query. This is working great.
The problem that has been playing in my mind for a long time now is when I want to search my data. This would not be too hard if it weren't for the sorting. After the search has been done the table would contain the results, but this table has to be sorted the same way as before.
Does anyone have any idea on how to do this without bothering the webserver's memory by looping through potentially thousands of clients? (meaning: i want to do this in Mysql).
If your solution would require altering the tables without losing the capability of storing this kind of data: that would be no problem.
you could relocate the looping. make a select from all the datatypes
Select * from Client_Data
then use that info to build a query like so (psuedo code)
orderby = "name"
query = "select *"
foreach(datatypes as dt){
query += ",(select d.value from Client_Client_Data as d where d.data_id="+dt.ID+" and d.client_id=cl.ID) as "+dt.Dataname
}
query = "from Client as cl order by "+orderby;
this will result in a table with all the available datatypes transfered into a column and the corresponding value connected to the correct client trough d.client_id=cl.ID
whereas cl.ID refers to the main queries client id and matches it against Client_Client_Data.client_id
now beware i am not entirely sure about the subqueries being more efficient. would require some testing