MySQL - Calculate Revenue per employee per month - mysql

I am trying to calculate from my table "Revenue per employee"(how much money each employee generates for the company) per month.
My code So far:
SELECT
y.yr,
d.details,
d.labelname,
sum(case when month(app_date) = 1 then val else 0 end) month_01,
sum(case when month(app_date) = 2 then val else 0 end) month_02,
sum(case when month(app_date) = 3 then val else 0 end) month_03,
sum(case when month(app_date) = 4 then val else 0 end) month_04,
sum(case when month(app_date) = 5 then val else 0 end) month_05,
sum(case when month(app_date) = 6 then val else 0 end) month_06,
sum(case when month(app_date) = 7 then val else 0 end) month_07,
sum(case when month(app_date) = 8 then val else 0 end) month_08,
sum(case when month(app_date) = 9 then val else 0 end) month_09,
sum(case when month(app_date) = 10 then val else 0 end) month_10,
sum(case when month(app_date) = 11 then val else 0 end) month_11,
sum(case when month(app_date) = 12 then val else 0 end) month_12,
sum(case when month(app_date) > 0 then val else 0 end) total
from (
select 'a' dorder,'Peter' labelname,'1' details union all
select 'b' dorder,'John + Mary' labelname,'2' details union all
select 'c' dorder,'John' labelname,'3' details
) d cross join (
select distinct year(app_date) yr
from tblapp
) y
left join (
SELECT app_date, COALESCE(app_price, 0) val, '1' details from tblapp
INNER JOIN tblemployee ON tblemployee.emp_id = tblapp.emp_id
where tblemployee.emp_id=1
union all
SELECT app_date, COALESCE(app_price, 0) val, '2' details from tblapp
INNER JOIN tblemployee ON tblemployee.emp_id = tblapp.emp_id
INNER JOIN tblassistant ON tblassistant.ass_id = tblapp.ass_id
where tblemployee.emp_id=2 AND tblassistant.ass_id=1
union all
SELECT app_date, COALESCE(app_price, 0) val, '3' details from tblapp
INNER JOIN tblemployee ON tblemployee.emp_id = tblapp.emp_id
where tblemployee.emp_id=2
) t on year(t.app_date) = y.yr and t.details = d.details
group by y.yr, d.details
order by y.yr desc, d.dorder;
Code is working fine. Only problem is when i add a new employee or assistant, i have to edit the code and create manually the WHERE conditions(groups between employee and assistant)
CREATE TABLE `tblapp` (
`app_id` smallint(5) UNSIGNED NOT NULL,
`app_date` date DEFAULT NULL,
`app_price` double DEFAULT NULL,
`emp_id` smallint(5) UNSIGNED DEFAULT NULL,
`ass_id` smallint(5) UNSIGNED DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tblapp` (`app_id`, `app_date`, `app_price`, `emp_id`, `ass_id`) VALUES
(1, '2021-01-04', 100, 1, NULL),
(2, '2021-01-29', 100, 5, 1),
(3, '2021-02-20', 100, 2, 1),
(4, '2021-02-02', 100, 3, 2),
(5, '2021-03-19', 100, 2, NULL),
(6, '2021-04-24', 100, 4, 2),
(7, '2021-05-09', 100, 1, 1),
(8, '2021-07-04', 100, 2, 2),
(9, '2021-09-18', 100, 3, 1),
(10, '2021-10-12', 100, 5, NULL);
CREATE TABLE `tblemployee` (
`emp_id` smallint(5) UNSIGNED NOT NULL,
`emp_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tblemployee` (`emp_id`, `emp_name`) VALUES
(1, 'Peter'),
(2, 'John'),
(3, 'Alex'),
(4, 'Stack'),
(5, 'Over'),
(6, 'Flow');
CREATE TABLE `tblassistant` (
`ass_id` smallint(5) UNSIGNED NOT NULL,
`ass_name` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `tblassistant` (`ass_id`, `ass_name`) VALUES
(1, 'Mary'),
(2, 'Andrew'),
(3, 'John'),
(4, 'Helen');
http://sqlfiddle.com/#!9/3fd588/3

Related

MYSQL 5.6 get latest data of each user

My Database table is as shown below. I need to get latest mark of each student. Latest entry is the row with maximum udate and maximum oder. (The oder will be incremented by one on each entry with same date)
In my example, I have two students Mujeeb, Zakariya and two subjects ENGLISH, MATHS. I need to get latest mark of each student for each subject. My expectd result is as follows
My sample data is
DROP TABLE IF EXISTS `students`;
CREATE TABLE IF NOT EXISTS `students` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`udate` date NOT NULL,
`oder` int(11) NOT NULL,
`name` varchar(20) NOT NULL,
`Subject` varchar(20) NOT NULL,
`mark` int(11) NOT NULL,
PRIMARY KEY (`uid`)
) ENGINE=MyISAM AUTO_INCREMENT=13 DEFAULT CHARSET=latin1;
INSERT INTO `students` (`uid`, `udate`, `oder`, `name`, `Subject`, `mark`) VALUES
(1, '2021-08-01', 1, 'Mujeeb', 'ENGLISH', 10),
(2, '2021-08-01', 1, 'Zakariya', 'ENGLISH', 20),
(3, '2021-08-10', 2, 'Mujeeb', 'ENGLISH', 50),
(4, '2021-08-11', 2, 'Zakariya', 'ENGLISH', 60),
(5, '2021-08-02', 1, 'Mujeeb', 'ENGLISH', 100),
(6, '2021-08-03', 1, 'Zakariya', 'ENGLISH', 110),
(7, '2021-08-10', 1, 'Mujeeb', 'ENGLISH', 500),
(8, '2021-08-11', 1, 'Zakariya', 'ENGLISH', 600),
(9, '2021-08-01', 2, 'Mujeeb', 'MATHS', 100),
(10, '2021-08-01', 2, 'Zakariya', 'MATHS', 75),
(11, '2021-08-10', 3, 'Mujeeb', 'MATHS', 50),
(12, '2021-08-11', 3, 'Zakariya', 'MATHS', 60);
Use NOT EXISTS:
SELECT s1.*
FROM students s1
WHERE NOT EXISTS (
SELECT 1
FROM students s2
WHERE s2.name = s1.name AND s2.Subject = s1.Subject
AND (s2.udate > s1.udate OR (s2.udate = s1.udate AND s2.oder > s1.oder))
);
Or with a correlated subquery in the WHERE clause:
SELECT s1.*
FROM students s1
WHERE s1.uid = (
SELECT s2.uid
FROM students s2
WHERE s2.name = s1.name AND s2.Subject = s1.Subject
ORDER BY s2.udate DESC, s2.oder DESC LIMIT 1
);
See the demo.
As ROW_NUMBER() function doesn't work at lower version of MySQL, So alternate way of row_number() is used for this solution.
-- MySQL (v5.6)
SELECT p.uid, p.udate, p.oder, p.name, p.Subject, p.mark
FROM (SELECT #row_no := IF((#prev_val = t.name && #prev_val1 = t.Subject), #row_no + 1, 1) AS row_number
, #prev_val := t.name AS name
, #prev_val1 := t.Subject AS Subject
, t.mark
, t.oder
, t.uid
, t.udate
FROM students t,
(SELECT #row_no := 0) x,
(SELECT #prev_val := '') y,
(SELECT #prev_val1 := '') z
ORDER BY t.name, t.Subject, t.udate DESC, t.oder DESC ) p
WHERE p.row_number = 1
ORDER BY p.name, p.Subject;
Please check the url http://sqlfiddle.com/#!9/b5befe/18

How can I rewrite this to remove select statement joins?

As it is now each time I am selecting from Bill in my actual database I am searching over 1 million rows. There has to be a better way to write this query, but I haven't been able to figure it out. There has to be some way I can write this that doesn't join using the select statements.
If you notice I have some form of this join three seperate times. What should I do to remove this join?
join
(
select b.year
, sum(bli.amount) amt
from Bill b
join BillLine bli
on bli.bill_id = b.id
where bli.payment = 0
and bli.refund = 0
and bli.type = 3
group
by b.year
) org
on org.year = b.year
select b.Year,
Round(case when b.year = (select collectoryear -1 from Entity) then 0 else beg.amt end,2) Beginning,
Round(case when b.year >= (select collectoryear -1 from Entity) then ifnull(org.amt,0) else 0 end,2) Additions,
Round(case when b.year = (select collectoryear -1 from Entity) then 0 else beg.amt end + case when b.year >= (select collectoryear -1 from Entity) then ifnull(org.amt,0) else 0 end - ending.amt ,2) Collections,
Round(ending.amt,2) Ending
from Bill b
left join Levy l on l.year = b.year
join Entity e on b.year = e.year
join( select b.year, sum(bli.amount) amt from Bill b join BillLine bli on bli.bill_id = b.id where bli.payment = 0 and bli.refund = 0 and bli.type = 3 group by b.year) org on org.year = b.year
join( select b.year, sum(bli.amount) amt from Bill b join BillLine bli on bli.bill_id = b.id where bli.type = 2 group by b.year ) beg on beg.year = b.year
join( select b.year, sum(bli.amount) amt from Bill b join BillLine bli on bli.bill_id = b.id where bli.type = 2 group by b.year ) ending on ending.year = b.year
where b.year > (select collectoryear -11 from Entity)
and b.year < (select collectoryear from Entity)
group by b.year
order by Year desc ;
Here is the db-fiddle databases
create table Bill
( id varchar(20) PRIMARY KEY
, year varchar(20) FOREIGN KEY
);
Insert into Bill (id, year) values
(1, 2020),
(2, 2020),
(3, 2020),
(4, 2019),
(5, 2019),
(6, 2018),
(7, 2017),
(8, 2016),
(9, 2015),
(10, 2013);
create table BillLine
( id varchar(20) PRIMARY KEY
, bill_id varchar(20) FOREIGN KEY
, amount varchar(20)
, refund varchar(20)
, payment varchar(20)
, type varchar(20)
);
Insert into BillLine (id, bill_id, amount, refund, payment, type) values
(1, 10, 100.00, 0, 0, 3),
(2, 9, 250.00, 1, 1, 5),
(3, 8, 102.00, 0, 0, 3),
(4, 7, 85.00, 1, 1, 5),
(5, 6, 20.00, 0, 0, 3),
(6, 5, 43.75, 0, 1, 2),
(7, 4, 22.22, 0, 0, 3),
(8, 3, 125.25, 0, 1, 2),
(9, 2, 77.70, 0, 0, 3),
(10, 1, 100.75, 1, 1, 5);
create table Entity
( id varchar(20) PRIMARY KEY
, collectoryear varchar(20)
, year varchar(20)
);
Insert into Entity (id, collectoryear, year) values (1, 2021, 2020);
create table Levy
( id varchar(20) PRIMARY KEY, county varchar(20), year varchar(20)
);
Insert into Levy (id, county, year) values (1, null, null);

SQL Join multiple rows

I am looking for how to select in a JOIN only the line with the cheapest amount. Because the ON clause responds to multiple lines.
By default, MySQL takes the first line that it finds and I can not act on it.
SELECT g.id
, g.name
, p.description
, x.annually
FROM tblproductgroups g
JOIN tblproducts p
ON p.gid = g.id
AND p.hidden = 0
JOIN tblpricing x
ON x.relid = p.id
WHERE g.hidden = 0
AND g.id in (1,2,3)
AND x.type = 'product'
GROUP
BY g.id
I have to modify the JOIN of such tblpricing, but any comparison operation of the column "annually" gives me an error.
Edit: samples
CREATE TABLE `tblproductgroups` (
`id` int(10) NOT NULL,
`name` text COLLATE utf8_unicode_ci NOT NULL,
`hidden` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `tblproductgroups` (`id`, `name`, `hidden`) VALUES
(1, 'Hébergement WEB', 0),
(2, 'Serveurs virtuels KVM', 0),
(3, 'Serveurs dédiés Pro', 0),
(5, 'Colocation', 0);
CREATE TABLE `tblproducts` (
`id` int(10) NOT NULL,
`gid` int(10) NOT NULL,
`name` text COLLATE utf8_unicode_ci NOT NULL,
`description` text COLLATE utf8_unicode_ci NOT NULL,
`hidden` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `tblproducts` (`id`, `gid`, `name`, `description`, `hidden`) VALUES
(1, 1, 'Web Basic 2018', 'blablabla', 0),
(2, 1, 'Web Classic 2018', 'blablabla', 0),
(3, 1, 'Web Advanced 2018', 'blablabla', 0),
(5, 2, 'VPS Basic', 'blablabla', 0),
(6, 2, 'VPS Classic', 'blablabla', 0),
(7, 2, 'VPS Advanced', 'blablabla', 0),
(8, 3, 'SD-S 2018', 'blablabla', 0),
(9, 3, 'SD-L 2016', 'blablabla', 1),
(10, 3, 'SD-M 2018', 'blablabla', 0),
(11, 3, 'SD-XL 2018', 'blablabla', 0);
CREATE TABLE `tblpricing` (
`id` int(10) NOT NULL,
`type` enum('product','addon') COLLATE utf8_unicode_ci NOT NULL,
`relid` int(10) NOT NULL,
`annually` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
INSERT INTO `tblpricing` (`id`, `type`, `relid`, `annually`) VALUES
(1, 'product', 1, '30'),
(39, 'product', 2, '20'),
(40, 'product', 3, '10'),
(42, 'product', 5, '100'),
(43, 'product', 6, '50'),
(44, 'product', 7, '25'),
(45, 'product', 8, '2000'),
(46, 'product', 9, '1000'),
(47, 'product', 9, '500'),
(48, 'product', 10, '250');
Result of my query is:
1 Hébergement WEB blablabla 30.00
2 Serveurs virtuels KVM blablabla 100.00
3 Serveurs dédiés Pro blablabla 2000.00
the correct result is:
1 Hébergement WEB blablabla 10.00
2 Serveurs virtuels KVM blablabla 25.00
3 Serveurs dédiés Pro blablabla 250.00
Crudely...
SELECT a.*
FROM
( SELECT g.id
, g.name
, p.description
, x.annually
FROM tblproductgroups g
JOIN tblproducts p
ON p.gid = g.id
AND p.hidden = 0
JOIN tblpricing x
ON x.relid = p.id
WHERE g.hidden = 0
AND g.id in (1,2,3)
AND x.type = 'product'
) a
JOIN
( SELECT id
, MIN(annually) annually
FROM
( SELECT g.id
, g.name
, p.description
, x.annually
FROM tblproductgroups g
JOIN tblproducts p
ON p.gid = g.id
AND p.hidden = 0
JOIN tblpricing x
ON x.relid = p.id
WHERE g.hidden = 0
AND g.id in (1,2,3)
AND x.type = 'product'
) x
GROUP
BY id
) b
ON b.id = a.id
AND b.annually = a.annually
This should do it :
SELECT g.id
, g.name
, p.name
, x.annually
FROM tblproductgroups g
JOIN tblproducts p
ON p.gid = g.id
AND p.hidden = 0
JOIN tblpricing x
ON x.relid = p.id
/* Subtable containing the minimum annual fee per group */
JOIN (SELECT subg.id, MIN(subx.annually) AS annually FROM tblproductgroups subg
INNER JOIN tblproducts subp On subg.id = subp.gid
AND subp.hidden = 0
INNER JOIN tblpricing subx ON subx.relid = subp.id
WHERE subg.hidden = 0
AND subg.id in (1,2,3)
AND subx.type = 'product'
GROUP BY subg.id) m
ON g.id = m.id AND x.annually = m.annually
WHERE g.hidden = 0
AND g.id in (1,2,3)
AND x.type = 'product'
Don't use GROUP BY if you're not actually using any aggregation function in your column definitions. It might work in MySQL but the results will be unpredictable unless you know exactly what you're doing.

Creating a weighted sum of values from different tables

I'm trying to create a list of students whose behaviour is statistically worst across each of our school's year groups.
We have a table named students.
We then have behavioural flags and alerts, plus sanctions.
However, different categories of flag/alert/sanction are deemed more serious than others. These are stored with labels in their respective _categories table, e.g. flag_categories and sanction_categories. The flag table will then have a column called Category_ID (alerts is a bit different as it's just a Type field with 'A', 'C', 'P' and 'S' values).
If I want to look at data which shows our highest-flagged students in a specific year group, I'd run this query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
COUNT(f.ID) AS `Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
ORDER BY `Flags` DESC
LIMIT 0, 20
If I wanted to show our students with the most Crisis alerts in a specific year group, I'd run this query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
COUNT(f.ID) AS `Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
AND f.Category_ID = 10
GROUP BY stu.id
ORDER BY `Flags` DESC
LIMIT 0, 20
If I want to find how many Late or Mobile flags a student has, and perhaps add these together (with weightings), I can run the following query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `Late Flags`,
SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END) AS `Mobile Flags`,
## not sure about this line below... is there a nicer way of doing it? `Late Flags` isn't recognised as a field apparently
## so I can't just do ( `Late Flags` + `Mobile Flags` )
(SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) + SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END)) AS `Points`
FROM `flags` f
LEFT JOIN `students` stu ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
ORDER BY `Points` DESC
LIMIT 0, 20
What I don't understand is how I would do this across myriad tables. I need to be able to weight:
Late (flags, Category_ID = 10), Absconded (flags, Category_ID = 15) and Community flags (flags, Category_ID = 13) plus Safeguarding alerts (alerts, Type = 'S') are all worth 1 point
Behavioural flags (flags, Category_ID IN (1, 7, 8)) are worth 2 points
Process alerts (alerts, Type = 'P') and detention sanctions (sanctions, Category_ID = 1) are worth 3 points
So on and so forth. That's far from an exhaustive list but I've included enough variables to help me get my head round a multi-table weighted sum.
The outcome I'm looking for is just 2 columns - student's name and weighted points.
So, according to the bullet points above, if a student has received 2 Late flags (1 point each) and 1 Process alert (3 points), the output should just say Joe Bloggs and 5.
Can anyone help me to understand how I can get these weighted values from different tables into one SUM'd output for each student?
[edit] SQLFiddle here: http://sqlfiddle.com/#!9/449218/1/0
Note, I am not doing this for the bounty. Please give to someone else.
This could be done with a few LEFT JOINs of derived tables. Note you did not supply the sanctions table. But the below would appear to be well illustrative. So I created a temp table. It would seem to allow for maximum flexibility without overcomplicating a larger left join notion that might be hard to debug. Afterall, you said your real querying will be much more complicated than this. As such, build out the temp table structure more.
This loads a tmp table up with default 0's for the students in the "passed by parameter Student Year" to a stored procedure. Two updates are performed. Then selects for a result set.
Schema / Load:
create schema s38741386; -- create a test database
use s38741386;
CREATE TABLE `students` (
`id` int(11) PRIMARY KEY,
`Firstname` varchar(50) NOT NULL,
`Surname` varchar(50) NOT NULL,
`Year_Group` int(2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
# STUDENT INSERTS
INSERT INTO `students`
(`id`, `Firstname`, `Surname`, `Year_Group`)
VALUES
(201, 'Student', 'A', 9),
(202, 'Student', 'B', 9),
(203, 'Student', 'C', 9),
(204, 'Student', 'D', 9),
(205, 'Student', 'E', 9);
CREATE TABLE `alert` (
`ID` int(11) PRIMARY KEY,
`Staff_ID` int(6) NOT NULL,
`Datetime_Raised` datetime NOT NULL,
`Room_Label` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`Type` enum('A','C','P','Q','S') COLLATE utf8_unicode_ci NOT NULL COMMENT 'A=Absconded, C=Crisis, P=Process, Q=Quiet, S=Safeguarding',
`Details` text COLLATE utf8_unicode_ci,
`Responder` int(8) DEFAULT NULL,
`Datetime_Responded` datetime DEFAULT NULL,
`Room_ID` int(11) NOT NULL COMMENT 'will be linked to internal room id.',
`Status` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'O:ngoing, R:esolved'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT INSERTS
INSERT INTO `alert`
(`ID`, `Staff_ID`, `Datetime_Raised`, `Room_Label`, `Type`, `Details`, `Responder`, `Datetime_Responded`, `Room_ID`, `Status`)
VALUES
(1, '101', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(2, '102', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(3, '102', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(4, '101', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R');
CREATE TABLE `alert_students` (
`ID` int(11) PRIMARY KEY,
`Alert_ID` int(6) NOT NULL,
`Student_ID` int(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT_STUDENT INSERTS
INSERT INTO `alert_students`
(`ID`, `Alert_ID`, `Student_ID`)
VALUES
(1, '1', '201'),
(2, '1', '202'),
(3, '2', '201'),
(4, '3', '202'),
(5, '4', '203'),
(6, '5', '204');
CREATE TABLE `flags` (
`ID` int(11) PRIMARY KEY,
`Staff_ID` int(11) NOT NULL,
`Student_ID` int(11) NOT NULL,
`Datetime` datetime NOT NULL,
`Category_ID` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT INSERTS
-- TRUNCATE TABLE flags;
INSERT INTO `flags`
(`ID`, `Staff_ID`, `Student_ID`, `Datetime`, `Category_ID`)
VALUES
(1, '101', '201', '2016-08-04 00:00:01', 10),
(2, '102', '202', '2016-08-04 00:00:02', 12),
(3, '102', '203', '2016-08-04 00:00:03', 10),
(4, '101', '204', '2016-08-04 00:00:04', 13),
(5, '102', '202', '2016-08-04 00:00:02', 12),
(6, '102', '203', '2016-08-04 00:00:03', 10),
(7, '101', '204', '2016-08-04 00:00:04', 13),
(8, '102', '202', '2016-08-04 00:00:02', 10),
(9, '102', '203', '2016-08-04 00:00:03', 10),
(10, '101', '204', '2016-08-04 00:00:04', 7),
(11, '101', '204', '2016-08-04 00:00:07', 8),
(12, '101', '204', '2016-08-04 00:00:08', 1),
(13, '101', '204', '2016-08-04 00:00:09', 8);
Stored Procedure:
DROP PROCEDURE IF EXISTS rptSM_by_year;
DELIMITER $$
CREATE PROCEDURE rptSM_by_year
( pSY INT -- parameter student year
)
BEGIN
DROP TEMPORARY TABLE IF EXISTS tmpStudentMetrics;
CREATE TEMPORARY TABLE tmpStudentMetrics
( `StudentId` int(11) PRIMARY KEY,
LateFP INT NOT NULL,
MobiFP INT NOT NULL,
AbscFP INT NOT NULL,
CommFP INT NOT NULL,
SafeAP INT NOT NULL,
BehaFP INT NOT NULL,
ProcAP INT NOT NULL
)ENGINE=InnoDB;
INSERT tmpStudentMetrics (StudentId,LateFP,MobiFP,AbscFP,CommFP,SafeAP,BehaFP,ProcAP)
SELECT id,0,0,0,0,0,0,0
FROM students
WHERE Year_Group = pSY;
UPDATE tmpStudentMetrics tmp
JOIN
( SELECT
stu.id,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `LateFP`,
SUM(CASE WHEN f.Category_ID = 15 THEN 1 ELSE 0 END) AS `AbscFP`,
SUM(CASE WHEN f.Category_ID = 13 THEN 1 ELSE 0 END) AS `CommFP`,
SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END) AS `MobiFP`,
SUM(CASE WHEN f.Category_ID IN (1,7,8) THEN 2 ELSE 0 END) AS `BehaFP`
FROM `flags` f
LEFT JOIN `students` stu ON f.Student_ID = stu.id
WHERE stu.Year_Group = pSY
GROUP BY stu.id
) xDerived
ON xDerived.id=tmp.StudentId
SET tmp.LateFP=xDerived.LateFP,
tmp.AbscFP=xDerived.AbscFP,
tmp.CommFP=xDerived.CommFP,
tmp.MobiFP=xDerived.MobiFP,
tmp.BehaFP=xDerived.BehaFP;
UPDATE tmpStudentMetrics tmp
JOIN
( SELECT
stu.id,
SUM(CASE WHEN a.Type = 'S' THEN 1 ELSE 0 END) AS `SafeAP`,
SUM(CASE WHEN a.Type = 'P' THEN 3 ELSE 0 END) AS `ProcAP`
FROM `alert_students` als
JOIN `alert` a
ON a.ID=als.Alert_ID
JOIN `students` stu
ON stu.id=als.Student_ID and stu.Year_Group = pSY
GROUP BY stu.id
) xDerived
ON xDerived.id=tmp.StudentId
SET tmp.SafeAP=xDerived.SafeAP,
tmp.ProcAP=xDerived.ProcAP;
-- SELECT * FROM tmpStudentMetrics; -- check detail
SELECT stu.id,
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
tmp.LateFP+tmp.MobiFP+tmp.AbscFP+tmp.CommFP+tmp.SafeAP+tmp.BehaFP+tmp.ProcAP AS `Points`
FROM `students` stu
JOIN tmpStudentMetrics tmp
ON tmp.StudentId=stu.id
WHERE stu.`Year_Group` = pSY
ORDER BY stu.id;
-- SELECT * FROM tmpStudentMetrics; -- check detail
DROP TEMPORARY TABLE IF EXISTS tmpStudentMetrics;
-- TEMP TABLES are connection based. Explicityly dropped above for safety when done.
-- Depends on your connection type and life-span otherwise.
END$$
DELIMITER ;
Test:
call rptSM_by_year(9);
+-----+-----------+--------+
| id | Student | Points |
+-----+-----------+--------+
| 201 | Student A | 7 |
| 202 | Student B | 11 |
| 203 | Student C | 6 |
| 204 | Student D | 10 |
| 205 | Student E | 0 |
+-----+-----------+--------+
Cleanup:
drop schema s38741386; -- drop the test database
Think all you have asked can be done with a subquery and a couple of sub-SELECTs:
SELECT `Student`,
`Late Flags` * 1
+ `Absconded Flags` * 1
+ `Community Flags` * 1
+ `Safeguarding Alerts Flags` * 1
+ `Behavioural flags` * 2
+ `Process Alerts Flags` * 3 AS `Total Points`
FROM
(
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `Late Flags`,
SUM(CASE WHEN f.Category_ID = 12 THEN 1 ELSE 0 END) AS `Mobile Flags`,
SUM(CASE WHEN f.Category_ID = 15 THEN 1 ELSE 0 END) AS `Absconded Flags`,
SUM(CASE WHEN f.Category_ID = 13 THEN 1 ELSE 0 END) AS `Community Flags`,
(SELECT COUNT(*) FROM `alert` a JOIN `alert_students` ast ON ast.`Alert_ID` = a.`ID`
WHERE ast.`Student_ID` = stu.`id` AND a.`Type` = 'S') AS `Safeguarding Alerts Flags`,
SUM(CASE WHEN f.Category_ID IN (1, 7, 8) THEN 1 ELSE 0 END) AS `Behavioural flags`,
(SELECT COUNT(*) FROM `alert` a JOIN `alert_students` ast ON ast.`Alert_ID` = a.`ID`
WHERE ast.`Student_ID` = stu.`id` AND a.`Type` = 'P') AS `Process Alerts Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
LIMIT 0, 20
) subq
ORDER BY `Total Points` DESC;
The above query includes everything you mentioned apart from sanctions (as your original SQL Fiddle demo didn't include this table).
Demo
An updated fiddle with the above query is here: http://sqlfiddle.com/#!9/449218/39.
You could use union all
Basically you create all your individual queries for each table and connect them all together using union all.
Here is an example, I used your student table twice but you would change the second one to what ever other table you want. SQLFiddle
You can do it with LEFT JOINS:
SELECT CONCAT(stu.firstname,' ', stu.surname) student,
COALESCE(f_group.weight_sum,0) + COALESCE(a_group.weight_sum,0) + COALESCE(s_group.weight_sum,0) points
FROM students stu
LEFT JOIN (
SELECT s_f.id, SUM(f.category_id IN (10,13,15) + 2 * f.category_id IN (1,7,8)) weight_sum
FROM students s_f
JOIN flags f
ON f.student_id = s_f.id
AND f.category_id IN (1,7,8,10,13,15)
WHERE s_f.year_group = :year_group
GROUP BY s_f.id
) f_group
LEFT JOIN (
SELECT s_a.id, 3 * COUNT(*) weight_sum
FROM students s_a
JOIN alerts a
ON a.student_id = s_a.id
AND a.type = 'P'
WHERE s_a.year_group = :year_group
GROUP BY s_a.id
) a_group
LEFT JOIN (
SELECT s_s.id, COUNT(*) weight_sum
FROM students s_s
JOIN sanctions s
ON s.student_id = s_s.id
AND s.category_id = 1
WHERE s_s.year_group = :year_group
GROUP BY s_s.id
) s_group
WHERE stu.year_group = :year_group
ORDER BY points DESC
LIMIT 0, 20
BUT if you have full access to the DB I'd be putting those weights in the respective categories and types, which will simplify the logic.

how can I avoid column which contain null or zero value [duplicate]

This question already has an answer here:
Can you help to modify the query or other query for getting the expected result
(1 answer)
Closed 5 years ago.
I want to avoid those column which contain null or zero value
here the table structure
-- Table structure for table orders
CREATE TABLE `orders` (
`id` int(11) NOT NULL,
`customer_id` int(11) NOT NULL,
`restaurant_id` int(11) NOT NULL,
`source_id` int(1) NOT NULL,
`purchase_method` varchar(255) NOT NULL,
`total_price` int(11) NOT NULL,
`date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `orders`
--
INSERT INTO `orders` (`id`, `customer_id`, `restaurant_id`, `source_id`, `purchase_method`, `total_price`, `date_created`) VALUES
(1, 1, 1, 3, 'Cash', 500, '2016-05-31 11:44:16'),
(2, 1, 1, 3, 'Cash', 1500, '2016-06-01 11:44:22'),
(3, 1, 1, 3, '', 650, '2016-06-02 11:44:26'),
(4, 1, 1, 2, 'cash', 1500, '2016-06-03 11:44:31'),
(5, 1, 1, 1, 'cash', 12000, '2016-06-04 21:08:00'),
(6, 1, 1, 1, 'cash', 14500, '2016-06-05 00:00:00'),
(7, 1, 1, 2, 'cash', 15000, '2016-06-10 09:47:15'),
(8, 1, 1, 2, 'cash', 14500, '2016-05-10 10:03:55'),
(9, 1, 1, 1, 'cash', 11800, '2016-06-08 00:00:00'),
(10, 1, 1, 2, 'ss', 300, '2016-06-08 01:06:56'),
(11, 1, 1, 1, 'online', 400, '2016-05-10 10:03:20'),
(12, 1, 1, 3, 'cash', 5000, '2016-06-09 06:23:16'),
(13, 1, 1, 2, 'cash', 2000, '2016-05-10 10:03:35'),
(14, 1, 1, 1, 'cash', 499, '2016-04-11 18:30:00'),
(15, 1, 1, 1, 'cash', 2010, '2016-03-11 18:58:00'),
(16, 1, 1, 1, 'cash', 599, '2016-03-11 18:30:00'),
(17, 1, 1, 1, 'online', 699, '2016-05-02 18:30:00');
-- --------------------------------------------------------
the query I tried below it
SELECT
SUM(CASE WHEN MONTH(date_created)=1 THEN (total_price) END) Jan,
SUM(CASE WHEN MONTH(date_created)=2 THEN (total_price) END) Feb,
SUM(CASE WHEN MONTH(date_created)=3 THEN (total_price) END) Mar,
SUM(CASE WHEN MONTH(date_created)=4 THEN (total_price) END) Apr,
SUM(CASE WHEN MONTH(date_created)=5 THEN (total_price) END) May,
SUM(CASE WHEN MONTH(date_created)=6 THEN (total_price) END) Jun,
SUM(CASE WHEN MONTH(date_created)=7 THEN (total_price) END) July,
SUM(CASE WHEN MONTH(date_created)=8 THEN (total_price) END) Aug,
SUM(CASE WHEN MONTH(date_created)=9 THEN (total_price) END) Sep,
SUM(CASE WHEN MONTH(date_created)=10 THEN (total_price) END) 'Oct',
SUM(CASE WHEN MONTH(date_created)=11 THEN (total_price) END) Nov,
SUM(CASE WHEN MONTH(date_created)=12 THEN (total_price) END) 'Dec'
FROM orders
WHERE source_id =1 AND date_created BETWEEN(CURDATE() - INTERVAL 1 MONTH)
AND CURDATE()
Result I get from query
Jan Feb Mar Apr May Jun July Aug Sep Oct Nov Dec
null null null null null 38300 null null null null null null null
desire result
may june
0 38300
A SQL query returns a fixed set of columns, determined by the from clause. If you want a variable set of columns, then you can use dynamic SQL.
In your case, perhaps getting the results as columns would suffice:
select month(date_created), sum(total_price)
from orders
where source_id = 1 and
date_created BETWEEN(CURDATE() - INTERVAL 1 MONTH) and CURDATE()
group by month(date_created)
having sum(total_price) <> 0;