I have an entry where users can vote once per day. I''m saving this in my database.
Now I need to check, if the user is allowed to vote on this entry again (after one day).
So far I got this:
SELECT count(*)
FROM entries e
WHERE e.voterID =1
AND e.pID =1
AND e.date < NOW( ) - INTERVAL 1
DAY
But this doesn't work to well, when in the DB there are more entries for the voter and pid. A voter can vote multiples times for the same entry.
if there are more entries for the same user and the same projects, count(*) fives me a value more then 1. etc.
How do I check if the user is allowed to vote again properly?
Thanks.
Did the present user vote for the present pID in the most recent 24 hours? If so, the votecount result in this query will be more than zero.
SELECT count(*) AS votecount
FROM entries AS e
WHERE e.voterID = 1
AND e.pID = 1
AND e.date >= NOW() - INTERVAL 1 DAY
Notice the >= comparison for the date. Your sample code says <.
You will need a compound index on (voterID, pID, date) when you need this query to run efficiently on a large table.
Related
My following query shows the date and the count of the emails found on each day (last 2 days)
My problem is that if no emails are found today, the today date will not be displayed on the output. (if yesterday has emails, it will show only 1 row with yesterday date and email).
How can I edit my query to always show 2 rows, today and yesterday, date and number of emails even zero?
SELECT maildate,
COUNT(*) AS totalEmails
FROM emails
WHERE maildate >= Date_add(Curdate(), interval - 2 DAY)
AND company_id = 1
GROUP BY DATE(maildate)
ORDER BY maildate desc
There are many tricks to creating a list of dates (or numeric sequences similarly). The one I like to use with MySQL is using #sqlvariables. I will typically start with a baseline value such as your date -2 days. I will do a cross-join to any other table in the database that has at least as many records as you expect in your output... Say 30 days, or a whole year 366 days, or longer. The inner sql variable prep will keep increasing itself by whatever increment (you could even do date ranges such as begin/end of a week, month, etc). Now you have your table of all possible dates you are looking to fill.
Now, I do a secondary query by the value -- in this case your email date and apply the group by. Using the where clause in this query will make IT faster since it can utilize the date on its query result set before returning for the LEFT-JOIN to the date range result set.
Now, your simple left-join gets both parts of all dates to be included and those corresponding counts that do exist.
Note the table alias "AnyTableWithAtLeast3RecordInIt" in the "JustDates" query could in-fact be your "emails" table. Since we don't care about any criteria except a record exists, and we are applying a limit of 30 days in my example, it will be instantaneous.
select
JustDates.DateToInclude,
coalesce( SumCnts.TotalEmails, 0 ) TotalEmails
from
( select
#myDate := DATE_ADD( #myDate, INTERVAL 1 DAY ) as DateToInclude
from
( select #myDate := Date_add(Curdate(), interval - 2 DAY) ) as SQLVars,
AnyTableWithAtLeast3RecordInIt
limit 30 ) JustDates
left join
( select
maildate,
COUNT(*) AS totalEmails
FROM
emails
WHERE
maildate >= Date_add(Curdate(), interval - 2 DAY)
AND company_id = 1
GROUP BY
DATE(maildate) ) SumCnts
ON JustDates.DateToInclude = SumCnts.MailDate
Now, judging by your query, but unclarified request... Your emails table CAN HAVE FUTURE DATES? Is that correct? Such as a Dr. Office and appointments are for the future and you want to get emails out for a given range. This is what I was inferring and hence had my limit to only go out 30 days... If you need longer, just extend the LIMIT clause.
You need a table that contains all dates in the needed range. If its only about today and yesterday, you can easily create it as a subquery (derived table).
SELECT Curdate() as maildate
UNION ALL
SELECT Curdate() - INTERVAL 1 DAY
http://rextester.com/ALH50651
Now you can LEFT JOIN your table and count the rows:
SELECT sub.maildate,
COUNT(m.maildate) AS totalEmails
FROM (
SELECT Curdate() as maildate
UNION ALL
SELECT Curdate() - INTERVAL 1 DAY
) sub
LEFT JOIN emails m
ON DATE(m.maildate) = sub.maildate
AND m.company_id = 1
GROUP BY sub.maildate
ORDER BY sub.maildate desc
I am trying to achieve the Sum of Hours spent per day, where my mysql database has multiple logging records of every activity they perform (e.g; login, view, update, logout). What i am trying to do is SELECT (all records of a particular day) and find the difference in epoch time between the first and last entry giving me the 'Time Spent' for that period. Later add it by Month etc.
Database schema;
id, userid, time, action
**I can select the first and last entry from the query below:
**
SELECT
(SELECT time FROM log WHERE time(myDate) = DATE(NOW()) ORDER BY time LIMIT 1) as 'first',
(SELECT column FROM table WHERE time(myDate) = DATE(NOW()) ORDER BY time DESC LIMIT 1) as 'last'
But i am guessing a cross join or SUM for all these have to happen. Some guidance would be much appreciated.
I have a system that allows users to assign a specific file to a past or present date. The limitations are that they may only upload one file per day per user. When the user goes to upload a file the date field must default to the current date and when that date is not available it will show the first available date in the past in DESC order. Below is the relevent field names.
file_id (INT - INDEX - AUTO INCREMENT)
user_id (INT - may index this)
upload_date (INT - stores date as a unix timestamp)
The only solution I have really found would be to build them all into an array in DESC order by date and loop through until i found an empty slot. However, I feel this could really cause speed issues if the user had the past thousand days filled. I feel like I am overlooking a simple solution.
PLEASE NOTE: For one reason or another they Date is being stored as a Unix timestamp which I understand the downsides on and I am not concerned about correcting at this time.
To get the most recent date that has not been used:
select user_id, max(date) - 1
from (select ud.*,
(select max(date) from upload_date ud2 where ud2.user_id = ud.user_id and ud2.date < ud.date
) as prevdate
from upload_date ud
) ud
where date(from_unixtime(ud.prevdate)) <> date(from_unixtime(ud.date)) - 1 or
ud.prevdate is null
group by user_id
This query first gets the previous date for any given day using a correlated subquery. It then converts the time values to dates and selects any row where the previous date has a gap. The largest of the date minus one is the date you are looking for.
This SQL is untested, so it may have syntax errors.
One way to approach this is with a classic "return missing rows" query. Basically, to get a "missing" row returned from the database, you need a way to generate the "missing" rows.
To build such a query, we can start with:
SELECT MAX(t.upload_date)
FROM mytable t
WHERE t.upload_date <= NOW()
AND t.user = 'someuser'
That gets the initial date, that we are going to work backwards from.
For the "one per day" requirement, you probably want to truncate that upload_date to midnight, at least for this query. For now, we'll assume that the expression in the SELECT list is already truncated, to illustrate the approach, without bogging down in the details of dealing with a unix timestamp.
To generate a descending list of dates, starting with that initial date retrieved by the previous query...
SELECT s.upload_date - INTERVAL n.d DAY AS available_date
FROM ( SELECT MAX(t.upload_date) AS upload_date
FROM mytable t
WHERE t.upload_date <= NOW()
AND t.user = 'someuser'
) s
CROSS
JOIN ( SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
) n
ORDER BY n.d DESC
With that result, we can use an anti-join pattern to find which dates are not already used. This is a LEFT JOIN and a predicate that throws out matching rows:
SELECT s.upload_date - INTERVAL n.d DAY AS available_date
FROM ( SELECT MAX(t.upload_date) AS upload_date
FROM mytable t
WHERE t.upload_date <= NOW()
AND t.user = 'someuser'
) s
CROSS
JOIN ( SELECT 0 AS d UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
) n
LEFT
JOIN mytable u
ON u.upload_date = s.upload_date - INTERVAL n.d DAY
AND u.user = 'someuser'
WHERE u.upload_date IS NULL
ORDER BY n.d DESC
LIMIT 1
That only looks back 9 days, to get it to look back more days, just extend the inline view aliased as n to return more consecutive integers. (There's some tricks we can play with cross joins to get a whole boatload of integers.)
All that remains is working on the "matching" criteria (which works with the MySQL DATE datatype):
ON u.upload_date = s.upload_date - INTERVAL n.d DAY
into something like this:
ON u.upload_date >= UNIX_TIMESTAMP(FROM_UNIXTIME(s.upload_date)-INTERVAL n.d+1 DAY)
AND u.upload_date < UNIX_TIMESTAMP(FROM_UNIXTIME(s.upload_date)-INTERVAL n.d DAY)
And futzing with the integer timestamp value to get a MySQL DATE out of it...
SELECT DATE(FROM_UNIXTIME(s.upload_date)) - INTERVAL n.d DAY AS available_date
I have the following tables (simplified):
hours hour_rates
- user_id - user_id
- date - date
- hours - hourly_rate
Hours table example:
1 - 2012-03-19 - 8
This means that user with id=1, at 2012-03-19 worked 8 hours in total.
The hourly rate for a person can change in time, so I have the second table:
hour_rates table Example
1 - 2011-12-01 - 20
1 - 2011-12-20 - 25
So for user with id=1, we set a hourly rate of 20$ at 2011-12-01.
We changed his hourly rate at 2011-12-20, to 25$.
What I want is, to calculate how much I have to pay for a given user (ex. id=1) for a given period (ex. 2012-01-01 -> 2012-02-01).
Can I calculate this simply mysql side?
If not, how to do it in an efficient way?
In hour_rates table you should have two dates: start_date and end_date this means from start_date to end_date the emplyer has been paid x$ per hour.
Then use the same query proposed by bpgergo modified like this:
select sum(h.hours * hr.hourly_rate) as pay
from hours h, hour_rates hr
where h.user_id = :user_id --here you will set the user id parameter
and h.user_id = hr.user_id and (h.date BETWEEN hr.start_date AND hr.end_date
and h.date between STR_TO_DATE('01,1,2012','%d,%m,%Y') and STR_TO_DATE('01,1,2011','%d,%m,%Y')
Can I calculate this simply mysql side?
Yes, this is the SQL
select sum(outer_h.hours *
(select inner_hr.hourly_rate
from hour_rates inner_hr
where inner_hr.user_id = outer_h.user_id
and inner_hr.date >= outer_h.date
order by inner_hr.date asc
limit 1)
) as pay
from hours outer_h
where outer_h.user_id = :user_id --here you will set the user id parameter
and outer_h.date between STR_TO_DATE('01,1,2012','%d,%m,%Y') and STR_TO_DATE('01,1,2011','%d,%m,%Y')
EDIT: So, sorry but what do you need 2 tables for that? They are totally equal, if every day can have its own hourly_rate, you only need to store it like this:
hours: user_id, date, hours, hourly_rate
e.g. 1 | 2012-03-19 | 8 | 25
You use
SELECT user_id, date, hours, hourly_rate FROM hours WHERE (user_id=$var_of_user_id AND date ...)
and then multiply for each row hours*hourly_rate and add it to $sum, e.g.
while {...
$sum=$sum+($row['hours']*$row['hourly_rate']);
}
what does the hourly_rate depend on? probably you don't want to have the field date in the hour_rates table.
if hourly_rate is different for each job, you want to have only one table with user_id, start_date, end_date (or hours worked) and hourly_rate.
if hourly_rate depends only on user_id, then you want to have two tables:
hours: user_id, start_date, end_date (or hours worked)
hourly_rate: user_id, hourly_rate
and join the tables on user_id. If you already have a table users, you could store the hourly_rate there too, in the second case.
Then use php to simply multiply the hours worked with the hourly rate where user_id... etc.
This query should work:
SELECT SUM( h.hours*COALESCE(hr.hourly_rate,0) ) AS salary
FROM hours h
LEFT JOIN hour_rates hr
ON h.user_id = hr.user_id
AND hr.date = ( SELECT MAX(hd.date) FROM hour_rates hd
WHERE hd.user_id = h.user_id AND hd.date <= h.date )
WHERE h.user_id = 1
AND h.date BETWEEN '2012-01-01' AND '2012-04-01'
The coalesce part is there just in case the hourly pay is not defined for a given user and date, you can put a default rate there instead of 0. Also make sure that every pair (user_id,date) in your hour_rates table is unique.
I have the following query that outputs the users that received the highest number of favorites in the past week in descending order:
SELECT COUNT(faves.user_id) AS topFaves, faves.created_on, user_name
FROM users
INNER JOIN faves ON faves.user_id= users.id
WHERE DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= created_on
GROUP BY id ORDER BY topFaves DESC
I would like to be able to extend this list to contain all users, not just from the past week, but still order them by the same criteria (the number of favorites they got in the past week and not by the number of faves they have in total).
I tried to include a subquery in the select but didnt have any luck with it.
Thanks in advance for any help
Maybe I'm missing something, but just delete this line:
WHERE DATE_SUB(CURDATE(),INTERVAL 7 DAY) <= created_on
And you'll have "all users" - still sorting by topFaves...
Also if you want to list everyone (not just 10 people), you'll need to delete:
LIMIT 10