PDO and prepared statements on dynamic sized queries - php

I am developing a small gaming website for college fest where users attend few contests and based on their ranks in result table, points are updated in their user table. Then the result table is truncated for the next event. The schemas are as follows:
user
-------------------------------------------------------------
user_id | name | college | points |
-------------------------------------------------------------
result
---------------------------
user_id | score
---------------------------
Now, the first 3 students are given 100 points, next 15 given 50 points and others are given 10 points each.
Now, I am having problem in developing queries because I don't know how many users will attempt the contest, so I have to append that many ? in the query. Secondly, I also need to put ) at the end.
My queries are like
$query_top3=update user set points =points+100 where id in(?,?,?);
$query_next5=update user set points = points +50 where id in(?,?,?,?,?);
$query_others=update user set points=points+50 where id in (?,?...........,?);
How can I prepare those queries dynamically? Or, is there any better approach?
EDIT
Though its similar to this question,but in my scenario I have 3 different dynamic queries.

If I understand correctly your requirements you can rank results and update users table (adding points) all in one query
UPDATE users u JOIN
(
SELECT user_id,
(
SELECT 1 + COUNT(*)
FROM result
WHERE score >= r.score
AND user_id <> r.user_id
) rank
FROM result r
) q
ON u.user_id = q.user_id
SET points = points +
CASE
WHEN q.rank BETWEEN 1 AND 3 THEN 100
WHEN q.rank BETWEEN 4 AND 18 THEN 50
ELSE 10
END;
It totally dynamic based on the contents in of result table. You no longer need to deal with each user_id individually.
Here is SQLFiddle demo

Related

SELECT JOIN MYSQL

I have 2 tables which is userand userdata.
Once a customer buy a product, it will insert into table userdata and user.
In user table , the product id is recorded in user.buyid in form of "id, id ,id ,id ,id".
example : "23, 24". So I have tried to get all rows with id ( 23, 24 ) from product by using these queries
SELECT * FROM `userdata`,`user` WHERE `userdata`.`id` IN (`user`.`buyid`);
Query return 1 row product which is id=23
SELECT * FROM `user` INNER JOIN `userdata` ON `userdata`.`id` IN (`user`.`buyid`);
This too return only 1 row of product with id 23.
Expected result each row return this:
user.id | user.name | user.contact | product.id | product.name
1 | nazmi | 99999 | 23 | A
1 | nazmi | 99999 | 24 | B
Fix your data model! Storing lists of ids in a string is bad, bad, bad:
Your id is an integer. It should be stored as a number, not a string.
Your id refers to another table. It should have properly declared foreign key relationships.
A column should contain a single column.
SQL has a great way of storing lists. Really powerful. It is called a "table" not a "string column".
String processing capabilities are pretty poor in SQL.
Queries with such data structures cannot take advantage of indexes and partitioning.
Sometimes, you are stuck with other people's really, really, really bad decisions. MySQL offers a nice function (too nice in my opinion) to help:
SELECT *
FROM user u JOIN
userdata ud
ON find_in_set(ud.id, u.buyid) > 0;
If you have spaces in your id list, then you need to get rid of them. And the separator has to be a comma.
SELECT *
FROM user u JOIN
userdata ud
ON find_in_set(ud.id, replace(u.buyid, ' ', '')) > 0;

MySql Statement across different rows

Im trying to query a wordpress mySQL database where the values pertaining to each user is spread across different rows (only god knows why they did it that way, why didnt they just put all the user information on one row)
For example
ID USER_ID FIELD_ID VALUE
1 1 2 my name is paul smith
2 1 3 books
3 1 4 loggedin
4 1 5 state=busy
5 2 2 my name is big boy
5 2 3 pens
6 2 4 offline
7 2 5 state=idle
here you can see each row has a different meaning based on the FIELD_ID for the same user
FIELD_ID=2 contains the user name
FIELD_ID=3 contains mean what they bought
FIELD_ID=4 means logged in
FIELD_ID=5 is busy or idle
I need to make ONE query that will return all users that are LOGGED IN and STATE=IDLE and bought BOOKS (or bought some other item like pens whatever they want to search for)
so i started off with this php statement
$find="pens";
$q = "SELECT * FROM table
WHERE FIELD_ID='4' AND VALUE='loggedin'
AND FIELD_ID='5' AND VALUE='idle'
AND USER_ID IN
(SELECT USER_ID FROM table WHERE FIELD_ID ='3' AND VALUE LIKE '%$find%')
But i dont think this will work because how can
FIELD_ID = '4' AND VALUE='loggedin' AND FIELD_ID='5' AND VALUE='idle' all at the same time because they are on different rows.
Does anybody know how to make ONE mySQL statement to do it?
you could use a self join
SELECT distinct a.user_id
FROM table a
inner join table b on a.user_id = b.user_id and b.field_id = 5 and b.value='idle'
inner join table c on a.user_id = c.user_id and c.field_id = 4 and c.value = 'loggedin'
where a.field_id=3 and a.value='BOOK'

GROUP_CONCAT with ordering and missing fields

I have a series of tables that I want to get rows returned from in the following format:
Student ID | Last Name | First Name | Quiz Scores
-------------------------------------------------
xxxxxxx | Snow | Jon | 0,0,0,0,0,0,0,0
There's 3 relevant tables (changing any existing DB structure is not an option):
person - table of all people in the organization
enrollment - table of student and faculty enrollment data
tilt.quiz - table of quiz scores, with each row storing an individual score
The tricky part of this is the Quiz Scores. A row for the quiz score only exists if the student has taken a the quiz. Each quiz row has a module, 1 - 8. So possible quiz data for a student could be (each of these being a separate row):
person_id | module | score
---------------------------
223355 | 1 | 100
223355 | 2 | 95
223355 | 4 | 80
223355 | 7 | 100
I need the quiz scores returned in proper order with 8 comma separated values, regardless if any or all of the quizzes are missing.
I currently have the following query:
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(tilt.quiz.score) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name
The problems with this are:
It does not order the quizzes by module
If any of the quizzes are missing it simply returns fewer values
So I need the GROUP_CONCAT scores to at least include commas for missing quiz values, and have them ordered correctly.
The one solution I considered was creating a temporary table of the quiz scores, but I'm not sure this is the most efficient method or exactly how to go about it.
EDIT: Another solution would be to execute a query to check for the existence of each quiz individually but this seems clunky (a total of 9 queries instead of 1); I was hoping there was a more elegant way.
How would this be accomplished?
There are some assumptions here about your data structure, but this should be pretty close to what you're after. Take a look at the documentation for GROUP_CONCAT and COALESCE.
SELECT `person`.`id`, `person`.`first_name`, `person`.`last_name`,
GROUP_CONCAT(
COALESCE(`tilt`.`quiz`.`score`, 'N/A')
ORDER BY `tilt`.`quiz`.`module_id`
) AS `scores`
FROM `person`
CROSS JOIN `modules`
LEFT JOIN `enrollment` USING (`person_id`)
LEFT JOIN `tilt`.`quiz` USING (`person_id`, `module_id`)
WHERE (`enrollment`.`course_id` = '$num')
AND (`enrollment`.`enrollment_status_id` = 1)
GROUP BY `person`.`id`
ORDER BY `person`.`last_name`
First thing to do is use the IFNULL() function on the score
Then, use ORDER BY inside the GROUP_CONCAT
Here is my proposed query
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(IFNULL(tilt.quiz.score,0) ORDER BY tilt.quiz.module) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name

Joining multiple tables with NULL values to avoid duplication

I am building a news feed from multiple tables status, events and tracks. The data retrieved from these tables should correspond to the user-id of all the users that I follow. On the face of it I thought this seemed simple enough and I could do this with a few joins.
Every row in each of the status, events and tracks table has unique ID and they are also unique from each other, this should make matters easier later. I have done this using a unique_id table with a primary key to retrieve ID's before inserting.
My trouble is upon joining everything together the values duplicate.
Example
If I have this data.
----------
**Status**
user-id = 1
id = 1
status = Hello Universe!
----------
**Events**
user-id = 1
id = 2
event-name = The Big Bang
----------
**Tracks**
user-id = 1
id = 3
track-name = Boom
----------
Assuming I follow user 1 I would want to retrieve this.
user-id ---- id ---- status ---- event-name ---- track-name
1 1 Hello NULL NULL
Universe
1 2 NULL The Big Bang NULL
1 3 NULL NULL Boom
But in reality what I would get is something like this.
user-id ---- status.id ---- events.id ---- tracks.id ---- status ---- event-name ---- track-name
1 1 2 3 Hello The Big Bang Boom
Universe
And that row would be repeated 3 times.
Most of the queries I have tried will get something along those lines.
SELECT * FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN status ON followers.`follows-id` = status.`user-id`
LEFT JOIN events ON followers.`follows-id` = events.`user-id`
LEFT JOIN tracks ON followers.`follows-id` = tracks.`user-id`
WHERE users.`id` = 2
I am using laravel, so eventually this query will be put into Eloquent format. If there is a simpler and a not performance degrading way of doing this in Eloquent please let me know.
Edit
I cannot use a UNION as there is a different number of values in each table. The example is simplified for ease of reading.
Thanks to Frazz for pointing out I could use UNIONS. I have researched into them and come up with this query.
SELECT stream.*, users.id AS me FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN (
SELECT `id`,`user-id`,`created_at`, `name`, NULL as status
FROM events
UNION ALL
SELECT `id`,`user-id`, `created_at`,NULL AS name, `status`
FROM status
) AS stream ON stream.`user-id` = `followers`.`follows-id`
WHERE users.id = 2
Now comes the process of converting it to an eloquent model...

How to optimize a score/rank table with different specific scores and ranks?

in our project we've got an user table where userdata with name and different kind of scores (overall score, quest score etc. is stored). How the values are calculated doesn't matter, but take them as seperated.
Lets look table 'users' like below
id name score_overall score_trade score_quest
1 one 40000 10000 20000
2 two 20000 15000 0
3 three 30000 1000 50000
4 four 80000 60000 3000
For showing the scores there are then a dummy table and one table for each kind of score where the username is stored together with the point score and a rank. All the tables look the same but have different names.
id name score rank
They are seperated to allow the users to search and filter the tables. Lets say there is one row with the player "playerX" who has rank 60. So if I filter the score for "playerX" I only see this row, but with rank 60. That means the rank are "hard stored" and not only displayed dynamically via a rownumber or something like that.
The different score tables are filled via a cronjob (and under the use of a addional dummy table) which does the following:
copies the userdata to a dummy table
alters the dummy table by order by score
copies the dummy table to the specific score table so the AI primary key (rank) is automatically filled with the right values, representing the rank for each user.
That means: Wheren there are five specific scores there are also five score tables and the dummy table, making a total of 6.
How to optimize?
What I would like to do is to optimize the whole thing and to drop duplicate tables (and to avoid the dummy table if possible) to store all the score data in one table which has the following cols:
userid, overall_score, overall_rank, trade_score, trade_rank, quest_score, quest_rank
My question is now how I could do this the best way and is there another way as the one shown above (with all the different tables)? MYSQL-Statements and/or php-code is welcome.
Some time ago I tried using row numbers but this doesn't work a) because they can't be used in insert statements and b) because when filtering every player (like 'playerX' in the example above) would be on rank 1 as it's the only row returning.
Well, you can try creating a table with the following configuration:
id | name | score_overall | score_trade | score_quest | overall_rank | trade_rank | quest_rank
If you do that, you can use the following query to populate the table:
SET #overall_rank:=-(SELECT COUNT(id) FROM users);
SET #trade_rank:=#overall_rank;
SET #quest_rank:=#overall_rank;
SELECT *
FROM users u
INNER JOIN (SELECT id,
#overall_rank:=#overall_rank+1 AS overall_rank
FROM users
ORDER BY score_overall DESC) ovr
ON u.id = ovr.id
INNER JOIN (SELECT id,
#trade_rank:=#trade_rank+1 AS trade_rank
FROM users
ORDER BY score_trade DESC) tr
ON u.id = tr.id
INNER JOIN (SELECT id,
#quest_rank:=#quest_rank+1 AS quest_rank
FROM users
ORDER BY score_quest DESC) qr
ON u.id = qr.id
ORDER BY u.id ASC
I've prepared an SQL-fiddle for you.
Although I think performance will weigh in if you start getting a lot of records.
A bit of explanation: the #*_rank things are SQL variables. They get increased with 1 on every new row.

Categories