I have a table setup for member logins. Right now the last_login field is stamped with MySQL's NOW(). But I want to also track their last active time on the site. And the only way I can think of is to create a new query, insert it into every procedure I have on every page, and update a timestamped last_activity field for the current login. Is there a better way to do this?
Example:
MariaDB [master]> select logintime, last_activity
from memberlogins
where memberid = "1"
order by loginid
desc limit 1;
+---------------------+---------------------+
| logintime | last_activity |
+---------------------+---------------------+
| 2017-02-11 22:28:54 | 2017-02-11 23:48:14 |
+---------------------+---------------------+
That's what I want, to add the last_activity to the table. And the only way I can think of to accomplish this is to add this query:
$stmt = $dbsotp->prepare('UPDATE memberlogins
SET last_activity = NOW()
WHERE memberid = :memberid');
And then the rest of the PDO here. So all I'm asking is if there's a better way to do this than inserting this query into every procedure I have on every page. I have 67 pages with a several hundred procedures, that's why I ask if this is the only way or if there's a better way to go.
Related
I would like to create an event that when a lend_date column has passed exactly 15 days, it would execute an INSERT query.
It would get the ID of that row and userid, and insert it to another table.
For example:
id | userid | lend_date
---+----------+----------------------
1 | 1 | 2015-09-24 15:58:48
2 | 1 | 2015-09-21 08:22:48
And right now, it is exactly 2015-10-06 08:23:41. So the event should get the ID of the second row, which is 2, and insert it to another table.
What should my event query look like?
The event type is RECURRING. But I'm also not sure if I should execute it every hour or everyday. What would be the best recommendation for it?
And is this a better way than using Task Scheduler?
The other table that I wanted to insert the fetched ID is notification_table, where it will notify the user that he/she has an overdue date.
notification_table looks like this:
id | userid | notificationid | notificationdate |
---+----------+------------------+----------------------+
1 | 1 | 1 | 2015-09-24 15:58:48 |
2 | 1 | 1 | 2015-09-21 08:22:48 |
I'm looking at this query:
INSERT INTO notification_table (id, userid, notificationid, notificationdate)
SELECT id, userid, 1, NOW()
FROM first_table
WHERE lend_date + INTERVAL 15 DAY = NOW();
Seeing the words exactly, event, and datetime in the same sentence makes me cringe. Why? For one thing, it's hard to get one datetime value to exactly match another. For another thing, events often run slightly after the scheduled time, especially on a busy database server. It takes them a little time to start up.
If you need the id values from a table where the records are more than 15 days old, the most time-precise way to get them is with a query or view.
CREATE OR REPLACE VIEW fifteen
AS SELECT id
FROM table
WHERE `datetime` < NOW() - INTERVAL 15 DAY
You can, of course, write an event to copy the ids to a new table. You'll have to go to some trouble to make sure you don't hit the same id values more than once, by using this sort of query in the event.
INSERT INTO newtable (id)
SELECT id
FROM table
WHERE `datetime` < NOW() - INTERVAL 15 DAY
AND id NOT IN (SELECT id FROM newtable)
How often should you run the repeating event? That depends entirely on how quickly the id values need to make it into the new table after they turn fifteen days old. If your application requires it to be less than a minute, you really should go with the view rather than the event. Anything more than a minute of allowable delay will let you use a repeating event at that frequency.
Using an audit trail in MySQL, how would you find records that had a specific value during a certain time frame?
Let's say I want to get the ids of users who were a site admin one week ago (role_id=1).
Given the table:
**USER**
id | role_id | ...
And a revision table:
**TABLE_HISTORY**
table_name | date | id | column_name | column_value
(assume I'm storing create data in there for better or worse and I'm soft deleting)
How would you find people where role_id = 1 one week ago today using only one query? Keep in mind that their role might have changed to 2 after it was one, this means you can't just select where role_id = 1
Something like this maybe?:
SELECT * FROM
(
SELECT *
WHERE (date < $timestamp AND column_name = 'role_id')
LIMIT 1
)
WHERE ('is_admin'=1)
In my application I have association between two entities employees and work-groups.
This association usually changes over time, so in my DB I have something like:
emplyees
| EMPLOYEE_ID | NAME |
| ... | ... |
workgroups
| GROUP_ID | NAME |
| ... | ... |
emplyees_workgroups
| EMPLOYEE_ID | GROUP_ID | DATE |
| ... | ... | ... |
So suppose I have an association between employee 1 and group 1, valid from 2014-01-01 on.
When a new association is created, for example from 2014-02-01 on, the old one is no longer valid.
This structure for the associative table is a bit problematic for queries, but I actually would avoid to add an END_DATE field to the table beacuse it will be a reduntant value and also requires the execution of an insert + update or update on two rows every time a change happens in an association.
So have you any idea to create a more practical architecture to solve my problem? Is this the better approach?
You have what is called a slowly changing dimension. That means that you need to have dates in the employees_workgroup table in order to find the right workgroup at the right time for a set of employees.
The best way to handle this is to have to dates, which I often call effdate and enddate on each row. This greatly simplifies queries, where you are trying to find the workgroup at a particular point in time. Such a query might look like with this structure:
select ew.*
from employees_workgroup ew
where MYDATE between effdate and enddate;
Now consider the same results using only one date per field. It might be something like this:
select ew.*,
from employees_workgroup ew join
(select employee_id, max(date) as maxdate
from employees_workgroup ew2
where ew2.employee_id = ew.employee_id and
ew2.date <= MYDATE
) as rec
on ew.employee_id = rec.employee_id and ew.adte = ew.maxdate;
The expense of doing an update along with the insert is minimal compared to the complexity this will introduce in the queries.
I'm trying to design a table that will help me determine if a licence has expired or not. I'd like suggestions for better ways to structure the table.
Specifically I want to do the below 3 things:
I want to know if a licence exists
I want to know if the licence has
expired
I want to extend the licence if it hasn't expired
I came up with the below table. When a user's licence is extended the first entry for that user's licence is marked as expired.
I thought this method was good because if there was some kind of problem I still have the history to go by.
----------------------------------------------------------------------------
| user_id | licence | start | stop | expired |
----------------------------------------------------------------------------
| 22 | other | 03JUL2010 | 03JUL2012 | true |
| 55 | commercial | 03JUL2012 | 03JUL2014 | true | <= marked as expired because it was extended.
| 55 | commercial | 04JUL2012 | 03JUL2016 | false |
----------------------------------------------------------------------------
NOTE: 04JUL2012 shows the day the licence was extended.
Does this seem like a good approach? Is there anything you would change?
I would have three tables, because it seems that there is a N:M (many-to-many relationship) between users and licenses:
users
------------
user_id [PK]
...
users_has_licenses
----------------------
user_id [PK] (references users.user_id)
license_id [PK] (references licenses.license_id)
issued [PK]
expire_date
licenses
-------------------
license_id [PK]
...
The tables users and licenses are fairly straightforward. They store information intrinsic to users and licenses as standalone entities.
In the users_has_licenses cross-reference table, rows are unique across three fields: user_id, license_id, and issued. issued is a datetime field representing the issue date or start date for the license. Since a user can renew licenses more than once, and you require a history to be kept of the license renewals, we include this field as part of the composite primary key.
You can do without an expired field because you can already tell whether or not it's expired from the data itself, and you won't have to do routine updates to keep the data up to date. For example, to find a user's expired licenses, you can just execute the following query:
SELECT
a.license_id
FROM
(
SELECT user_id, license_id, MAX(issued) AS issued
FROM users_has_licenses
WHERE user_id = <user_id here>
GROUP BY user_id, license_id
) a
INNER JOIN
users_has_licenses b ON
a.user_id = b.user_id AND
a.license_id = b.license_id AND
a.issued = b.issued AND
b.expire_date < NOW()
It's a bit more hectic than you might expect, but only because you are retaining the data for past license renewals. In that case, you must do a group-wise maximum to make sure you're getting the most recent license period. The first sub-select figures out the most recent of each of a particular user's licenses, and then joins on the condition that the expire_date has already passed.
If you want to get all of the user's licenses (most recent period) whether or not they're expired and still want to tell whether they are expired or not, just change the INNER JOIN to a LEFT JOIN, and all non-expired licenses will have NULL values for the joined table.
To renew a user's license, you just need one INSERT rather than an INSERT and an UPDATE such as would be the case with your current design:
INSERT INTO users_has_licenses VALUES (<user_id here>, <license_id here>, NOW(), NOW() + INTERVAL 4 YEAR)
EDIT: Addressing the asker's comment made to this answer:
I have one more question in regards to renewing the licence. If the
user renewed their licence 6months into their first licence, how would
you include that extra time in the INSERT INTO statement you listed
above? I think the last value would need to be now() +remaining time +
expiry period (or something like that). Unless I've misunderstood
something. I'm just not sure how to get the remaining time.
Assuming you have chosen not to retain the history of past license periods, you can just do an UPDATE to the current record:
UPDATE users_has_licenses a SET
a.issued = NOW(),
a.expire_date = (
SELECT
NOW()
+ INTERVAL CASE WHEN DATEDIFF(expire_date, NOW()) < 0 THEN 0 ELSE DATEDIFF(expire_date, NOW()) END DAY
+ INTERVAL 4 YEAR
FROM
users_has_licenses
WHERE
user_id = a.user_id AND
license_id = a.license_id
)
WHERE
a.user_id = <user_id here> AND
a.license_id = <license_id here>
That will update issued to the current date and the expire_date to the current date + the remaining days of the old period + the regular expiry period(whatever it may be). If the past license has already expired, then the remaining days will be negative, in which case we would just add the regular expiry period.
As a sidenote in regards to the above schema I posted to the original question, you no longer need to include issued as part of the primary key.
you are storing the same record twice. Instead you can go for a better design
table1
user-id | license | start | stop | expired | extended
table2
prim_key | user_id | extended_date | extended_date_expiry
In table1 extended column is a boolean value true or false.
If extended is true then you can search for the date the user has extended to in table2.
In table2 you can store multiple extended dates for same user and get the history also. The highest date to which it is extended would be the date of validity of that user_id.
So I have a table where I collect data for the jobs that I do. Each time I create a job I assign it a date. The problem with this is the days I don't have jobs aren't stored in the database therefore when I graph my data I never see the days that I had zero jobs.
My current query looks like this:
SELECT job_data_date, SUM(job_data_invoice_amount) as job_data_date_income
FROM job_data
WHERE job_data_date >= '2010-05-05'
GROUP BY job_data_date
ORDER BY job_data_date;
The output is:
| job_data_date | job_data_date_income |
| 2010-05-17 | 125 |
| 2010-05-18 | 190 |
| 2010-05-20 | 170 |
As you can see from the example output the 2010-05-19 would not show up in the results because it was never stored there.
Is there a way to show the dates that are missing?
Thank you,
Marat
One idea is that you could have a table with all of the dates in it that you want to show and then do an outer join with that table.
So if you had a table called alldates with one column (job_data_date):
SELECT ad.job_data_date, SUM(job_data_invoice_amount) as job_data_date_income
FROM alldates ad left outer join job_data jd on ad.job_data_date = jd.job_data_date
WHERE ad.job_data_date >= '2010-05-05'
GROUP BY ad.job_data_date
ORDER BY ad.job_data_date;
The down side is that you would need to keep this table populated with all of the dates you want to show.
There's no reasonable way to do this using pure SQL, on MySQL at least, without creating a table with every date ever devised. Your best option is to alter the application that's using the results of that query to fill in the holes itself. Rather than graphing only the values it received, construct its own set of values with a simple loop; counting up one day at a time, filling in values from the query wherever they're available.