Traffic Light system for warranties in a MYSQL Database - mysql

I have a table of printers with a warranty start and warrant end date stored as a date field in our MYSQL database
I want to visualise the warranty end dates using the current date as a traffic lights system
Red - warranties ended
Amber - warranties ending within 90 days
Green - warranties that end in more than 90 days
Example data
+--------------------------------------+-----------------+---------------+
| id | warrantystart_c | warrantyend_c |
+--------------------------------------+-----------------+---------------+
| 001c28c4-b9cf-11e9-88a2-020683bef092 | 2018-07-13 | 2020-07-12 |
| 453b9ba2-b9ce-11e9-8fd6-0265ed7510c2 | 2018-09-26 | 2020-09-25 |
| 5c8353e2-0585-11ea-bbe5-066c2def7d44 | 2018-09-26 | 2020-09-25 |
| 71F1934 | 2018-08-28 | 2020-08-27 |
| 71F1936 | 2018-08-28 | 2020-08-27 |
| 71F2367 | 2018-09-03 | 2020-09-02 |
| 001c28c4-b9cf-11e9-88a2-020683bef092 | 2018-07-13 | 2020-07-12 |
| 0024d2ca-fb2f-11e9-8fcc-0265ed7510c2 | 2019-08-20 | 2022-08-19 |
| 00514c1c-6a5a-11e9-9026-0265ed7510c2 | 2019-04-18 | 2022-04-17 |
| 00613772-bdc4-11e9-8c8f-0265ed7510c2 | 2019-07-01 | 2022-06-30 |
| 006ac524-ef5d-11e9-b323-020683bef092 | 2019-09-30 | 2022-09-29 |
| 006b3ea2-e911-11e9-8ab6-0265ed7510c2 | 2019-10-07 | 2022-10-06 |
| 008fb146-f501-11e9-9875-06a63d65978a | 2018-02-14 | 2021-02-13 |
| 0130078c-d2ff-11e9-96e7-06a63d65978a | 2019-08-23 | 2022-08-22 |
| 0180c41e-bf5a-11e9-8abf-06a63d65978a | 2019-07-25 | 2022-07-24 |
| 0279f4ce-bdc2-11e9-ac0d-020683bef092 | 2019-07-01 | 2022-06-30 |
| 002f826c-c7cf-11e9-9104-0265ed7510c2 | NULL | 1969-12-31 |
| 031b46b4-113f-11ea-80e4-020683bef092 | 2014-07-10 | 2017-07-09 |
| 0af91f74-52da-11e9-947b-0265ed7510c2 | 2019-04-01 | 2019-04-30 |
| 0c89a096-f5bb-11e9-9313-066c2def7d44 | 1900-01-02 | 1905-01-01 |
+--------------------------------------+-----------------+---------------+
from this table the result I'm looking to get is
+-----+-------+-------+--+
| Red | amber | green | |
+-----+-------+-------+--+
| 4 | 6 | 10 | |
+-----+-------+-------+--+
I have tried three WHERE clauses to get this data
snc.warrantyend_c < current_date // Red
snc.warrantyend_c BETWEEN CURDATE() and DATE_ADD(CURDATE(), INTERVAL 90 DAY) // Amber
snc.warrantyend_c > subdate(current_date, INTERVAL 90 DAY) // Green
I just wandered if there's a better way to do the where clauses and an efficient way to do this all in one query or should I use sub queries?

You can aggregate and sum() the expressions for the different colors. This works as Boolean expressions in numeric context are interpreted as 1 for true and 0 for false in MySQL.
SELECT sum(snc.warrantyend_c < current_date) red,
sum(snc.warrantyend_c BETWEEN current_date and date_add(current_date, INTERVAL 90 DAY)) amber,
sum(snc.warrantyend_c > subdate(current_date, INTERVAL 90 DAY)) green
FROM elbat snc;

You're correct that your existing individual queries will get you the data you need:
SELECT COUNT(*) as red FROM warrantiesWHERE end < current_date;
SELECT COUNT(*) as yellow FROM warranties WHERE end BETWEEN CURDATE() and DATE_ADD(CURDATE(), INTERVAL 90 DAY);
SELECT COUNT(*) as green FROM warrantiesWHERE end > subdate(current_date, INTERVAL 90 DAY);
But these can be combined with a CASE statement as a separate column:
SELECT
*,
CASE
WHEN end < current_date THEN 'red'
WHEN end BETWEEN CURDATE() and DATE_ADD(CURDATE(), INTERVAL 90 DAY) THEN 'yellow'
WHEN end > subdate(current_date, INTERVAL 90 DAY) THEN 'green'
END as color
FROM warranties;
And then finally you can use GROUP BY on this query to get the counts:
SELECT
color,
COUNT(*) as count
FROM (
SELECT
*,
CASE
WHEN end < current_date THEN 'red'
WHEN end BETWEEN CURDATE() and DATE_ADD(CURDATE(), INTERVAL 90 DAY) THEN 'yellow'
WHEN end > subdate(current_date, INTERVAL 90 DAY) THEN 'green'
END as color
FROM warranties
) colors
GROUP BY color;
Working example at http://sqlfiddle.com/#!9/aed6cd/2

Thanks for you help and I'm working through some lessons to learn more about the points raised. I've used this to create the full statement
SELECT
color,
COUNT(*) AS count
FROM (
SELECT
sn.id,
CASE
WHEN snc.warrantyend_c < current_date THEN 'red'
WHEN snc.warrantyend_c BETWEEN CURDATE() and DATE_ADD(CURDATE(), INTERVAL 90 DAY) THEN 'amber'
WHEN snc.warrantyend_c > subdate(current_date, INTERVAL 90 DAY) THEN 'green'
END AS color
FROM
s_serialnumber sn
LEFT JOIN s_serialnumber_cstm snc ON sn.id = snc.id_c
WHERE
snc.dealer_id_c = "1000521"
) colors
GROUP BY color;

Related

MySql select 1 week & 1 Month advance date from Current date

i want to get the list of date which is going to expire in a week or a month...
MySQL, and field is TIMESTAMP.
i tried below queries...
SELECT CERT_ID, date(CERTVALIDITY)
FROM mst_CERT WHERE date(CERTVALIDITY) > now() - interval 1 week ORDER BY CERTVALIDITY;
This returns all the dates from current date
SELECT date(CERTVALIDITY) from mst_CERT where curdate() =
DATE(DATE_SUB(CERTVALIDITY, INTERVAL 1 week));
returns nothing...
SELECT date(DATE_SUB(CERTVALIDITY, INTERVAL 1 MONTH)) from mst_CERT;
returns nothing...
| CT00078 | 2020-02-28 |
| CT00098 | 2020-02-29 |
| CT00011 | 2020-03-31 |
| CT00012 | 2020-03-31 |
| CT00013 | 2020-03-31 |
| CT00014 | 2020-03-31 |
| CT00075 | 2020-03-31 |
| CT00055 | 2020-03-31 |
| CT00056 | 2021-03-31 |
This will show CERT_ID's outside of a 7 day period.
SELECT CERT_ID, date(CERTVALIDITY)
FROM mst_CERT
WHERE date(CERTVALIDITY) > (NOW() + INTERVAL 7 day)
ORDER BY date(CERTVALIDITY);
This will show you valid CERT_ID's within a 7 day period.
SELECT CERT_ID, date(CERTVALIDITY)
FROM mst_CERT
WHERE date(CERTVALIDITY) BETWEEN NOW() AND (NOW() + INTERVAL 7 day)
ORDER BY date(CERTVALIDITY);
Your first query is almost right. You made a mistake of subtracting interval of 1 week but you actually had to add interval.
SELECT CERT_ID, DATE(CERTVALIDITY)
FROM ms_CERT
WHERE DATE(CERTVALIDITY) < NOW() + INTERVAL 1 WEEK AND DATE(CERTVALIDITY) > NOW()
ORDER BY CERTVALIDITY;
Above Query will return following result
| CT00078 | 2020-02-28 |
| CT00098 | 2020-02-29 |

How to get the data per month in MySQL

I'm trying to get the data per every month.
I'm a newbie at MySQL.
I just google and learn from the internet.
So, My Query may be not suitable.
If not suitable, please fix my query.
HERE IS MY QUERY
SELECT
SUM( cr_dt > EXTRACT( YEAR_MONTH FROM CURDATE() )
AND
cr_dt < EXTRACT( YEAR_MONTH FROM CURDATE() + INTERVAL 1 MONTH )
) as thisMonth,
SUM( cr_dt > EXTRACT( YEAR_MONTH FROM CURDATE() - INTERVAL 1 MONTH
AND
cr_dt < EXTRACT( YAER_MONTH FROM CURDATE()
) as last1Month
FROM MYTABLE
I just found the EXTRACT() and INTERVAL, but I don't know it may suitable.
If you have a better way to solve, share please.
WHAT I EXPECT
| thisMonth | last1Month |
+-----------+------------+
| 123 | 1294 |
WHAT I SEE NOW
| thisMonth | last1Month |
+-----------+------------+
| 0 | 0 |
SAMPLE DATAS
| idx | cr_dt |
+-----+---------------------+
| 1 | 2018-02-01 17:00:00 |
| 2 | 2018-02-19 16:00:00 |
| 3 | 2018-02-28 15:00:00 |
| 4 | 2018-03-04 14:00:00 |
| 5 | 2018-04-01 13:00:00 |
| 6 | 2018-04-09 12:00:00 |
| 7 | 2018-04-12 11:00:00 |
| 8 | 2018-04-17 10:00:00 |
You need to use conditional aggregation here, something like this:
SELECT
COUNT(CASE WHEN cr_dt >= DATE_FORMAT(CURRENT_DATE, '%Y/%m/01')
THEN 1 END) AS thisMonth,
COUNT(CASE WHEN cr_dt >= DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y/%m/01') AND
cr_dt < DATE_FORMAT(CURRENT_DATE, '%Y/%m/01') THEN 1 END) AS last1Month
FROM MYTABLE
WHERE cr_dt >= DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y/%m/01');
More typically you might see this sort of query using a GROUP BY clause, to compute aggregates over individual groups. In your case, however, it appears you want to aggregate over the entire table.

Getting multiple maxes of summed values

Consider this sample data:
+----------+------+--------+-----------------+
| username | qnum | qvalue | date |
+----------+------+--------+-----------------+
| Linda | 1 | 2 | 11/14/2017 7:25 |
+----------+------+--------+-----------------+
| Fred | 1 | 1 | 11/23/2017 7:59 |
+----------+------+--------+-----------------+
| Brian | 5 | 2 | 11/17/2017 7:25 |
+----------+------+--------+-----------------+
| Sandra | 6 | 1 | 11/25/2017 7:26 |
+----------+------+--------+-----------------+
| Tom | 6 | 1 | 11/22/2017 7:32 |
+----------+------+--------+-----------------+
| Paul | 6 | 1 | 11/22/2017 7:36 |
+----------+------+--------+-----------------+
| Andrew | 7 | 2 | 11/23/2017 7:37 |
+----------+------+--------+-----------------+
| Luke | 3 | 1 | 11/23/2017 8:03 |
+----------+------+--------+-----------------+
| William | 8 | 1 | 11/23/2017 8:03 |
+----------+------+--------+-----------------+
| Linda | 9 | 2 | 11/15/2017 8:03 |
+----------+------+--------+-----------------+
| Brian | 3 | 2 | 11/17/2017 8:04 |
+----------+------+--------+-----------------+
| Joan | 9 | 1 | 11/23/2017 8:04 |
+----------+------+--------+-----------------+
| Chris | 8 | 1 | 11/23/2017 8:04 |
+----------+------+--------+-----------------+
| Kim | 8 | 1 | 11/15/2017 8:04 |
+----------+------+--------+-----------------+
I am attempting to get the person who has the highest sum of qvalue for last week. I am able to get this information with the following SQL but my problem is that if more than one user has the top score then it does not show both of their names because I am using the LIMIT function. Is there a way to use max and sum together to get the desired result? The desired result would be a result set with both Linda and Brian listed because last week they both had a sum score of 4 and were tied.
SELECT username, SUM(qvalue) AS score FROM trivia_scoreboard
WHERE `date` >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY AND `date` < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
GROUP BY username
ORDER BY score DESC
LIMIT 1
You have to join that query with a query that gets everyone's total.
SELECT t1.*
FROM (
SELECT username, SUM(qvalue) AS score FROM trivia_scoreboard
WHERE `date` >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY AND `date` < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
GROUP BY username
) AS t1
JOIN (
SELECT SUM(qvalue) AS score FROM trivia_scoreboard
WHERE `date` >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY AND `date` < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
GROUP BY username
ORDER BY score DESC
LIMIT 1
) AS t2 ON t1.score = t2.score
DEMO
You can also phrase this using having:
SELECT ts.username, SUM(ts.qvalue) AS score
FROM trivia_scoreboard fs
WHERE ts.`date` >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY AND
ts.`date` < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
GROUP BY username
HAVING score = (SELECT SUM(ts2.qvalue) as score
FROM trivia_scoreboard ts2
WHERE ts2.`date` >= CURDATE() - INTERVAL DAYOFWEEK(CURDATE())+6 DAY AND
ts2.`date` < CURDATE() - INTERVAL DAYOFWEEK(CURDATE())-1 DAY
GROUP BY username
ORDER BY score DESC
LIMIT 1
);
The difference between this and the version using join is really a matter of taste. This might have slightly better performance, because of details about how aggregations scale on larger amounts of data.

MYSQL select sum for existing dates and 0 for future dates

I have been able to select all of the months with values from this year and last if they exist. What I am trying to do is create a future date if nothing exists for it yet with a TotalAmount of 0. I could possibly fill this in using PHP however I am hoping MYSQL will have a simpler solution.
SELECT
DATE_FORMAT(STR_TO_DATE(Invoice.Date, '%m/%e/%Y'), '%m-%Y') AS Date_Month,
SUM(Invoice.Amount) AS TotalAmount
FROM Invoice
WHERE STR_TO_DATE(Invoice.Date, '%m/%e/%Y') BETWEEN DATE_SUB(DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY), INTERVAL 1 YEAR), INTERVAL 2 YEAR) AND NOW()
GROUP BY Date_Month
ORDER BY Date_Month
Current Output:
01-2016 | 103077
01-2017 | 104925
02-2016 | 187065
02-2017 | 32865
03-2016 | 135525
04-2016 | 106500
05-2016 | 98812
06-2016 | 111690
07-2016 | 96193
08-2016 | 123970
09-2016 | 126356
10-2016 | 147454
11-2016 | 160254
12-2016 | 98430
Expected Output:
01-2016 | 103077
01-2017 | 104925
02-2016 | 187065
02-2017 | 32865
03-2016 | 135525
03-2017 | 0
04-2016 | 106500
04-2017 | 0
05-2016 | 98812
05-2017 | 0
06-2016 | 111690
06-2017 | 0
07-2016 | 96193
07-2017 | 0
08-2016 | 123970
08-2017 | 0
09-2016 | 126356
09-2017 | 0
10-2016 | 147454
10-2017 | 0
11-2016 | 160254
11-2017 | 0
12-2016 | 98430
12-2017 | 0
You can do it with a query like this, but is not tested
SELECT
DATE_FORMAT(STR_TO_DATE(Invoice.Date, '%m/%e/%Y'), '%m-%Y') AS Date_Month,
SUM(IF(STR_TO_DATE(Invoice.Date > NOW(), 0, Invoice.Amount)) AS TotalAmount
FROM Invoice
WHERE STR_TO_DATE(Invoice.Date, '%m/%e/%Y') BETWEEN DATE_SUB(DATE_ADD(DATE_SUB(CURDATE(), INTERVAL DAYOFYEAR(CURDATE()) DAY), INTERVAL 1 YEAR), INTERVAL 2 YEAR) AND NOW()
GROUP BY Date_Month
ORDER BY Date_Month;

MySql query extract value with between syntax

These are my rows in MySql database table:
+------------+----------+-------------+
| theDate | theHour | theUserCode |
+------------+----------+-------------+
| 2015-11-16 | 06:30:00 | XX2111905 |
| 2015-11-16 | 21:30:37 | XX2112111 |
| 2015-11-16 | 22:21:29 | XX2112111 |
| 2015-11-16 | 17:15:18 | XX2142122 |
| 2015-11-16 | 04:22:13 | XX2146905 |
| 2015-11-16 | 15:15:00 | XX2146905 |
| 2015-11-16 | 21:26:00 | XX2148516 |
+------------+----------+-------------+
7 rows in set
I need extract from this table the rows with theHour between 15:00:00 and 03:00:00.
I have tried this Sql query but in output I have the Empty set.
SELECT
theDate,
theHour,
theUserCode
FROM
`tblUserRegistered`
WHERE
theDate BETWEEN DATE_SUB('2015-11-16', INTERVAL 1 DAY)
AND '2015-11-16'
AND theHour BETWEEN '15:00:00'
AND '03:00:00'
ORDER BY
theUserCode,
theDate,
theHour ASC;
Please help me, thank you so much in advance.
I used the HOUR() function in my query below to handle the check by hour. This query will include records later than 3pm (15 hours), or earlier than 3am (03 hours).
SELECT theDate, theHour, theUserCode
FROM tblUserRegistered
WHERE theDate BETWEEN DATE_SUB('2015-11-16', INTERVAL 1 DAY) AND '2015-11-16'
AND (HOUR(theHour) >= 15 OR HOUR(theHour) <= 3)
ORDER BY theUserCode, theDate, theHour ASC