Finding events in a MySQL table, manipulating and storing the data - php

This is question I have still not been able to resolve, so maybe I need to be clearer in what I need.
I have a datasource which I receive and upload to a MySQL database each month. I cannot alter the data although it may not be in the most helpful format. It looks like this:
Ref Action Date/Time User Location
00123 Create 01:02:12_09:13:13 J Jones Home
00456 Create 01:02:12_09:13:13 J Jones Home
00123 Revise 03:02:12_15:20:01 A Smith Home
00789 Create 01:02:12_09:13:13 J Jones Home
00123 Delete 05:02:12_10:51:45 B Halls Home
x 1000's
It tracks events that occur against a reference number, which is generated by the first event (the Create event). These events occur at varying intervals and are done by various people in various locations.
Using the above example I need to be able to loop through the data for a particular month, pull out a reference number and its 'Create' event, then locate all the other events for that ref number.
I then need to be able to manipulate this information, for example calculate the time difference between Create and Revise, and then Revise and Delete, and who did them, where.
I need to be able to this for all the ref numbers created in the month or a date range.
So I am hoping I can create a query that can do this - find a ref number, find the other events, string them together in some way - so that in the end I have new data -
Ref Number, Time of Create, Create By Who, Time of Revise/By Who, Time of Delete etc
It would be useful if this new data could be stored in a new table, I would think(?)
Can this be done as a query or perhaps combination of query and PHP (arrays?)

Okay, the first stage is to transform this data in a MySQL table, and
you will have something like
ref integer,
action enum('create','revise','delete'), -- ordered by life cycle: create first, delete last
ts timestamp,
user varchar(32), -- or maybe user_id
loc varchar(32), -- again maybe location_id, or ENUM
So to pull out an event given its reference
SELECT * FROM mytable WHERE ref = 123 ORDER BY action;
For date ranges you can use
WHERE ts BETWEEN 'date1' AND 'date2'
To get time differences,
SELECT TIMEDIFF(b.ts, a.ts) AS delta, b.*
FROM mytable AS a
JOIN mytable AS b ON (a.action = 'CREATE' AND a.ref = b.ref)
WHERE a.ref = 123;
Finally to denormalize the table:
SELECT _create.*,
TIMEDIFF(_revise.ts - _create.ts) AS revise_delta,
TIMEDIFF(_delete.ts - _delete.ts) AS delete_delta,
FROM mytable AS _create
LEFT JOIN mytable AS _revise ON (_create.ref = _revise.ref
AND _create.action = 'CREATE' AND AND _revise.action = 'REVISE' AND ***)
LEFT JOIN mytable AS _delete ON (_create.ref = _delete.ref
AND _create.action = 'CREATE' AND AND _delete.action = 'DELETE' AND ***)
;
Here * is some way of identifying the unique relation between a CREATE event and the corresponding DELETE event.
If refs are unique, then AND *** is not needed (* equals to True).
For example if the refs are recycled every month, and it never happens that an event may span two months, you can impose that the year and month of _create should be the same as those of _revise; that and the equality of .ref establish a biunivocal match.
Otherwise it gets much more complicated, and I'd try creating a VIEW that for each CREATE event selects the COALESCE of NOW() and the datetime of the earliest CREATE event with that same ref but ts greater or equal to the current. This way you identify a "window" in which events with that ref should be attributed to that specific CREATE. But this is based on the hypothesis that it never happens that
00123 CREATE 01-NOV-2012 Jack
00123 CREATE 04-NOV-2012 Jill
00123 DELETE 05-NOV-2012 Joe <-- which event is this one?

Well, to get field values for a particular action:
$db_connection = mysqli_connect();//Have DB variables here
$ref = ;//Ref number here e.g 123
$action = '';//action to check for here e.g create
$query = "SELECT * FROM yourtable WHERE ref=".$ref." AND action=".$action."";
$q = mysqli_query($db_connection,$query);
while($row = mysqli_fetch_asocc($q)){
//The variables are in the $row array with each on an index based on a column name
}

Related

storing a bunch of dates on a separate table, but have trouble fetching it properly

I have some tasks that I have to store in my database. And each task has an array of dates in which the tasks were completed. I've learn that it is better to not use a array (serialize) to store dates, but instead make another table. So I did:
taskTable contains columns: taskID, userid, description, name
task_days contains columns: taskID, day
But Im having trouble with php,
usually I can easily send my data to client with:
function getTasks(){
$app = \Slim\Slim::getInstance();
$userid = $app->request->params('userid');
$db = getDB();
$result = $db->prepare("Select * From taskTable where userid = ?");
$result->execute(array($userid));
$result->setFetchmode(PDO::FETCH_ASSOC);
echo json_encode($result->fetchAll());
}
I encode it, then client can easily read it as an array of JSON. But now with two tables, I'm not sure how to do it efficiently. I know I can get the required information with this query:
Select * from taskTable as t, task_days as d where t.taskID = d.taskID
But how do I make it so the days will be in an array associated with the correct task.
Do I first Select * From taskTable where userid = $userid, then for each task, I will do a query on table task_days? that seems extremely inefficient though.
So I want something like the following:
[
{taskid: 123, userid: 1, description: "do task", name: "tony", day:[1998-01-02, 1998-02-03]},
{taskid: 124, userid: 2, description: "do task2", name: "Ann", day:[2016-01-02, 2016-02-03, 2016-01-01]},
...
]
There's a couple of approaches.
1) One approach, as you already outline, is to run a query that returns the the columns from just `taskTable`. And for each row returned, run another query to get the associated rows from task_days. And you are right, that's usually not the most efficient approach. But for a reasonably small number of rows, performance should be reasonable as long as appropriate indexes are available.)
2) Another approach, assuming `taskid` is the primary key of `taskTable` is to perform a join, and use a "GROUP BY" to collapse the rows. The "GROUP_CONCAT" aggregate function can convert the multiple values of `day` from the `task_days` table into a single string. For example:
SELECT t.taskid
, t.userid
, t.description
, t.name
, GROUP_CONCAT(d.day ORDER BY d.day) AS `day`
FROM taskTable t
LEFT
JOIN task_days d
ON d.taskid = t.taskid
GROUP BY t.taskid
ORDER BY t.taskid
This would return the day as a string, not an array. If you need an array, your code would need to do that. (As a convenient way to do that, the PHP explode function might be suitable.)
NOTE: the length of the string returned by GROUP_CONCAT is limited by group_concat_max_len variable, and also by max_allowed_packet.
3) Another way to approach this is to perform a join operation, and pull back the "duplicated" task information, ordered by taskid and day
SELECT t.taskid
, t.userid
, t.description
, t.name
, d.day
FROM taskTable t
LEFT
JOIN task_days d
ON d.taskid = t.taskid
ORDER BY t.taskid, d.day
That would get a result set like this:
taskid userid description name day
------ ------ ----------- ----- ----------
123 1 do task tony 1998-01-02
123 1 do task tony 1998-02-03
124 2 do task2 Ann 2016-01-02
124 2 do task2 Ann 2016-02-03
124 2 do task2 Ann 2016-01-01
Then your code would need to do some rudimentary "control break" processing. Basically, compare the taskid of the current row to the taskid from the previous row. If they match, you are processing just a new `day` value for the same task.
If the taskid of the current row is different than the taskid from the previous row, then you are starting a new task.
Your code would effectively be ignoring the duplicated rows from `taskTable`, basically squinting at the result set and seeing it like this:
taskid userid description name day
------ ------ ----------- ----- ----------
- 123 1 do task tony 1998-01-02
+ 1998-02-03
- 124 2 do task2 Ann 2016-01-02
+ 2016-02-03
+ 2016-01-01
FOLLOWUP
The second option is closest to your original implementation, a comma separated list of values as a string, in a character column.
As far as storing a comma separated list, that's a SQL anti-pattern, and it's usually best avoided it. Multi-valued attributes can be stored in a separate table, like you have done.
The exception would be if you never, ever need the database to see the values in the list as separate values.
If you are storing that "list of dates" as if it were an image, for example like the contents of a jpeg... if you always store the entire value into the column, and always extract the contents of the column as a single value... if never need to search for an individual date, or add a date to an existing list, or remove a date from a list... and if you never need the database to enforce any constraints on the values, or do any validation of the contents...
If all of those conditions are satisfied, only then might it make sense to store a comma separated list as a single column.
My personal preference, if the implementation is targeted only to MySQL, would be the second option... using GROUP_CONCAT. If the length of the string generated by the GROUP_CONCAT exceeds group_concat_max_len, the string will be truncated, with no warning or error. (I believe that's a limitation in bytes, and not characters.)
The safest coding practice would be to do perform a query:
SELECT ##session.group_concat_max_len
save the value returned by that. Then, for the values returned from the GROUP_CONCAT expression, compare the length (in bytes) to the saved value, to see if truncation has occurred. (If the length of the returned string is less than the value of group_concat_max_len, then you can be pretty confident that truncation has not occurred.) It's also possible to override the current value of the variable (before you run the statement containing GROUP_CONCAT, with a separate SET statement. Something like this:
SET SESSION group_concat_max_len = 131072 ;
(Just be careful not to exceed max_allowed_packet.)

mysql - How to add a counter to a column?

I have events each with 50 maximum participants.
Every time a user signs up for an event, his name, email, and mobile no. gets saved in the database. So I am inserting these values whenever a sign up is made: ID, p_name, p_mobile, p_email, p_event the last column is the name of the event.
I need to add a new column called p_max wherein it will automatically increase by 1 for each unique event. A sort of counter.
So I can then check if the maximum is reached by getting the p_max column of the last row with the event name.
I hope I explained the question clearly. Feel free to ask questions for clarification.
Thanks in advance!
If I understand the question correctly, you would need to check for the number of users for a specific event before you confirm the sign-up.
So you would not need a new column (or it would be a column in the events table where you can specify the maximum number of participants per event), you need to check how many users there already are for that specific event.
You could use something like this to query the database:
SELECT COUNT(*) as number_of_participants FROM users WHERE p_event = YOUR_EVENT_ID
First, you want an events table, that has the maximum number of participants.
Then you need a before insert trigger on the participants table. This trigger would fail if the following is true:
select (count(*) >= max(e.maxparticipants))
from participants p join
events e
on p.event = e.event
where event = new.event;
These will limit the number of participants in an event.
You can also implement this in a stored procedure rather than a trigger, if you wrap your insert logic in stored procedures.
Or, you can implement this logic directly in your application, although I think it is safer to implement it in the database. If you do so, consider this statement:
insert into participants(<columns go here>)
select <column values go here>
from dual
where (select max(e.maxparticipants) - COUNT(*))
from participants p join
events e
on p.event = e.event
where event = THEEVENT
) > 0;
This insert will not insert any rows once the maximum has been reached.

Recalculate values in MySQL tables

I have a web application that stores points in a table, and total points in the user table as below:
User Table
user_id | total_points
Points Table
id | date | user_id | points
Every time a user earns a point, the following steps occur:
1. Enter points value to points table
2. Calculate SUM of the points for that user
3. Update the user table with the new SUM of points (total_points)
The values in the user table might get out of sync with the sum in the points table, and I want to be able to recalculate the SUM of all points for every user once in a while (eg. once a month). I could write a PHP script that could loop through each user in the user table and find the sum for that user and update the total_points, but that would be a lot of SQL queries.
Is there a better(efficient) way of doing what I am trying to do?
Thanks...
A more efficient way to do this would be the following:
User Table
user_id
Points Table
id | date | user_id | points
Total Points View
user_id | total_points
A view is effectively a select statement disguised as a table. The select statement would be: SELECT "user_id", SUM("points") AS "total_points" FROM "Points Table" GROUP BY "user_id". To create a view, execute CREATE VIEW "Total Points View" AS <SELECT STATEMENT> where SELECT STATEMENT is the previous select statement.
Once the view has been created, you can treat it as you would any regular table.
P.S.: I don't know that the quotes are necessary unless your table names actually contain spaces, but it's been a while since I worked with MySQL, so I don't remember it's idiosyncrasies.
You have to user Triggers for this, to make the users total points in sync with the user_points table. Something like:
Create Trigger UpdateUserTotalPoints AFTER INSERT ON points
FOR EACH ROW Begin
UPDATE users u
INNER JOIN
(
SELECT user_id, SUM(points) totalPoints
FROM points
GROUP BY user_id
) p ON u.user_id = p.user_id
SET u.total_points = p.totalPoints;
END;
SQL Fiddle Demo
Note that: As noted by #FireLizzard, if these records in the second table, are frequently updated or delted, you have to have other AFTER UPDATE and AFTER DELETE triggers as well, to keep the two tables in sync. And in this case the solution that #FireLizzard will be better in this case.
If you want it once a month, you can’t deal with just MySQL. You have too « logic » code here, and put too logic in database is not the correct way to go. The trigger of Karan Punamiya could be nice, but it will update the user_table on every insert in points table, and it’s not what you seem to want.
For the fact you want to be able to remove points, just add bsarv new negated rows in points, don’t remove any row (it will break the history trace).
If you really want it periodically, you can run a cron script that does that, or even call your PHP script ;)

How can I store dates/attendance?

I'm looking for for an opinion.
I have a list of people and will need to store when they are present at a location so those in charge can check them off a list. I'm not 100% sure how long the dates will be needed but I'm assuming they may need to look at previous attendance lists.
My first instinct is to have a column for each date but that could result in many many columns. I could just store a list of dates next to each person:
"01/01/2012,01/15/2012,02/18/2012..."
that could result in a very long entry. It seems like neither is a good option.
If anyone has a suggestion or guidance on an approach please let me know. Thanks.
A complex, but also very clean approach would be
Table "persons":
id
name
Table "dates":
id
location
date
... whatever info the "dates" table needs
Table "attendances":
date_id (link to an entry in the "dates" table)
person_id (link to an entry in the "persons" table)
attended (yes/no)
Then fill the database with the appropriate dates, and fill the "attendances" table according to which persons need to be present at each date.
This is, as said, complex to implement, but it's incredibly flexible - you can have any number of dates and attendees; you can excuse people from attending a specific date programmatically; you can add people to groups...
Link tables.
One table of people
ID
Name
One table of classes
ID
Name
One table linking person to class to date.
ID
personID
classID
cDate
So all you would need to do to determine which students were preset on a certain date in a certain class:
SELECT *
FROM people p
LEFT JOIN peopletoclass ptc ON p.id = ptc.personid
LEFT join class c ON c.id = ptc.classid
WHERE ptc.cDate = '2011-11-07' AND c.id = '1';
Above (for example) would get all people in class id 1 on November 7th 2011.
Create a table "attendance" consisting of a person_id field and a date_present field. You can't store this into columns or a long list using a string ;-).
Than you can use queries where you join the table Person with Attendance.
Your first instinct would result in a horrible table design. What you should have is a seperate table that stores the users/locations/dates tuples
e.g.
userID locationID date
1 party 1/1/2011 00:00:00
1 bathroom 1/1/2011 00:05:00
1 party 1/1/2011 00:15:00
would show that user #1 was at a New Year's Eve party, then went to pray before the porcelain altar at 12:05am, then returned to the party 10 minutes later.

Designing my MySQL Database for rating things on different days

I've been using reddit as my help recently, and they've been great but then I found this site and feel it's a much more appropriate place to ask my questions. Hopefully the helpfulness trend continues here. Let me start by saying I know this project is pretty intensive considering I have never done anything like it before, however that doesn't change the fact that I am doing it and I am willing to learn how.
I am designing a website which will feature the the bar and dining specials for my city. Users will be able to log on, and the home page will display the top 5 rated bar specials/ happy hours, and the top 5 dining specials/happy hours for that day(i.e. monday, tuesday, etc.). Each "special" will have an up down rating for the user to give their input on the special.
A bar page will list all of the bars and their specials for that day, and the option to upvote or downvote the special. Same thing with a dining page. From there, the person can click on the bar name to be taken to a page specifically about that bar. That page will show the specials for the entire week.
I'm in the process of designing the MySQL table now in phpmyadmin, as it seems like that's where I need to start after having written the html and css for the design. I've tried googling for a framework that I could build off of, however I didn't come up with anything relevant.
Would it be best to make separate tables under each database for each day of the week, put everything into one table, or another way entirely. This is what I made so far, let me know if I am on the right track or not. Thanks a million!
!(http://dl.dropbox.com/u/16887445/Screen%20shot%202011-07-06%20at%208.01.10%20PM.png)
Day of the week is an attribute of a "specials" record, not a record unto itself. Picture this -- if you have one table per day, and you want to get all specials for one bar for a week, you'll have to make seven queries:
SELECT * FROM sunday_specials WHERE
establishment_id = <id> and date = <sunday>;
SELECT * FROM monday_specials WHERE
establishment_id = <id> and date = <monday>;
SELECT * FROM tuesday_specials WHERE
establishment_id = <id> and date = <tuesday>;
...
This is (hopefully) obviously a bad design. Whay you want is to do be able to do it with one query:
SELECT * FROM specials WHERE
establishment_id = <id> and date >= <sunday> and date <= <saturday>;
Note also that you don't even really care about storing the day of the week, all you need is the date (which you need anyway), from which you can extract day of the week. I'd create a schema something like this:
-- This holds one record for each bar/restaurant.
CREATE TABLE establishments
(
id INT SERIAL,
name TEXT NOT NULL,
address TEXT,
phone TEXT,
uri TEXT,
type INT NOT NULL DEFAULT 1,
PRIMARY KEY (id)
);
-- This holds one record per day per establishment. Not storing the
-- day of the week allows you to keep all records going back in time.
-- Up/Down voting simply increments or decrements the rating field.
CREATE TABLE specials
(
id INT SERIAL,
establishment_id INT NOT NULL REFERENCES establishments(id),
special_date DATE NOT NULL DEFAULT NOW()::DATE,
name TEXT NOT NULL,
description NOT NULL,
rating INT NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
So, to display the specials for "My Bar", you do something like:
$start_date = <whenever>
$end_date = <whenever>
$id = SELECT id FROM establishments WHERE name = 'My Bar';
$specials = SELECT * FROM specials WHERE
id = $id AND special_date >= $start_date AND special_date <= $end_date;
To display the top five rated specials for Monday:
SELECT * FROM specials WHERE special_date = <monday> ORDER BY rating DESC LIMIT 5;
If I understand what you're going for correctly, this is probably how I'd design the schema:
TABLE location // For storing details about the bars & restaurants
location_id (pkey)
location_name
location_type // For flagging if it's a restaurant, bar, other, etc.
location_address
...
TABLE specials // Holds all the specials for all days of the week
specials_id (pkey)
location_id (fkey)
day_of_week // 1-7 values
special_name
special_detail
...
TABLE votes
vote_id (pkey)
specials_id (fkey)
vote_type // Flag for up/down (1, -1)
...
Once you have the data normalised, you can use queries or views to pull the aggregated data. For example, to find all the up votes for a special, you could use a query like this:
SELECT COUNT(*) FROM votes WHERE specials_id = '1234' AND vote_type = '1'

Categories