Hi I have a database with a column containing email addresses and a second column containing category and a third column containing date.
What I want to do is count the number of unique email addresses in category 'A' between multiple date ranges. So I have this:
SELECT COUNT(DISTINCT email) as counter
FROM table
WHERE category = "A" AND date < "2015-12" AND date > "2015-11";
Then I'll do a separate query for the second date range.
NOW HERE'S MY PROBLEM:
If an email address appears in month one, and also in month two it will go on the count for both months because it's unique within the range I'm querying.
How do I create a query that will count the unique email addresses for a year let's say, then count the distinct entries in a month period without including the duplicates?
Thanks!
If you want custom date you can set the value to vars
$custom_date_begin = "2015-01-10";
$custom_date_end = "2015-02-10";
then for vars and the month group by you can use somethings like this
"SELECT COUNT(DISTINCT email) as counter
FROM table
WHERE category = 'A'
AND date >= '$custom_begin_date' AND date <='$custom_end_date'
group by MONTH (date);"
Information provided is not enough for writing adequate query. So I'll try to guess details.
Lets assume that we need to count number of unique emails for each month and category.
The query could be like the following:
SELECT dt, category, COUNT(*) AS cnt
FROM (
SELECT LEFT(`date`,7) AS dt, category, email
FROM table
GROUP BY LEFT(`date`,7), category, email
) x
GROUP BY dt, category
If you have variable date ranges, then you'd better group on daily basis, and then count emails via script for each date range.
I have a question about MySQL. I have a Table with this fields:
WorkerName
Date
HoursWorked
Ok, if I do this Query:
SELECT WorkerName, Date, HoursWorked, SUM(HoursWorked) FROM myTable GROUP BY WorkerName
I have the field grouped by the Worker Name BUT with a only row. I want to show all days worked by this Worker in the same row, and the other Worker in another row.
In PHP actually have a While that shows all days worked, but only shows the Hours of the first day sorted.
You can use GROUP_CONCAT aggregate function:
SELECT
WorkerName,
GROUP_CONCAT(Date) AS dates_worked,
SUM(HoursWorked)
FROM
myTable
GROUP BY
WorkerName
I have two tables in my database:
tickets
ticket_updates
each table has a column called ticketnumber which match. there are sometimes multiple rows in ticket_updates where there is only one row in tickets
I want to be able to show the number of rows from tickets where status = 'Completed' but where it has been completed TODAY
for each row in the ticket_updates table there is a datetime column
As there are multiple rows in ticket_updates for each 1 row in tickets it will need to select the latest datetime from ticket_updates too
You should be able to do this with a simple join and a little MySQL date function:
select
count(sub.counter)
from
(
select distinct
ti.ticketnumber as counter
from
tickets ti
join ticket_updates tu
on ti.ticketnumber=tu.ticketnumber
where
ti.status='Completed'
and date(tu.datetime)=curdate()
) sub
If your datetime (Assuming that isn't the actual name) contains date AND time information, you will need to strip out the time component to compare it properly to the value returned by curdate() which is just a date of today.
The MySQL date() function returns just the date component of a date and time.
Edit: Corrected code to account for multiple relationship as correctly pointed out by #mituw16
I am going to assume there is a key linking these two tables. You might try something like this...
select count(*) as TicketCount from tickets
join ticket_updates on ticket_updates.ticketnumber = tickets.ticketnumber
where tickets.status='Completed' and ticket_updates.datetime = CURDATE()
group by ticket_updates.ticketnumber
I'm working with some imported data that stores details about whether a "room" is available on a specific day or not. Each room has an individual entry for the date that it is available.
| id | date | price |
--------------------------------
| 1 | 2010-08-04 | 45.00 |
A user can search across a date range and the search needs to bring back the relevant rooms that are available between those two dates.
In other words using a sql query to search:
where date>=2010-08-04 AND date<=2010-08-09
would not suffice as this would bring back all rooms available at SOME point between the chosen dates not the rooms that are available for ALL of the dates concerned.
I am considering using a temporary date table in some way to cross-reference that there is an entry for every date in the range but are uncertain as to the best way to implement this.
The end code platform is PHP and I'm also exploring whether the data can be processed subsequently within the code but would like to keep everything with the sql if possible.
Any suggestions that put forward would be greatly appreciated.
Thanks
Update: my original answer was identical to Quassnoi's but 1 minute too late, so I decided to delete it and do something different instead. This query does not assume that (id, date) is unique. If there is more than one entry, it selects the cheapest. Also, it also sums the total cost and returns that too which might also be useful.
SELECT id, SUM(price) FROM (
SELECT id, date, MIN(price) AS price
FROM Table1
GROUP BY id, date) AS T1
WHERE `date` BETWEEN '2010-08-05' AND '2010-08-07'
GROUP BY id
HAVING COUNT(*) = DATEDIFF('2010-08-07','2010-08-05') + 1
Provided that (id, date) combination is unique:
SELECT id
FROM mytable
WHERE date BETWEEN '2010-08-04' AND '2010-08-09'
GROUP BY
id
HAVING COUNT(*) = DATEDIFF('2010-08-09', '2010-08-04') + 1
Make sure you have a UNIQUE constraint on (id, date) and the date is stored as DATE, not DATETIME.
I am currently working on a leave application (which is a subset of my e-scheduler project) and I have my database design as follows:
event (event_id, dtstart, dtend... *follows icalendar standard*)
event_leave (event_id*, leave_type_id*, total_days)
_leave_type (leave_type_id, name, max_carry_forward)
_leave_allocation (leave_allocation_id, leave_type_id*, name, user_group_id, total_days, year)
_leave_carry_forward(leave_carry_forward_id, leave_type_id*, user_id, year)
Does anyone here in stackoverflow also working on an e-leave app? mind to share your database design as I am looking for a better design than mine. The problem with my current design only occurs at the beginning of the year when the system is calculating the number of days that can be carried forward.
In total I would have to run 1 + {$number_of users} * 2 queries (the first one to find out the number of allocation rules and the maximum carry forward quota. Then for each user, I need to find out the balance, and then to insert the balance to the database)
I'm not following the schema very well (it looks like each leave_type would have a carry forward? There's no user on the event* tables?) but you should be able to dynamically derive the balance at any point in time - including across years.
AAMOF, normalization rules would require you to be able to derive the balance. If you then chose to denormalize for performance is up to you, but the design should support the calculated query. Given that, then calculating the year end carryforward is a single set based query.
Edit: I had to change the schema a bit to accommodate this, and I chose to normalize to make the logic easier - but you can insert denormalization along the way for performance if you need to:
First the tables that are important for this scenario...hopefully my pseudo-syntax will make sense:
User { User_Id (PK) }
// Year may be a tricky business logic issue here...Do you charge the Start or End year
// if the event crosses a year boundary? Or do you just do 2 different events?
// You want year in this table, though, so you can do a FK reference to Leave_Allocation
// Some RDBMS will let you do a FK from a View, though, so you could do that
Event { Event_Id (PK), User_Id, Leave_Type_Id, Year, DtStart, DtEnd, ...
// Ensure that events are charged to leave the user has
FK (User_Id, Leave_Type_Id, Year)->Leave_Allocation(User_Id, Leave_Type_Id, Year)
}
Leave_Type { Leave_Type_Id, Year, Max_Carry_Forward
// Max_Carry_Forward would probably change per year
PK (Leave_Type_Id, Year)
}
// Starting balance for each leave_type and user, per year
// Not sure the name makes the most sense - I think of Allocated as used leave,
// so I'd probably call this Leave_Starting_Balance or something
Leave_Allocation { Leave_Type_Id (FK->Leave_Type.Leave_Type_Id), User_Id (FK->User.User_Id), Year, Total_Days
PK (Leave_Type_Id, User_Id, Year)
// Ensure that leave_type is defined for this year
FK (Leave_Type_Id, Year)->Leave_Type(Leave_Type_Id, Year)
}
And then, the views (which is where you may want to apply some denormalization):
/* Just sum up the Total_Days for an event to make some other calcs easier */
CREATE VIEW Event_Leave AS
SELECT
Event_Id,
User_Id,
Leave_Type_Id,
DATEDIFF(d, DtEnd, DtStart) as Total_Days,
Year
FROM Event
/* Subtract sum of allocated leave (Event_Leave.Total_Days) from starting balance (Leave_Allocation) */
/* to get the current unused balance of leave */
CREATE VIEW Leave_Current_Balance AS
SELECT
Leave_Allocation.User_Id,
Leave_Allocation.Leave_Type_Id,
Leave_Allocation.Year,
Leave_Allocation.Total_Days - SUM(Event_Leave.Total_Days) as Leave_Balance
FROM Leave_Allocation
LEFT OUTER JOIN Event_Leave ON
Leave_Allocation.User_Id = Event_Leave.User_Id
AND Leave_Allocation.Leave_Type_Id = Event_Leave.Leave_Type_Id
AND Leave_Allocation.Year = Event_Leave.Year
GROUP BY
Leave_Allocation.User_Id,
Leave_Allocation.Leave_Type_Id,
Leave_Allocation.Year,
Leave_Allocation.Total_Days
Now, our Leave CarryForward query is just the minimum of current balance or maximum carryforward as of midnight on 1/1.
SELECT
User_Id,
Leave_Type_Id,
Year,
/* This is T-SQL syntax...your RDBMS may be different, but should be able to do the same thing */
/* If not, you'd do a UNION ALL to Max_Carry_Forward and select MIN(BalanceOrMax) */
CASE
WHEN Leave_Balance < Max_Carry_Forward
THEN Leave_Balance
ELSE
Max_Carry_Forward
END as Leave_Carry_Forward
FROM Leave_Current_Balance
JOIN Leave_Type ON
Leave_Current_Balance.Leave_Type_Id = Leave_Type.Leave_Type_Id
/* This assumes max_carry_forward is how much you can carry_forward into the next year */
/* eg,, a max_carry_forward of 300 hours for year 2008, means I can carry_forward up to 300 */
/* hours into 2009. Otherwise, you'd join on Leave_Current_Balance.Year + 1 if it's how much */
/* I can carry forward into *this* year. */
AND Leave_Current_Balance.Year = Leave_Type.Year
So, at the end of the year, you'd insert the CarryForward balances back into LeaveAllocation with the new year.
There is always a better design!!
Does your current design work? How many users do you expect (ie does it matter you would have to run x thousand queries).
If the problem of the current design is only at the beginning of the year then perhaps you could live with it!
Cheers
NZS
Further notes on my database design and some use cases.
Table Design
This is the main table (basically based on iCalendar schema) that stores event. The event may be a typical event, or a meeting, public holiday etc.
event (event_id (PK), dtstart, dtend, ... --other icalendar fields--)
If a particular type of event has extra information that I have to keep track, I decorate it with another table. For instance, the table to store e-leave specific information. (total_days is not a computed field as part of the requirements)
event_leave (event_id (PK/FK->event), total_days, leave_type_id (FK->leave_type))
Leave type table stores some information on each leave type. For instance, does the application needs approval/recommendation etc. Besides that, it also stores the maximum carry forward allowed. I assume the maximum carry forward would not be altered frequently.
leave_type (leave_type_id (PK), name, require_support, require_recommend, max_carry_forward)
Users are divided into groups, and each group will be given a number of days available for leave for some of the leave_type. Data stored in this table will be populated annually (a new revision for each year). It only stores the number of leave given for each group, not per user.
leave_allocation (leave_allocation_id, year(PK), leave_type_id (PK/FK->leave_type), total_days, group_id)
Next is the table to store carry forward information. This table will be populated once every year for each user. This table will be populated once a year as calculation on the fly is not easy. The formula of counting leave_carry_forward for the user is:
leave_carry_forward(2009) = min(leave_allocation(2008) + leave_carry_forward(2007) - leave_taken(2008), maximum_carry_forward());
leave_carry_forward (leave_carry_forward_id, user_id, year, total_days)
Some Example Use Cases and Solution
Calculate Balance (WIP)
To calculate balance, I make a query to the view declared as follows
DROP VIEW IF EXISTS leave_remaining_days;
CREATE OR REPLACE VIEW leave_remaining_days AS
SELECT year, user_id, leave_type_id, SUM(total_days) as total_days
FROM (
SELECT allocated.year, usr.uid AS "user_id", allocated.leave_type_id,
allocated.total_days
FROM users usr
JOIN app_event._leave_allocation allocated
ON allocated.group_id = usr.group_id
UNION
SELECT EXTRACT(year FROM event.dtstart) AS "year", event.user_id,
leave.leave_type_id, leave.total_days * -1 AS total_days
FROM app_event.event event
LEFT JOIN app_event.event_leave leave
ON event.event_id = leave.event_id
UNION
SELECT year, user_id, leave_type_id, total_days
FROM app_event._leave_carry_forward
) KKR
GROUP BY year, user_id, leave_type_id;
Populate leave_allocation table at the beginning of year
public function populate_allocation($year) {
return $this->db->query(sprintf(
'INSERT INTO %s (%s)' .
"SELECT '%s' AS year, %s " .
'FROM %s ' .
'WHERE "year" = %s',
'event_allocation',
'year, leave_type_id, total_days ...', //(all the fields in the table)
empty($year) ? date('Y') : $year,
'leave_type_id, total_days, ..', //(all fields except year)
$this->__table,
empty($year) ? date('Y') - 1 : $year - 1
))
->count() > 0; // using the database query builder in Kohana PHP framework
}
Populate leave_carry_forward table at the beginning of year
Find out leave type assigned to the user
I would probably need to rename this view (I am bad in naming stuff...). It is actually a leave_allocation table for a user.
DROP VIEW IF EXISTS user_leave_type;
CREATE OR REPLACE VIEW user_leave_type AS
SELECT la.year, usr.uid AS user_id, lt.leave_type_id, lt.max_carry_forward
FROM users usr
JOIN app_event._leave_allocation la
JOIN app_event._leave_type lt
ON la.leave_type_id = lt.leave_type_id
ON usr.group_id = la.group_id
The actual query
INSERT INTO leave_carry_forward (year, user_id, leave_type_id, total_days)
SELECT '{$this_year}' AS year, user_id, leave_type_id, MIN(carry_forward) AS total_days
FROM (
SELECT year, user_id, leave_type_id, total_days AS carry_forward
FROM leave_remaining_days
UNION
SELECT year, user_id, leave_type_id, max_carry_forward AS carry_forward
FROM user_leave_type
) KKR
WHERE year = {$last_year}
GROUP BY year, user_id, leave_type_id;