Related
I have
order table with columns
id
date
supplier_id
order_lineitem table with columns
id
order_id
article_id
order_quantity
order_price
a prices table with columns
id
article_id
supplier_id
valid_until
minimum_order_quantity
list_price
The prices table doesn't necessarily have to have a matching / valid entry, so this one would have to be joined via an outer join.
I'd like to compare order_prices against list_prices.
Therefore I need to somehow join
SELECT
o.id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
p.list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id
LEFT OUTER JOIN prices p on
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
/* here comes the fun part that doesn't work (reliably) */
ORDER BY
IFNULL(p.valid_until, DATE('2099-12-31')) asc,
p.minimum_order_quantity desc
GROUP BY o.id, ol.id, p.article_id
/* ... trying to get only THAT price from the prices table that applies for the
(a) the given article
(b) from the given supplier
(c) that was valid at the time of purchase (i.e. has the smallest "valid_until" date that is greater than the purchase date)
(d) when ordering the given quantity (prices can also increase with higher quantities, so it has to be the price with the largest minimum_order_quantity that is smaller than the ordered quantity)
*/
I particularly don't want to fall into the trap (which I dug for myself here) of using group by to limit the results to 1 record from the prices table based on a previous sorting, since
(i) as per MySQL documentation it is non-deterministic which record will actually get returned (although it may in effect often work and this is a frequently suggested route to go) - also see this excellent explanation on the issue: https://stackoverflow.com/a/14770936/9818188 and
(ii) this concept wouldn't work on other SQL implementations like SQL Server, Maria DB & Co.
The question is not around putting in a nested query in order to be able to ORDER first and then GROUP subsequently. It's more about how to really properly get the correct row--ideally also working on other SQL implementations like SQL Server, Maria DB or Google BigQuery.
And since I can't really rely on prices being cheaper the more I buy I also can't simply get the min(list_price).
How can this can be achieved?
Since the output of this query is required for downstream processing, I can't slice & dice the task but need a full list of all orders with respective list prices.
EDIT
Here is a SQL fiddle - the desired prices are shown in column order_price, the prices incorrectly determined by the JOIN (excluding the order byclause - as this would cause non-deterministic results) are shown in column list_price:
http://sqlfiddle.com/#!9/f03a4f/2
CREATE TABLE `order`
(`id` int, `date` datetime, `supplier_id` int)
;
INSERT INTO `order`
(`id`, `date`, `supplier_id`)
VALUES
(1, '2022-01-15 00:00:00', 1),
(2, '2022-02-15 00:00:00', 1),
(3, '2022-03-15 00:00:00', 1),
(4, '2022-01-15 00:00:00', 2),
(5, '2022-02-15 00:00:00', 2),
(6, '2022-03-15 00:00:00', 2)
;
CREATE TABLE order_lineitem
(`id` int, `order_id` int, `article_id` int, `order_quantity` int, `order_price` int)
;
INSERT INTO order_lineitem
(`id`, `order_id`, `article_id`, `order_quantity`, `order_price`)
VALUES
(1, 1, 1, 1, 11),
(2, 1, 1, 10, 8),
(3, 1, 1, 100, 9),
(4, 2, 1, 1, 15),
(5, 2, 1, 10, 12),
(6, 2, 1, 100, 13),
(7, 3, 1, 1, 17),
(8, 3, 1, 10, 14),
(9, 3, 1, 100, 16),
(10, 4, 1, 1, 10),
(11, 4, 1, 10, 80),
(12, 4, 1, 100, 80),
(13, 5, 1, 1, 10),
(14, 5, 1, 10, 80),
(15, 5, 1, 100, 80),
(16, 6, 1, 1, 10),
(17, 6, 1, 10, 10),
(18, 6, 1, 100, 10)
;
CREATE TABLE prices
(`id` int, `article_id` int, `supplier_id` int, `valid_until` varchar(10), `minimum_order_quantity` int, `list_price` int)
;
INSERT INTO prices
(`id`, `article_id`, `supplier_id`, `valid_until`, `minimum_order_quantity`, `list_price`)
VALUES
(1, 1, 1, '2022-01-31', 1, 11),
(2, 1, 1, '2022-01-31', 10, 8),
(3, 1, 1, '2022-01-31', 100, 9),
(4, 1, 2, NULL, 1, 10),
(5, 1, 1, '2022-02-31', 1, 15),
(6, 1, 1, '2022-02-31', 10, 12),
(7, 1, 1, '2022-02-31', 100, 13),
(8, 1, 1, NULL, 1, 17),
(9, 1, 1, NULL, 10, 14),
(10, 1, 1, NULL, 100, 16),
(11, 2, 1, NULL, 1, 99),
(12, 1, 2, '2022-02-31', 10, 80)
;
SELECT
o.id,
o.supplier_id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
p.list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id
LEFT OUTER JOIN prices p on
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
/* here comes the fun part that doesn't work (reliably) */
/* NOTE: I am purposesly commenting out the ORDER BY clause here, because
(a) it would have to go after GROUP BY - requiring a nested table which I would like to prevent AND, more importantly,
(b) limiting the numer of rows returned to 1 by GROUPing with an incomplete set of columns on a sorted table may return non-deterministic results as per the MySQL documentation.
see also https://stackoverflow.com/a/14770936/9818188 explaining the issue with GROUP BY in this context
#
# ORDER BY
# IFNULL(p.valid_until, DATE('2099-12-31')) asc,
# p.minimum_order_quantity desc
*/
GROUP BY o.id, ol.id, p.article_id
/* ... trying to get only THAT price from the prices table that applies for the
(a) the given article
(b) from the given supplier
(c) that was valid at the time of purchase (i.e. has the smallest "valid_until" date that is greater than the purchase date)
(d) when ordering the given quantity (prices can also increase with higher quantities, so it has to be the price with the largest minimum_order_quantity that is smaller than the ordered quantity)
*/
If you are interrestd in the highest listprice, you would do it like the.
If you need also other columns from theprices table, you need to SQL select only rows with max value on a column
as you have to join the sub querys for all articles
SELECT
o.id,
o.date,
ol.article_id,
ol.order_quantity,
ol.order_price,
(SELECT `list_price` FROM prices p WHERE
p.article_id = ol.article_id
AND p.supplier_id = o.supplier_id
AND p.minimum_order_quantity <= ol.order_quantity
AND IFNULL(p.valid_until, DATE('2099-12-31')) >= o.date
ORDER BY `list_price` DESC
LIMIT 1
) list_price
FROM
`order` o JOIN order_lineitem ol on ol.order_id = o.id
I have three tables
CREATE TABLE `LineItems` (
`LineItemID` int NOT NULL,
`OrderID` int NOT NULL,
`ProductID` int NOT NULL
);
INSERT INTO `LineItems` (`LineItemID`, `OrderID`, `ProductID`) VALUES
(1, 1, 2),
(2, 1, 1),
(3, 2, 3),
(4, 2, 4),
(5, 3, 1),
(6, 4, 2),
(7, 5, 4),
(8, 5, 2),
(9, 5, 3),
(10, 6, 1),
(11, 6, 4),
(12, 7, 4),
(13, 7, 1),
(14, 7, 2),
(15, 8, 1),
(16, 9, 3),
(17, 9, 4),
(18, 10, 3);
CREATE TABLE `Orders` (
`OrderID` int NOT NULL,
`UserID` int NOT NULL,
`OrderDate` datetime NOT NULL
);
INSERT INTO `Orders` (`OrderID`, `UserID`, `OrderDate`) VALUES
(1, 21, '2021-05-01 00:00:00'),
(2, 21, '2021-05-03 00:00:00'),
(3, 24, '2021-05-06 00:00:00'),
(4, 23, '2021-05-12 00:00:00'),
(5, 21, '2021-05-14 00:00:00'),
(6, 22, '2021-05-16 00:00:00'),
(7, 23, '2021-05-20 00:00:00'),
(8, 21, '2021-05-22 00:00:00'),
(9, 24, '2021-05-23 00:00:00'),
(10, 23, '2021-05-26 00:00:00');
CREATE TABLE `Products` (
`ProductID` int NOT NULL,
`ProductTitle` VARCHAR(250) NOT NULL,
`ProductType` enum('doors','windows','flooring') NOT NULL
);
INSERT INTO `Products` (`ProductID`, `ProductTitle`, `ProductType`) VALUES
(1, 'French Doors','doors'),
(2, 'Sash Windows','windows'),
(3, 'Sliding Doors','doors'),
(4, 'Parquet Floor','flooring');
SQL Fiddle:
Orders - contains an order date and a user id
LineItems - Foreign key to the orders table, contains product ids that are in the order
Products - Contains details of the products (including if they are a door, window, or flooring)
I have figured out how to get the latest order per user with
SELECT O.* FROM Orders O LEFT JOIN Orders O2
ON O2.UserID=O.UserID AND O.OrderDate < O2.OrderDate
WHERE O2.OrderDate IS NULL;
This works fine and is included in the SQL fiddle, along with a query that returns a complete picture for reference.
I am trying to figure out how to get the latest order with flooring per user, but I'm not having any luck.
In the SQL fiddle linked above, the intended output for what I am after would be
OrderID | UserID | OrderDate
6 | 22 | 2021-05-16T00:00:00Z
5 | 21 | 2021-05-14T00:00:00Z
9 | 24 | 2021-05-23T00:00:00Z
7 | 23 | 2021-05-20T00:00:00Z
EDIT: To clarify, in the intended result, two rows (for users 21 and 23) are different than in the query that gets just latest order per user. This is because order IDs 8 and 10 (from the latest order per user query) do not include flooring. The intended query has to find the latest order with flooring from each user to return in the result set.
You need to add the LineItems and Products tables to your query to find orders where flooring was purchased:
SELECT DISTINCT O.*
FROM Orders O
LEFT JOIN Orders O2
ON O2.UserID=O.UserID AND
O.OrderDate < O2.OrderDate
INNER JOIN LineItems i
ON i.OrderID = O.OrderID
INNER JOIN Products p
ON p.ProductID = i.ProductID
WHERE O2.OrderDate IS NULL AND
p.ProductType = 'flooring'
db<>fiddle here
Given the following data set, how would I find the email addresses that were references for the most ApplicationIDs that have an "Accepted" decision?
CREATE TABLE IF NOT EXISTS `EmailReferences` (
`ApplicationID` INT NOT NULL,
`Email` VARCHAR(45) NOT NULL,
PRIMARY KEY (`ApplicationID`, `Email`)
);
INSERT INTO EmailReferences (ApplicationID, Email)
VALUES
(1, 'ref10#test.org'), (1, 'ref11#test.org'), (1, 'ref12#test.org'),
(2, 'ref20#test.org'), (2, 'ref21#test.org'), (2, 'ref22#test.org'),
(3, 'ref11#test.org'), (3, 'ref31#test.org'), (3, 'ref32#test.org'),
(4, 'ref40#test.org'), (4, 'ref41#test.org'), (4, 'ref42#test.org'),
(5, 'ref50#test.org'), (5, 'ref51#test.org'), (5, 'ref52#test.org'),
(6, 'ref60#test.org'), (6, 'ref11#test.org'), (6, 'ref62#test.org'),
(7, 'ref70#test.org'), (7, 'ref71#test.org'), (7, 'ref72#test.org'),
(8, 'ref10#test.org'), (8, 'ref81#test.org'), (8, 'ref82#test.org')
;
CREATE TABLE IF NOT EXISTS `FinalDecision` (
`ApplicationID` INT NOT NULL,
`Decision` ENUM('Accepted', 'Denied') NOT NULL,
PRIMARY KEY (`ApplicationID`)
);
INSERT INTO FinalDecision (ApplicationID, Decision)
VALUES
(1, 'Accepted'), (2, 'Denied'),
(3, 'Accepted'), (4, 'Denied'),
(5, 'Denied'), (6, 'Denied'),
(7, 'Denied'), (8, 'Accepted')
;
Fiddle of same:http://sqlfiddle.com/#!9/03bcf2/1
Initially, I was using LIMIT 1 and ORDER BY CountDecision DESC, like so:
SELECT er.email, COUNT(fd.Decision) AS CountDecision
FROM EmailReferences AS er
JOIN FinalDecision AS fd ON er.ApplicationID = fd.ApplicationID
WHERE fd.Decision = 'Accepted'
GROUP BY er.email
ORDER BY CountDecision DESC
LIMIT 1
;
However, it occurred to me that I could have multiple email addresses that referred different "most accepted" decisions (i.e., a tie, so to speak), and those would be filtered out (is that the right phrasing?) with the LIMIT keyword.
I then tried a variation on the above query, replacing the ORDER BY and LIMIT lines with:
HAVING MAX(CountDecision)
But I realized that that's only half a statement: MAX(CountDecision) needs to be compared to something. I just don't know what.
Any pointers would be much appreciated. Thanks!
Note: this is for a homework assignment.
Update: To be clear, I'm trying to find value and count of Emails from EmailReferences. However, I only want rows that have FinalDecision.Decision = 'Accepted' (on matching ApplicantIDs). Based on my data, the result should be:
Email | CountDecision
---------------+--------------
ref10#test.org | 2
ref11#test.org | 2
For example...
SELECT a.*
FROM
( SELECT x.email
, COUNT(*) total
FROM emailreferences x
JOIN finaldecision y
ON y.applicationid = x.applicationid
WHERE y.decision = 'accepted'
GROUP
BY x.email
) a
JOIN
( SELECT COUNT(*) total
FROM emailreferences x
JOIN finaldecision y
ON y.applicationid = x.applicationid
WHERE y.decision = 'accepted'
GROUP
BY x.email
ORDER
BY total DESC
LIMIT 1
) b
ON b.total = a.total;
MySQL still lack window functions, but when version 8 is production ready, this becomes easier. So for fuure reference, or for those databases like Mariadb that already have window functions:
CREATE TABLE IF NOT EXISTS `EmailReferences` (
`ApplicationID` INT NOT NULL,
`Email` VARCHAR(45) NOT NULL,
PRIMARY KEY (`ApplicationID`, `Email`)
);
INSERT INTO EmailReferences (ApplicationID, Email)
VALUES
(1, 'ref10#test.org'), (1, 'ref11#test.org'), (1, 'ref12#test.org'),
(2, 'ref20#test.org'), (2, 'ref21#test.org'), (2, 'ref22#test.org'),
(3, 'ref30#test.org'), (3, 'ref31#test.org'), (3, 'ref32#test.org'),
(4, 'ref40#test.org'), (4, 'ref41#test.org'), (4, 'ref42#test.org'),
(5, 'ref50#test.org'), (5, 'ref51#test.org'), (5, 'ref52#test.org'),
(6, 'ref60#test.org'), (6, 'ref11#test.org'), (6, 'ref62#test.org'),
(7, 'ref70#test.org'), (7, 'ref71#test.org'), (7, 'ref72#test.org'),
(8, 'ref10#test.org'), (8, 'ref81#test.org'), (8, 'ref82#test.org')
;
CREATE TABLE IF NOT EXISTS `FinalDecision` (
`ApplicationID` INT NOT NULL,
`Decision` ENUM('Accepted', 'Denied') NOT NULL,
PRIMARY KEY (`ApplicationID`)
);
INSERT INTO FinalDecision (ApplicationID, Decision)
VALUES
(1, 'Accepted'), (2, 'Denied'),
(3, 'Accepted'), (4, 'Denied'),
(5, 'Denied'), (6, 'Denied'),
(7, 'Denied'), (8, 'Accepted')
;
select email, CountDecision
from (
SELECT er.email, COUNT(fd.Decision) AS CountDecision
, max(COUNT(fd.Decision)) over() maxCountDecision
FROM EmailReferences AS er
JOIN FinalDecision AS fd ON er.ApplicationID = fd.ApplicationID
WHERE fd.Decision = 'Accepted'
GROUP BY er.email
) d
where CountDecision = maxCountDecision
email | CountDecision
:------------- | ------------:
ref10#test.org | 2
dbfiddle here
I started from here:
Rename duplicate rows in MySQL
SELECT mid(s,instr(s,',')+1) as dubid
FROM (
SELECT group_concat(id ORDER BY id ASC) s, count(*) c
FROM `...`
GROUP BY field
HAVING c > 1) as z
Seems to work fine and returns dubid rows like
1
3,5,6
8
10,15
So I made this:
set #suf:=0;
UPDATE `table_cf_vendlist`
SET `cf_vendlist` = concat(`cf_vendlist`, ' \(', #suf:=#suf+1,'\)')
WHERE `cf_vendlistid` IN ({SELECT mid...})
But it renames only first ID from each row. So how I can make it work? Also how to nullyfy #suf counter for each row?
And no PHP. MySQL only!
set #suf:=0;
set #last := 0;
UPDATE `vtiger_cf_vendlist` t1
INNER JOIN (
SELECT MIN(cf_vendlistid) as id, cf_vendlist
FROM vtiger_cf_vendlist
GROUP BY cf_vendlist) AS unique1
ON (t1.cf_vendlist = unique1.cf_vendlist)
SET t1.`cf_vendlist` = concat(t1.`cf_vendlist`, ' \(',
#suf := if (#last = t1.cf_vendlistid, #suf+1, 1 and #last := t1.cf_vendlist), '\)')
WHERE t1.cf_vendlistid <> unique1.id
But I am not shure where is t1.cf_vendlist, and where is unique1.cf_vendlist. Also result is 0 and 1 in names, no increments and zeroing for each id set. But still, loks like it renamed everyting is needed.
Here is some data to run with sqlfiddle.com:
CREATE TABLE IF NOT EXISTS `table_cf_vendlist` (
`cf_vendlistid` int(11) NOT NULL AUTO_INCREMENT,
`cf_vendlist` varchar(200) NOT NULL,
PRIMARY KEY (`cf_vendlistid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=78 ;
INSERT INTO `table_cf_vendlist` (`cf_vendlistid`, `cf_vendlist`) VALUES
(2, 'Поставщик 2'),
(3, 'Поставщик 3'),
(4, 'Поставщик 4'),
(5, 'del 1'),
(6, 'Поставщик 1 (1)'),
(7, 'Поставщик 1 (2)'),
(8, 'пост 66'),
(9, 'пост 77'),
(10, 'пост 67'),
(11, 'пост 5'),
(12, '123'),
(13, '102'),
(14, 'пост 5 (1)'),
(15, 'пост 70'),
(47, 'Поставщик 1'),
(48, 'Поставщик 2'),
(49, 'Поставщик 3'),
(50, 'Поставщик 4'),
(51, 'del 1'),
(52, 'Поставщик 1'),
(53, 'Поставщик 1'),
(54, 'пост 5'),
(55, '124523452346256456'),
(56, 'пост 66'),
(57, 'пост 77'),
(58, 'пост 5'),
(59, 'пост 5'),
(60, 'пост 5'),
(61, 'пост 67'),
(62, '123'),
(63, '123'),
(64, '123'),
(65, 'пост 5'),
(66, 'пост 68'),
(67, '123'),
(68, '123'),
(69, '123'),
(70, '123'),
(71, '102'),
(72, 'пост 5'),
(73, 'пост 70');
UPD
We got
52 Поставщик 1 (11)
53 Поставщик 1 (12)
Instead of
52 Поставщик 1 (3)
53 Поставщик 1 (4)
and
48 Поставщик 2 (13)
49 Поставщик 3 (14)
50 Поставщик 4 (15)
Instead of
48 Поставщик 2 (2)
49 Поставщик 3 (2)
50 Поставщик 4 (2)
UPD 2:
set #suf:=0;
set #last := 0;
UPDATE table_cf_vendlist t
INNER JOIN (SELECT cf_vendlistid, concat(cf_vendlist, ' (',
#suf := if (STRCMP(#last1, cf_vendlist) = 0,
#suf+1,
1 or #last := cf_vendlist), ')') as new_label
FROM (
SELECT t1.cf_vendlistid, t1.cf_vendlist, unique1.id as reference_id
FROM table_cf_vendlist t1
INNER JOIN (SELECT MIN(cf_vendlistid) as id, cf_vendlist
FROM table_cf_vendlist
GROUP BY cf_vendlist) AS unique1
ON t1.cf_vendlist = unique1.cf_vendlist
ORDER BY cf_vendlist, cf_vendlistid
) AS t1_sorted
WHERE t1_sorted.cf_vendlistid <> t1_sorted.reference_id)
AS t1_sorted_labeled
ON t.cf_vendlistid = t1_sorted_labeled.cf_vendlistid
SET t.cf_vendlist = t1_sorted_labeled.new_label;
works. But 1 or #last := cf_vendlist is a hack that allows to return 1 or 0(and) instead of cf_vendlist string (see my comment below)
I have a solution in sqlfiddle. To follow how it works,
1) look at the subquery t1_sorted
SELECT t1.cf_vendlistid, t1.cf_vendlist, unique1.id as reference_id
FROM table_cf_vendlist t1
INNER JOIN (SELECT MIN(cf_vendlistid) as id, cf_vendlist
FROM table_cf_vendlist
GROUP BY cf_vendlist) AS unique1
ON t1.cf_vendlist = unique1.cf_vendlist
WHERE t1.cf_vendlistid <> unique1.id
ORDER BY cf_vendlist, cf_vendlistid
which returns results like in fig. 1.
2a) In subquery t1_sorted_labeled, the results are projected to remove the first occurrence of the cf_vendlist in each group and
2b) an extra field new_label is created with the cf_vendlist and (copy number)
3) Finally, the original query is joined with this subquery in the UPDATE statement, which merely copies new_label to cf_vendlist
Thus, complete answer:
set #suf:=0;
set #last := 0;
UPDATE table_cf_vendlist t
INNER JOIN (SELECT cf_vendlistid, concat(cf_vendlist, ' (',
#suf := if (STRCMP(#last, cf_vendlist) = 0,
#suf+1,
1 and length(#last := cf_vendlist)), ')') as new_label
FROM (
SELECT t1.cf_vendlistid, t1.cf_vendlist, unique1.id as reference_id
FROM table_cf_vendlist t1
INNER JOIN (SELECT MIN(cf_vendlistid) as id, cf_vendlist
FROM table_cf_vendlist
GROUP BY cf_vendlist) AS unique1
ON t1.cf_vendlist = unique1.cf_vendlist
ORDER BY cf_vendlist, cf_vendlistid
) AS t1_sorted
WHERE t1_sorted.cf_vendlistid <> t1_sorted.reference_id)
AS t1_sorted_labeled
ON t.cf_vendlistid = t1_sorted_labeled.cf_vendlistid
SET t.cf_vendlist = t1_sorted_labeled.new_label;
BTW, you should really have an index on cf_vendlist.
I have a database of students.
CREATE TABLE classlist
(`id` int, `studentid` int, `subjectid` int, `presentid` int)
;
CREATE TABLE student
(`id` int, `name` varchar(4))
;
CREATE TABLE subject
(`id` int, `name` varchar(4))
;
CREATE TABLE classStatus
(`id` int, `name` varchar(8))
;
INSERT INTO classlist
(`id`, `studentid`, `subjectid`, `presentid`)
VALUES
(1, 111, 1, 1),
(2, 222, 3, 0),
(3, 333, 2, 1),
(4, 111, 4, 1),
(5, 111, 1, 0),
(6, 222, 3, 0),
(7, 333, 2, 1),
(8, 111, 4, 1),
(9, 111, 2, 0),
(10, 111, 4, 1),
(11, 111, 1, 1),
(12, 333, 3, 1),
(13, 333, 2, 1),
(14, 333, 3, 1)
;
INSERT INTO student
(`id`, `name`)
VALUES
(111, 'John'),
(222, 'Kate'),
(333, 'Matt')
;
INSERT INTO subject
(`id`, `name`)
VALUES
(1, 'MATH'),
(2, 'ENG'),
(3, 'SCI'),
(4, 'GEO')
;
INSERT INTO classStatus
(`id`, `name`)
VALUES
(0, 'Absent'),
(1, 'Present')
;
And I have a query which shows how many times they have been present or absent.
SELECT
studentid,
students.name AS NAME,
SUM(presentid = 1) AS present,
SUM(presentid = 0) AS absent
FROM classlist
INNER JOIN student as students ON classlist.studentid=students.id
GROUP BY studentid, NAME
See this fiddle below.
http://sqlfiddle.com/#!2/fe0b0/1
There seems to be a trend from looking at this sample data that after someone attends subjectid 4 they are often not coming to the next class. How can I capture this in a query. I want to ONLY show data WHERE last subjectid =4. So in my sample data rows matching my criteria would be.
(5, 111, 1, 0),
(9, 111, 2, 0),
(11, 111, 1, 1),
as these rows are all the next row of a studentid who had a subjectid=4.
My output would be
| STUDENTID | NAME | PRESENT | ABSENT|
| 111 | John | 1 | 2 |
To get the next class for a student, use a correlated subquery:
select cl.*,
(select min(cl2.id) from classlist cl2 where cl2.studentid = cl.studentid and cl2.id > cl.id) as nextcl
from classlist cl
Plugging this into your query example tell you you who is present and absent for the next class:
SELECT students.id, students.name AS NAME,
SUM(cl.presentid = 1) AS present, SUM(cl.presentid = 0) AS absent,
sum(clnext.presentid = 1) as presentnext
FROM (select cl.*,
(select min(cl2.id) from classlist cl2 where cl2.studentid = cl.studentid and cl2.id > cl.id) as nextcl
from classlist cl
) cl INNER JOIN
student as students
ON cl.studentid = students.id left outer join
classlist clnext
on cl.nextcl = clnext.id
GROUP BY students.id, students.NAME
Add a where cl.subjectid = 4 to get the answer for subject 4.
I fixed the query. The SQLFiddle is k.
A quick and dirty solution could be to get the Classlist.Id for all lines where subjectid=4 (let's call them n) then select all the lines where Id = n+1