Getting multiple maxes of summed values - mysql

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.

Related

Traffic Light system for warranties in a MYSQL Database

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;

Calculate the change in price by matching symbol in two tables and calculate the price difference at set interval

I have two tables, as showing below. Both tables have symbol column.
assetList table have lastPriceUSD and VWAPUSD table have vwapUSD along
with the createdAt column.
VWAPUSD table gets updated every 10mins and store prices of each
asset. I want to match the symbol in both table and then calculate the
price difference at 24h/7d/30d interval.
assetList Table
+----+--------------+--------+-----------------+------------------------+
| id | name | symbol | lastPriceUSD | circulatingSupply |
+----+--------------+--------+-----------------+------------------------+
| 1 | Bitcoin | BTC | 7293.2028511419 | 17688625.0000000000 |
| 2 | Ethereum | ETH | 234.9522240344 | 105978867.0616000000 |
| 3 | XRP | XRP | 0.3711016204 | 42004966728.0000000000 |
| 4 | Bitcoin Cash | BCHABC | 359.6217859183 | 17770489.6469232500 |
| 5 | Litecoin | LTC | 86.7230191550 | 61699595.7334713500 |
+----+--------------+--------+-----------------+------------------------+
VWAPUSD Table
+--------+--------+----------------+---------------------+
| vwapId | symbol | vwapUSD | createdAt |
+--------+--------+----------------+---------------------+
| 2 | ETH | 184.2507663730 | 2019-05-12 23:36:11 |
| 3 | LTC | 84.8173610151 | 2019-05-12 23:36:11 |
| 4 | BNB | 20.5221663735 | 2019-05-12 23:36:11 |
| 5 | NEO | 9.1588715813 | 2019-05-12 23:36:11 |
| 6 | QTUM | 2.3849431039 | 2019-05-12 23:36:11 |
+--------+--------+----------------+---------------------+
This is what I am trying
SELECT al.symbol,
al.lastPriceUSD,
al.circulatingSupply,
vw.symbol,
vw.vwapUSD,
100 * (al.lastPriceUSD - vw.vwapUSD) / al.lastPriceUSD as difference
FROM assetList al JOIN VWAPUSD vw
ON al.symbol = vw.symbol
WHERE al.symbol = vw.symbol AND
vw.createdAt >= NOW() - INTERVAL 1 DAY
ORDER BY (al.circulatingSupply * al.lastPriceUSD) DESC
Using scalar subquery:
SELECT al.symbol
, al.lastPriceUSD
, al.circulatingSupply
, ( SELECT 100 * (al.lastPriceUSD - vw.vwapUSD) / al.lastPriceUSD
FROM VWAPUSD vw
WHERE vw.symbol = al.symbol
AND vw.createdAt >= now() -interval 1 day
AND vw.createdAt < now() -interval 1 day + interval 10 minute
) diff24h
, ( SELECT 100 * (al.lastPriceUSD - vw.vwapUSD) / al.lastPriceUSD
FROM VWAPUSD vw
WHERE vw.symbol = al.symbol
AND vw.createdAt >= now() -interval 7 day
AND vw.createdAt < now() -interval 7 day + interval 10 minute
) diff7d
FROM assetList al
ORDER BY (al.circulatingSupply * al.lastPriceUSD) DESC

select item from table and order by amount on 2 days?

For example I have below table:
---------------------
| amount | date |
---------------------
| 50 | Day 1 |
| 60 | day 2 |
| 20 | day 3 |
| 25 | day 3 |
| 23 | day 4 |
| 26 | day 4 |
| 15 | day 5 |
---------------------
What I basically want to do is to retrieve item from last 2 days and order the row by max amount. so the result would be like:
---------------------
| amount | date |
---------------------
| 26 | day 4 |
| 23 | day 4 |
| 15 | day 5 |
---------------------
FYI: the date input is in 2018-06-10 14:37:44 mode above is just an example
I tried: SELECT AMOUNT FROM table WHERE AMOUNT=(SELECT MAX(AMOUNT) FROM table) ORDER BY DATE;
But the result I am getting is only | 60 | day 2 | which is the max amount and is not from 2 recent dates;
You could use the DATE_ADD function that gives you a date after a certain time alongside CURDATE() that gives you current date;
SELECT * FROM table WHERE date >= DATE_ADD(CURDATE(), INTERVAL -2 DAY)
ORDER BY amount DESC;
Try this:
SELECT amount, date FROM table WHERE date >= DATE_ADD(CURDATE(), INTERVAL -2 DAY) ORDER BY amount DESC;
-- date > DATE_ADD(CURDATE(), INTERVAL -2 DAY) will give you all rows within 2 days
-- ORDER BY amount DESC will give you ordered by amount descending

How to write this query MYSQL

I have this database:
| id | name | email | control_number | created | | | | | |
|:--:|-------|-----------------|----------------|------------|---|---|---|---|---|
| 1 | john | john#gmail.com | 1 | 14/09/2016 | | | | | |
| 2 | carl | carl#gmail.com | 1 | 13/08/2016 | | | | | |
| 3 | frank | frank#gmail.com | 2 | 12/08/2016 | | | | | |
And i want to get the COUNT in the last 12 months by the control_number.
basicly is a COUNT where control_number = 1 but by month.
So if the query is done today, its september, it should start from september to October 2015 and display the count of records for each month.
Result should be:
09/2016 = 50
08/2016 = 35
07/2016 = 20
06/2016 = 50
05/2016 = 21
04/2016 = 33
03/2016 = 60
02/2016 = 36
01/2016 = 11
12/2015 = 0
11/2015 = 0
10/2015 = 0
Hmmm. Getting the 0 values can be tricky. Assuming that you have some data each month (even if not for "1"), th en you can do:
select extract(year_month from created) as yyyymm,
sum(control_number = 1)
from t
where created >= date_sub(curdate(), interval 12 month)
group by extract(year_month from created)
order by yyyymm;
If you don't have at least one record for each month, then you'll need a left join and a table with one row per month.
Try this:
select CONCAT(SUBSTRING(ym, 5, 2), '/', SUBSTRING(ym, 1, 4)) Month, Count from (
select EXTRACT(YEAR_MONTH FROM created) ym, count(*) Count
from mytable
where EXTRACT(YEAR_MONTH FROM created) > (EXTRACT(YEAR_MONTH FROM SUBDATE(NOW(), INTERVAL 1 YEAR))
group by 1
order by 1 desc) x
Try:
select concat(month(created),'/',year(created)) as period, count(*) as cnt
from mytable
where control_number=1 and TIMESTAMPDIFF(year, created, now())=0
group by (month(created));

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