badge system / MySQL GROUP BY and ORDER BY - mysql

I'm creating a badge system for my website: the screenshot below shows you how it looks now. You receive badges with multiple levels (bronze, silver...) after earning 10,100 etc points for specific actions (such as answering a question, correcting a wiki). Note that the looking glass with the dollar sign is there two times, one in silver and one in bronze:
The underlying are two tables, one with the badge informations (x_badges: stores what action and how many points in it gives you what badge level), one with which user has earned which badge (x_badges_earned: badgeID and uID).
Explanation of FKs:
uID is the user ID, passed as parameter (use 1 for tests)
xb_ID is the auto_increment ID for each badge type
xbe_ID is the auto_increment ID of the row that matches userIDs to the ID of the badges they have earned (xb_ID -> uID)
This was my original query, which gives me only one badge of each category (but not the highest, ie if I have earned silver and bronze in one category it'll return only bronze):
SELECT *
FROM x_badges_earned, x_badges
WHERE xbe_bID = xb_ID
AND xbe_uID = ?
GROUP BY xb_requirement
ORDER BY xb_requirement ASC, xb_requirement_value ASC
After research on SO, I created this query which generates the above screenshot. If I change the last GROUP BY to xb_requirement, I'm back with the results from query 1:
SELECT b.*
FROM x_badges b
LEFT JOIN x_badges b2 ON b.xb_ID = b2.xb_ID
AND b.xb_requirement_value < b2.xb_requirement_value
LEFT JOIN x_badges_earned be ON be.xbe_bID = b.xb_ID
WHERE b2.xb_requirement_value IS NULL
AND be.xbe_uID = ?
GROUP BY b.xb_ID
I feel like I'm pretty much going in circles at this moment. I have come up with this which probably would work (at least in my head) but I can't solve it:
SELECT *
FROM x_badges
WHERE xb_ID IN (
SELECT xb_ID, MAX(xb_requirement_value)
FROM x_badges_earned, x_badges
WHERE xbe_bID = xb_ID AND xbe_uID = ?
GROUP BY xb_requirement
)
Any help is greatly appreciated!
I have attached the MySQL code to create the two tables and populate them to play around. Furthermore, you can use 1 as parameter value since it's my userID. My current output looks like this, the desired output would be the same without the first row (xb_ID 11) since it's a bronze badge for which I have earned silver.
CREATE TABLE `x_badges` (
`xb_ID` int(11) NOT NULL,
`xb_requirement` varchar(40) NOT NULL,
`xb_requirement_value` int(11) NOT NULL,
`xb_text` text NOT NULL,
`xb_text_details` text NOT NULL,
`xb_icon` varchar(20) NOT NULL,
`xb_color` varchar(6) NOT NULL,
`xb_color_text` varchar(6) NOT NULL,
`xb_level_name` varchar(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `x_badges` (`xb_ID`, `xb_requirement`, `xb_requirement_value`, `xb_text`, `xb_text_details`, `xb_icon`, `xb_color`, `xb_color_text`, `xb_level_name`) VALUES
(11, 'added_interaction', 10, 'Added interactions', 'Received more than %d points adding interactions with target companies!', 'search-dollar', 'cc8e34', '', 'Bronze'),
(15, 'added_target', 10, 'Added targets', 'Received more than %s points adding target companies!', 'dollar-sign', 'cc8e34', '', 'Bronze'),
(16, 'asked_question', 10, 'Asked questions', 'Received more than %s points asking questions!', 'question', 'cc8e34', '', 'Bronze'),
(17, 'notified_colleagues_interaction', 10, 'Interactions shared with colleagues', 'Received more than %s points tagging colleagues in interactions added!', 'bullhorn', 'cc8e34', '', 'Bronze'),
(18, 'reply_accepted', 10, 'Helpful replies', 'Received more than %s points thanks to replies marked as helpful!', 'check-double', 'cc8e34', '', 'Bronze'),
(19, 'reply_question', 10, 'Replies to questions', 'Received more than %s points replying to questions!', 'comments', 'cc8e34', '', 'Bronze'),
(20, 'updated_info', 10, 'Updated target profiles', 'Received more than %s points updating profiles of targets!', 'funnel-dollar', 'cc8e34', '', 'Bronze'),
(21, 'updated_wiki', 10, 'Updated wiki', 'Received more than %s points updating profiles of Group companies!', 'pen-nib', 'cc8e34', '', 'Bronze'),
(22, 'upvote_question', 10, 'Helpful questions', 'Received more than %s points for having own questions upvoted!', 'grin-alt', 'cc8e34', '', 'Bronze'),
(23, 'added_interaction', 100, 'Added interactions', 'Received more than %s points adding interactions with target companies!', 'search-dollar', 'aaa9ad', '', 'Silver'),
(24, 'added_interaction', 500, 'Added interactions', 'Received more than %s points adding interactions with target companies!', 'search-dollar', 'ffd700', '495057', 'Gold'),
(25, 'updated_wiki', 1000, 'Updated Bertelsmann wiki', 'Received more than %s points asking questions!', 'pen-nib', 'b9f2ff', '495057', 'Diamond'),
(26, 'added_interaction', 1000, 'Added interactions', 'Received more than %s points adding interactions with target companies!', 'search-dollar', 'b9f2ff', '495057', 'Diamond'),
(27, 'added_interaction', 5000, 'Added interactions', 'Received more than %s points adding interactions with target companies!', 'search-dollar', 'e0115f', '', 'Ruby');
CREATE TABLE `x_badges_earned` (
`xbe_ID` int(11) NOT NULL,
`xbe_uID` int(11) NOT NULL,
`xbe_bID` int(11) NOT NULL,
`xbe_timestamp` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `x_badges_earned` (`xbe_ID`, `xbe_uID`, `xbe_bID`, `xbe_timestamp`) VALUES
(19, 1, 11, '2020-03-23 15:24:54'),
(20, 1, 15, '2020-03-23 15:24:54'),
(21, 1, 16, '2020-03-23 15:24:54'),
(22, 1, 17, '2020-03-23 15:24:54'),
(23, 1, 18, '2020-03-23 15:24:54'),
(24, 1, 19, '2020-03-23 15:24:54'),
(25, 1, 23, '2020-03-23 16:00:51'),
(26, 1, 20, '2020-03-23 15:32:31'),
(27, 1, 21, '2020-03-23 15:32:31'),
(28, 1, 22, '2020-03-23 15:32:31'),
(29, 10, 25, '2020-03-23 15:37:32');
db<>fiddle here

I have not found identifying expression for any definite badge except (xb_icon, xb_requirement_value) values pair (color is secondary, timestamp not guaranteed the ordering).
So the solution can be:
SELECT *
FROM x_badges xb
JOIN x_badges_earned xbe ON xb.xb_ID = xbe.xbe_bID
JOIN ( SELECT xb.xb_icon, MAX(xb.xb_requirement_value) xb_requirement_value
FROM x_badges xb
JOIN x_badges_earned xbe ON xb.xb_ID = xbe.xbe_bID
WHERE xbe.xbe_uID = #user
GROUP BY xb_icon ) max_earned ON (xb.xb_icon, xb.xb_requirement_value)
= (max_earned.xb_icon, max_earned.xb_requirement_value)
WHERE xbe.xbe_uID = #user;
fiddle
As a recommendation - normalize x_badges table, divide it to 2 separate tables - one stored a badge type data, and another stored level-related data with the reference to first table.

Related

Calculate the number of jobs reviewed per hour per day for November 2020?

This question is really confusing me. They didn't provide enough details of it. Whatever they have provided I have written below.
job_id: unique identifier of jobs
actor_id: unique identifier of actor
event: decision/skip/transfer
language: language of the content
time_spent: time spent to review the job in seconds
org: organization of the actor
ds: date in the yyyy/mm/dd format. It is stored in the form of text and we use presto to run. no need for date function
CREATE TABLE job_data
(
ds DATE,
job_id INT NOT NULL,
actor_id INT NOT NULL,
event VARCHAR(15) NOT NULL,
language VARCHAR(15) NOT NULL,
time_spent INT NOT NULL,
org CHAR(2)
);
INSERT INTO job_data (ds, job_id, actor_id, event, language, time_spent, org)
VALUES ('2020-11-30', 21, 1001, 'skip', 'English', 15, 'A'),
('2020-11-30', 22, 1006, 'transfer', 'Arabic', 25, 'B'),
('2020-11-29', 23, 1003, 'decision', 'Persian', 20, 'C'),
('2020-11-28', 23, 1005,'transfer', 'Persian', 22, 'D'),
('2020-11-28', 25, 1002, 'decision', 'Hindi', 11, 'B'),
('2020-11-27', 11, 1007, 'decision', 'French', 104, 'D'),
('2020-11-26', 23, 1004, 'skip', 'Persian', 56, 'A'),
('2020-11-25', 20, 1003, 'transfer', 'Italian', 45, 'C');
Below is the data. Points to be considered :
What does the event mean? What to consider for reviewing?
And here's the query I've tried:
SELECT ds, COUNT(*)/24 AS no_of_job
FROM job_data
WHERE ds BETWEEN '2020-11-01' AND '2020-11-30'
GROUP BY ds;
Check below approach if it is what you are looking for.
select ds,count(job_id) as jobs_per_day, sum(time_spent)/3600 as hours_spent
from job_data
where ds >='2020-11-01' and ds <='2020-11-30'
group by ds ;
Demo MySQL 5.6: https://www.db-fiddle.com/f/7yUJcuMJPncBBnrExKbzYz/26
Demo MySQL 8.0.26: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=83e89a2ad2a7e73b7ca990ac36ae4df0
The difference between the demos as #FaNo_FN pointed out in the comments is: in MySQL 8.0.26version it will provide an error if date 2020-11-31 it is used, because there is no 31 Novembre.
Use and condition instead of between , it performs faster.
You need to sum the time_spent for the day.

Using mysql: column count doesn't match value count at row 1

I'm new to mysql, and can not figure out why this error keeps coming up. It's a simple table and I want id to be 1, 2, 3, 4 etc. alongside two other columns. Why does it keep reading, column count doesn't match value count at row 1?
CREATE DATABASE thedatabase;
USE thedatabase;
CREATE TABLE cars (
id INTEGER AUTO_INCREMENT,
model INTEGER NOT NULL,
mileage INTEGER NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO thedatabase.cars (
model,
mileage
) VALUES (
(45, 34598),
(22, 23847),
(10, 3847),
(487, 93229),
(237, 238975),
(23, 23987),
(34, 3498),
(57, 34984),
(56, 34983),
(20, 9845);
You have got an extra opening bracked in INSERT statement, after VALUES below should work fine:
INSERT INTO thedatabase.cars ( model,
mileage ) VALUES (45, 34598), (22, 23847), (10, 3847), (487, 93229), (237, 238975), (23, 23987), (34, 3498), (57, 34984), (56, 34983), (20, 9845);

Getting data if one value is NULL

I am having an issue with the code below and my issue is the following:
I need to be able to select the shop_shipping_rule_region_code if the country_iso is NULL but I also need to get the $country_iso data as well if present
What I am after:
The Final Result will have:
Overnight UPS
NZ Snail Mail
Whole World (in this example)
DB:
`shop_shipping_rules` (`shop_shipping_rule_id`, `shop_shipping_rule_country_iso`, `shop_shipping_rule_region_code`, `shop_shipping_rule_name`,
`shop_shipping_rule_type_single`, `shop_shipping_rule_item_single`, `shop_shipping_rule_type_multi`,
`shop_shipping_rule_item_multiple`,`shop_shipping_rule_created`, `shop_shipping_rule_modified`, `website_id`)
VALUES
(41, 'NZ', 'ALL', 'Overnight UPS', 'single', 2.00, 'multi', 4.00, '2013-11-05 02:30:19', '2013-11-05 03:00:27', 64),
(44, 'NZ', NULL, 'NZ Snail Mail', 'single', 25.00, 'multi', 35.00, '2013-11-14 03:57:06', NULL, 64),
(45, NULL, 'ALL', 'Whole World', 'single', 90.00, 'multi', 150.00, '2013-11-14 05:05:53', NULL, 64),
(46, NULL, 'EU', 'EU Ship', 'single', 28.00, 'multi', 35.00, '2013-11-15 01:04:01', NULL, 64);
if (isset($country_iso))
$sql = "SELECT * FROM shop_shipping_rules WHERE country_iso IS NULL OR country_iso = '$country_iso' ";
else
$sql = "SELECT * FROM shop_shipping_rules WHERE country_iso IS NULL";

Add up a points column based on an id from within the same mysql table

OK the database is layed out as (only columns being used are listed):
Table Name: race_stats
Columns: race_id, user_id, points, tournament_id
Table Name: user
Columns: user_id, driver
Table Name: race
Columns: race_id, race_name
Table Name: tournament
Columns: tournament_id, tournament_name
This is my current query:
$query = "
SELECT user.user_id, user.driver, race_stats.points, race_stats.user_id,
SUM(race_stats.points) AS total_points "."
FROM user, race_stats, tournament, race "."
WHERE race.race_id=race_stats.race_id
AND user.user_id=race_stats.user_id
AND tournament.tournament_id=race_stats.tournament_id
GROUP BY driver
ORDER BY total_points DESC
LIMIT 0, 15
";
Ok the query works but it is adding them all up for all the available races from the race_stats.race_id column as the total points. I have racked my brain beyond recognition to fix this but I just can't quite seem to find the solution I need. I'm sure it has to be an easy fix but I just can't get it. Any help is greatly appreciated.
///////////////////EDITED WITH RAW VALUES//////////////////////
INSERT INTO `race_stats` (`id_race`, `race_id`, `user_id`, `f`, `s`, `race_interval`, `race_laps`, `led`, `points`, `total_points`, `race_status`, `tournament_id`, `driver`, `tournament_name`) VALUES
(1, 1, 4, 1, 4, '135.878', 60, '2', 180, 0, 'Running', 1, 'new_driver_5', ''),
(2, 1, 2, 2, 2, '-0.08', 60, '22', 175, 0, 'Running', 1, 'new_driver_38', ''),
(3, 1, 5, 3, 5, '-11.82', 60, '2', 170, 0, 'Running', 1, 'new_driver_94', ''),
(4, 2, 2, 1, 15, '138.691', 29, '6', 180, 0, 'Running', 2, 'new_driver_38', ''),
(5, 2, 15, 2, 9, '-16.12', 29, '8*', 180, 0, 'Running', 2, 'new_driver_44', ''),
(6, 2, 8, 3, 11, '-2:03.48', 29, '0', 165, 0, 'Running', 2, 'new_driver_83', ''),
Let me know if this is what you meant by raw values if not I can get some more data for you.
Just posting the solution here for completeness:
SELECT user.driver, race_stats.race_id,
SUM(race_stats.points) AS total_points "."
FROM user, race_stats "."
WHERE user.user_id=race_stats.user_id
GROUP BY user.driver, race.race_id
Here's the query you want (formatted for readability):
SELECT
u.driver,
SUM(rs.points) AS total_points
FROM user u
LEFT JOIN race_stats rs on rs.user_id = u.user_id
GROUP BY 1;
The advantage of using an outer join (ie LEFT JOIN) is that drivers who have no stats still get a row, but with null as total_points.
p.s. I don't know what the usage of "." in your query is all about, so I removed it.

(SQL) Is it possible to update a field value with the value of the field in the previous row?

I have a table from which I would like the update the value of a certain colum of fields.
Basicly moving one value down and those under it should inherit the previous value of the one about them.
I wonder if this action is possible using a single SQL query.
My table:
CREATE TABLE `menu` (
`slot_index` int(2) NOT NULL,
`language_ID` int(2) NOT NULL,
`menuItem` text NOT NULL,
PRIMARY KEY (`slot_index`,`language_ID`),
KEY `language_ID` (`language_ID`)
)
The content in it:
INSERT INTO `menu` (`slot_index`, `language_ID`, `menuItem`) VALUES
(1, 1, 'Home'),
(2, 1, 'Wie zijn wij'),
(21, 1, 'Missie'),
(22, 1, 'Doelen'),
(23, 1, 'Visie'),
(24, 1, 'Test'),
(3, 1, 'Wat doen wij'),
(31, 1, 'Medische kaart voor op reis'),
(32, 1, 'Huisartsenkaart'),
(33, 1, 'Huisartsenkaart anderstaligen'),
(4, 1, 'Perskamer'),
(5, 1, 'Beheer'),
(6, 1, 'FAQ'),
(7, 1, 'Ervaringen'),
(8, 1, 'Contact'),
(81, 1, 'Disclaimer'),
(9, 1, 'Links'),
(91, 1, 'Adresgegevens')
I would like to move slot_index 5 to 9, and make the fields under it move up inheriting the value from the field above.
Is this possible with a single query at all, or should I just write a script for this?
Thanks in advance.
Wolfert
Maybe using auto increment will help for future uses.
As for the current one I doubt that this can go with single query, because you cant query the table you are trying to update.