MySql - Get next date which satisfy a condition - php

I have a mysql table called 'helper_leaves' in which user have entered their leave dates for the next 2 months.
I would like to get 2 upcoming working dates for a particular user_id which are NOT in the 'helper_leaves' table and also should be next to the current date.
**id | user_id | leave_date**
1 | 1 | 2016-07-07
2 | 1 | 2016-07-09
3 | 1 | 2016-07-15
4 | 1 | 2016-08-03
I want write a query to get next 2 working dates of user_id = 1, from now.
Please note, this table is having only the leave dates. But I want to get the next 2 availability dates .
For the sample data given, I expected to get 2016-07-14 and 2016-07-16 as the next 2 working dates because today is 2016-07-13
Note: All the dates which are not in this table is considered as a working date.
Please help!!!!

Can you please try with below query?
In that, i have just generate next 60 dates and exclude leave_date from that with limit 2.
SELECT CURDATE() + INTERVAL a + b DAY dte
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE CURDATE() + INTERVAL a + b DAY < DATE_ADD(CURDATE(),INTERVAL 60 DAY) and dte not in ( select leave_date from helper_leaves where user_id = 1)
ORDER BY a + b limit 2

For this you can use interval
For Ex:
SELECT DATE_ADD(leave_date, INTERVAL 1 DAY) AS workingDay;

Related

Select SUM "separately" for the past 30 days

I have a MySQL table which is as follows
------------------------------------------------
| id | user_id | day | value |
| INT(11) | INT(11) | INT(8) | DECIMAL(5,2) |
------------------------------------------------
Day is (Ymd) number like this: 20191012
I want to grab the SUM(value) for Today and the past 29 days separately (30 days, based on day column)
I thought of a loop like this
for($i=0;$i<=30;$i++)
{
$query = "SELECT SUM(value) FROM table WHERE day=".date('Ymd', strtotime("-{$i} days"));
}
But how can I achieve this more efficiently?
Any help is appreciated.
You could use Group By to fetch it with a single MySQL-Query
SELECT day, SUM(value) FROM table GROUP BY day WHERE day >= ".date('Ymd', strtotime("-29 days"));
It should work because your day field is a number and will increase everyday so you can just compare it with gte
Expected output will be
---------------------------
| day | SUM(value) |
| INT(8) | |
---------------------------
| 20191012 | 15.25 |
---------------------------
| 20191011 | 29.13 |
---------------------------
| ... | ... |
---------------------------
J4I: If there are days without values in your dataset, the missing "day-row" is also missing in the output so it could be that there are less than 30 result-rows.
This query will work for any version of MySQL. It creates a temporary numbers table with the numbers from 0-29, then uses that to compute a list of days from today to 29 days earlier. This is then LEFT JOINed to the data table to compute the sum per day, with 0 sums where there is no data for a given day:
SELECT d.day, COALESCE(SUM(value), 0)
FROM (SELECT DATE_FORMAT(CURDATE() -INTERVAL n10.n * 10 + n.n DAY, '%Y%m%d') AS day
FROM (SELECT 0 AS n 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
CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2) n10) d
LEFT JOIN `table` t ON t.day = d.day
GROUP BY d.day
Demo on dbfiddle

SQL - How to get newest data of same date created

I have table images with uploaded date field. How can I get the newest image if 2 or more images were uploaded in same day.
1.We check on hours, minutes or seconds.
2.If the H:M:S are same, check on the sequences of inserted ID.
ID | URL | create |
1 | 01.jpg | 2017-02-23 10:24:41 |<<same H:M:s
2 | 02.jpg | 2017-02-23 10:24:41 |<<same H:M:s
3 | 03.jpg | 2017-02-23 10:50:00 |<<same H
4 | 04.jpg | 2017-02-24 21:50:00 |<<others
5 | 05.jpg | 2017-03-28 17:50:00 |<<others
Output: I want to get only
3 | 03.jpg | 2017-02-23 10:50:00 |<< newer than 1, 2
4 | 04.jpg | 2017-02-24 21:50:00 |
5 | 05.jpg | 2017-03-28 17:50:00 |
To get all the rows with the max timestamp on a given day, use
select created,max(id) maxid
from (select t1.*,(select count(distinct created) from t
where created >=t1.created
and cast(created as date)=cast(t1.created as date)) rn
from t t1) x
where rn=1
If there can be ties on the latest timestamp and only the latest id is needed in that case, the above query can be extended to the following.
select y.maxid id,t.url,t.created
from (
select created,max(id) maxid
from (select t1.*,(select count(distinct created) from t
where created >=t1.created
and cast(created as date)=cast(t1.created as date)) rn
from t t1) x
where rn=1
group by created
) y
join t on t.id=y.maxid and t.created=y.created
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT MAX(create) AS latest, MAX(ID) AS maxID
FROM yourTable
GROUP BY DATE(create)
) t2
ON t1.create = t2.latest AND
t1.ID = t2.maxID
How about using an ORDER BY and a LIMIT?
SELECT *
FROM Images
ORDER BY create DESC, ID DESC
LIMIT 1
The ORDER by sorts the images by create, then ID. The limit selects only 1. This should select only the newest photo.

How to compute statistics with date intervals via MySQL

I want to compute some stats (with a MySQL backend) sorted by date and with dynamic intervals (week, month, year).
Here is a little example :
Mysql table :
tracker_click
| ID | SITE_ID | CREATED_AT |
| ---- |---------| --------------------|
| 153 | 2 | 2013-07-22 15:43:25 |
| 154 | 2 | 2013-07-25 16:45:46 |
| 2501 | 2 | 2013-09-15 17:45:48 |
I want to get the total click number by SITE_ID by week for the last month with one query
And the same thing by month for the last year.
An example of what I want by week for the last month is :
| click number | SITE_ID | BEGIN_DATE | END_DATE |
|----------------|---------|----------------------|---------------------|
| 25 | 2 | 2013-07-01 00:00:00 | 2013-07-08 00:00:00 |
| 19 | 2 | 2013-08-09 00:00:00 | 2013-08-16 00:00:00 |
| 53 | 2 | 2013-0717- 00:00:00 | 2013-08-24 00:00:00 |
I don’t know if there is a solution to get exaclty this array with only one query without any other processes.
Thank you
This should get you the counts for the last month (ie, last 4 weeks), including weeks where the count is 0 for each site id. If you have a table of sites to get the site id from it means the cross join to the sub query can be replaced with a simple cross join to a table.
This generates a range of numbers from 0 to 5 and subtracts that number of weeks from the current date, formats that to give the Sunday and Saturday of the resulting week and checks that the resuling week is a week between the current date and the current date minus 1 month (done this way rather that just subtracting 4 weeks to cope with variable length months).
SELECT Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Sunday 00:00:00'), '%X%V %W %H:%i:%s') AS aWeek_start,
STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Saturday 23:59:59'), '%X%V %W %H:%i:%s') AS aWeek_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5)units
WHERE DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U') BETWEEN DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y%U') AND DATE_FORMAT(NOW(), '%Y%U')
) Weeks
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Weeks.aWeek_start AND Weeks.aWeek_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id
A similar query could be done for months of the year
SELECT Months.aMonth_start, Months.aMonth_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i MONTH), '%Y/%m/01 00:00:00') AS aMonth_start,
DATE_FORMAT(LAST_DAY(DATE_SUB(NOW(), INTERVAL units.i MONTH)), '%Y/%m/%d 23:59:59') AS aMonth_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11)units
) Months
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Months.aMonth_start AND Months.aMonth_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Months.aMonth_start, Months.aMonth_end, all_site_id.site_id
SELECT count(*), site_id, extract(WEEK from created_at) as start_date,
date_add(extract(WEEK from created_at), 1, week) as end_date
FROM click_tracker
GROUP BY site_id, extract(WEEK from created_at)
and you can add where clause to filter results additionally.

calculate sales per day in MySQL

I have a simple table which holds the date and the total sales made for a certain group:
date | totalsales
=======================
2014-05-01 | 3000
2014-05-02 | 3100
2014-05-03 | 3500
2014-05-04 | 3650
I like to calculate some things like:
sales per day
average sales
growth in %
Result should look like (calculate by hand so maybe wrong :) )
date | sales | average | growth
=======================================
2014-05-01 | 0 | 0 | 0
2014-05-02 | 100 | 50 | 100
2014-05-03 | 400 | 166.66 | 400
2014-05-04 | 150 | 162.5 | 37.5
Is this even possible in a sql statement or should I calculate with PHP or another server software?
Assuming each date gets its own unique row, you could do it by joining back to your original table like so:
SELECT t1.Date, CASE WHEN t2.Date IS NULL THEN 0 ELSE (t1.totalsales - t2.totalsales)
END AS sales
FROM table t1
LEFT JOIN table t2 ON t2.Date = DATE_ADD(t1.Date, INTERVAL -1 DAY)
ORDER BY 1
This will give you at least your first column, and you should be able to figure out the math for the rest from there. It's important to use a left join with the CASE statement here because otherwise you won't get the lowest date in your table (your first row)
If each date does not get its own unique row, this is method is still viable, you just need to create your datasets in a subquery using GROUP BY and SUM on the date column.
Here is the full query with no subselect at each row : (Thanks to #nmarsh for writting the hardest part)
See SQL fiddle : http://sqlfiddle.com/#!2/be4654/34/0
SELECT
t1.Date,
CASE
WHEN t2.date IS NULL THEN 0 ELSE (t1.totalSales - t2.totalSales)
END AS sales,
CASE
WHEN t2.date IS NULL THEN 0 / (#curRow := #curRow + 1) ELSE ((#curSum := #curSum + (t1.totalSales - t2.totalSales)) / (#curRow := #curRow + 1))
END AS average,
CASE
WHEN t3.date IS NULL AND t2.date IS NULL THEN 0
WHEN t3.date IS NULL THEN (t1.totalSales - t2.totalSales)
WHEN t2.date IS NULL THEN 0 ELSE ((t1.totalSales - t2.totalSales) * 100) / (t2.totalSales - t3.totalSales)
END AS growth
FROM test t1
LEFT JOIN test t2 ON t2.date = DATE_ADD(t1.Date, INTERVAL -1 DAY)
LEFT JOIN test t3 ON t3.date = DATE_ADD(t2.Date, INTERVAL -1 DAY)
JOIN (SELECT #curRow := 0) r
JOIN (SELECT #curSum := 0) ct
ORDER BY 1;
Original table :
date | totalsales
=======================
2014-05-01 | 3000
2014-05-02 | 3100
2014-05-03 | 3500
2014-05-04 | 3650
OUTPUT
date | sales | average | growth
=======================================
2014-05-01 | 0 | 0 | 0
2014-05-02 | 100 | 50 | 100
2014-05-03 | 400 | 166.66 | 400
2014-05-04 | 150 | 162.5 | 37.5
You can use recursive statement. In each iteration, calculate the requested data for one day, and drop the first (oldest) day.
You can also do it with PHP, which seems better because you don't want to put too much load on the MySQL tables in case it does not saves you time/calculations.
Not too sure about the numbers, but if you are more specific about the results I can double check.
You can use ROW_NUMBER() to create 2 data sets and join them on ROW_NUMBER() and ROW_NUMBER()-1 to get an offset to current and previous values to calculate the growth. Sample :
DECLARE #Data TABLE (SalesDate DATETIME, totalSales INT)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-01' , 3000)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-02' , 3100)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-03' , 3500)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-04' , 3650)
SELECT
CurrentDt.SalesDate
,ISNULL(CurrentDt.totalSales - PreviousDt.totalSales ,0) AS Sales
,FirstDate.FirstDate
, NULLIF(CAST((CurrentDt.SalesDate - FirstDate.FirstDate) AS INT)+1,0) AS SellingDays
,(ISNULL(CurrentDt.totalSales - PreviousDt.totalSales ,0))
/ NULLIF(CAST((CurrentDt.SalesDate - FirstDate.FirstDate) AS INT)+1,0) AS AverageSales
FROM
(SELECT Min(SalesDate) AS FirstDate FROM #Data) AS FirstDate,
/*Base Sales Data*/
(
SELECT
ROW_NUMBER() OVER(ORDER BY SalesDate) AS RowNum
,SalesDate
,totalSales
FROM
#Data
) AS CurrentDt
/*Previous Value for Growth*/
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER(ORDER BY SalesDate) AS RowNum
,SalesDate
,totalSales
FROM
#Data
) AS PreviousDt
ON CurrentDt.RowNum -1 = PreviousDt.RowNum
I have used MSSQL, but MySQL supports ROW_NUMBER OVER.
I hope this query help you
SELECT
sample.id,
sample.date AS oggi,
sample.value AS sales,
((SELECT SUM(sample.value) FROM sample WHERE sample.date <= oggi ) / (SELECT COUNT(sample.value) FROM sample WHERE sample.date <= oggi ) ) AS avarege,
sample.value / IF((SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY )) = 0,sample.value,(SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY ))) *100 AS 'growt-percent'
-- (SELECT SUM(sample.value) FROM sample WHERE sample.date <= oggi ) AS somma,
-- (SELECT count(sample.value) FROM sample WHERE sample.date <= oggi ) AS conta,
-- (SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY )) as valoreieri,
FROM sample
WHERE sample.date BETWEEN '2014-05-01 00:00:00' AND '2014-05-31 00:00:00'
table data is
id date value
1 2014-05-01 00:00:00 0
2 2014-05-02 00:00:00 100
3 2014-05-03 00:00:00 400
4 2014-05-04 00:00:00 150
5 2014-05-05 00:00:00 200
result is
id oggi sales avarege growt-percent
1 2014-05-01 00:00:00 0 0.0000 (NULL)
2 2014-05-02 00:00:00 100 50.0000 100.0000
3 2014-05-03 00:00:00 400 166.6667 400.0000
4 2014-05-04 00:00:00 150 162.5000 37.5000
5 2014-05-05 00:00:00 200 170.0000 133.3333
note that i use datetime field not only date
if you have question about query ask
sorry for my bad english
edit
the last 3 rows are commented because i used it only for test

MySQL, sum up multiple rows and rank based on most votes

I have the following database
id rank1 rank2 rank3 rank4
1 5 4 8 9
2 5 8 9 4
3 8 5 3 1
4 5 8 2 1
5 8 5 3 1
6 5 8 3 1
i need a mysql query or php script that will tally up the ranks and display the top 4 based on the number of times it appears in the table... ie. the end result should look something like:
rank1 = 5
rank2 = 8
rank3 = 3
rank4 = 1
any ideas??? thanks in advance
Your table design is far from optimal, if you didn't think it was before you will definitely see it after realizing that the way to get the result you are after requires this "not that pretty" query, though it works.
SELECT name, rank FROM (
(
SELECT 'rank1' name, rank1 rank
FROM foobar GROUP BY rank1
ORDER BY count(*) DESC LIMIT 1
) rank1_foobar
)
UNION SELECT name, rank FROM (
(
SELECT 'rank2' name, rank2 rank
FROM foobar GROUP BY rank2
ORDER BY count(*) DESC LIMIT 1
) rank2_foobar
)
UNION SELECT name, rank FROM (
(
SELECT 'rank3' name, rank3 rank
FROM foobar GROUP BY rank3
ORDER BY count(*) DESC LIMIT 1
) rank3_foobar
)
UNION SELECT name, rank FROM (
(
SELECT 'rank4' name, rank4 rank
FROM foobar GROUP BY rank4
ORDER BY count(*) DESC LIMIT 1
) rank4_foobar
)
output
+-------+------+
| name | rank |
+-------+------+
| rank1 | 5 |
| rank2 | 8 |
| rank3 | 3 |
| rank4 | 1 |
+-------+------+
I would restructure your table into something as the below, that'd make it much easier to write queries as the one you've requested.
CREATE TABLE ranks (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
group_id INT UNSIGNED NOT NULL COMMENT 'to be able to group more than one row in `ranks` together',
rank_type ENUM('rank1','rank2','rank3','rank4'),
rank_value INT,
PRIMARY KEY(`id`)
);
Because of the poor data normalization, its not quite as simple as a single select/from group by. You need to query each "Rank" column as part of a union, THEN roll that up. To keep the interim temp summations down, we can still pre-group the counts so you are not running ALL rows 4 times, but the pre-roll-ups 1 per rank in the respective group segment
select
PreAgg.Rank,
SUM( PreAgg.RankCount ) as TotalCount
from
( select
YT.Rank1 as Rank,
COUNT(*) as RankCount
from
YourTable YT
group by
YT.Rank1
UNION ALL
select
YT.Rank2 as Rank,
COUNT(*) as RankCount
from
YourTable YT
group by
YT.Rank2
UNION ALL
select
YT.Rank3 as Rank,
COUNT(*) as RankCount
from
YourTable YT
group by
YT.Rank3
UNION ALL
select
YT.Rank4 as Rank,
COUNT(*) as RankCount
from
YourTable YT
group by
YT.Rank4 ) PreAgg
GROUP BY
PreAgg.Rank,
SUM( PreAgg.RankCount ) DESC
As pointed out by Ajreal, and it would need more clarification to the structure... Is there a reason why you have 4 distinct columns that are all "Rank" instead of a more normalized table something like..
ID RankGroup Rank
1 1 5
2 1 5
3 1 8
4 1 5
5 1 8
6 1 5
7 2 4
7 2 8
7 2 5
7 2 8
7 2 5
7 2 8
etc for ranks 3 and 4
Then you could just get your counts per RANK regardless of the "group level" condition, or get best ranking per group in very simplified query.

Categories