I have a MySQL database and I want to SUM the lowest 3 Point by a Person.
+--------+--------+
| Person | Points |
+--------+--------+
| 1 | 15 |
| 1 | 10 |
| 1 | 5 |
| 1 | 10 |
| 2 | 5 |
| 2 | 4 |
| 2 | 3 |
| 2 | 2 |
| 3 | 1 |
| 3 | 1 |
| 3 | 1 |
+--------+--------+
The result what I want:
+-------+-----+
| 1 | 25 |
| 2 | 9 |
| 3 | 3 |
+-------+-----+
But I am really lost how to solve this. This is my Query Until now, :
SELECT person, SUM(points) FROM (SELECT SUM(points) FROM table
GROUP BY person ORDER BY points ASC LIMIT 3)
This is my SQL Create Script:
CREATE TABLE `mytable` (
`person` int(11) DEFAULT NULL,
`points` int(11) DEFAULT NULL
) ;
INSERT INTO `mytable` (`person`, `points`) VALUES
(1, 15),
(1, 10),
(1, 5),
(1, 10),
(2, 5),
(2, 4),
(2, 3),
(2, 2),
(3, 1),
(3, 1),
(3, 1);
Use a row number in the inner query as a helper to limit the rows.Row number and not limit to avoid the duplicate values problem.Not tested but it should work,too lazy to create a fiddle.
SELECT Person,SUM(Points) FROM
(SELECT Person,Points,
CASE Person
WHEN #person THEN #rank:= #rank+ 1
ELSE #rank:= 1
END AS rank,
#person:= person
FROM t, (SELECT #rank:= 0,#person:='') x
ORDER BY Points ASC)y
WHERE y.rank<=3
GROUP BY Persons
FIDDLE
Related
Well, sorry but I don't know how to formulate the subject correctly.
That's why I'd better describe the issue.
I have a kind of following table:
CREATE TABLE IF NOT EXISTS `Test` (
`Id` INT NOT NULL AUTO_INCREMENT,
`Name` VARCHAR(5) NOT NULL,
`v1` INT NOT NULL,
PRIMARY KEY (`Id`)
);
INSERT INTO `Test` (`Name`, `v1`)
VALUES ('A', 4), ('A', 3), ('B', 6), ('C', 1), ('C', 2),
('C', 7), ('D', 7), ('D', 5), ('E', 3), ('F', 1),
('F', 2), ('G', 7), ('H', 9), ('H', 1), ('I', 5);
What I need is to find all names that matches e.g. 7.
These are 'C', 'D' and 'G'.
And now I need to select all rows except 'C', 'D' and 'G'.
Resulting table is supposed to be:
===============
| Name | v1 |
|–––––––––––––|
| A | 4 |
| A | 3 |
| B | 6 |
| E | 3 |
| F | 1 |
| F | 2 |
| H | 9 |
| H | 1 |
| I | 5 |
|–––––––––––––|
I've tried the query:
select t1.Name, t1.v1
from `Test` as t1
join (
select Name from `Test`
where v1 = 7
) as t2
on t1.Name != t2.Name
but only realized that I still do not understand the mechanics of JOIN statements.
Hope for some help with this issue.
Thanks in advance.
This query:
SELECT Name FROM Test WHERE v1 = 7
returns all the Names that you want to exclude.
Use it with the operator NOT IN:
SELECT *
FROM Test
WHERE Name NOT IN (SELECT Name FROM Test WHERE v1 = 7)
See the demo.
Results:
> Id | Name | v1
> -: | :--- | -:
> 1 | A | 4
> 2 | A | 3
> 3 | B | 6
> 9 | E | 3
> 10 | F | 1
> 11 | F | 2
> 13 | H | 9
> 14 | H | 1
> 15 | I | 5
You can use not exists:
select t.*
from test t
where not exists (select 1
from test t2
where t2.name = t.name and t2.v1 = 7
);
This selects all rows in test where the name on the row has no row with 7, anywhere in the table.
Here's how to do it with JOINs...
SELECT x.*
FROM test x
LEFT
JOIN test y
ON y.name = x.name
AND y.v1 = 7
WHERE y.id IS NULL;
+----+------+----+
| Id | Name | v1 |
+----+------+----+
| 1 | A | 4 |
| 2 | A | 3 |
| 3 | B | 6 |
| 9 | E | 3 |
| 10 | F | 1 |
| 11 | F | 2 |
| 13 | H | 9 |
| 14 | H | 1 |
| 15 | I | 5 |
+----+------+----+
I have the following MySQL tables:
CREATE TABLE notification
(`id` int, `person_id` int, `rule_id` int, `account_id` int, `account_display_name` varchar(16))
;
INSERT INTO notification
(`id`, `person_id`, `rule_id`, `account_id`, `account_display_name`)
VALUES
(1, 1, 1, 1, 'Muad''Dib'),
(2, 1, 1, 2, 'Kwisatz Haderach'),
(3, 1, 2, 2, 'Kwisatz Haderach'),
(4, 2, 1, 3, 'Duncan'),
(5, 2, 2, 4, 'Duncan Idaho')
;
CREATE TABLE person
(`id` int, `name` varchar(6), `organization_id` int)
;
INSERT INTO person
(`id`, `name`, `organization_id`)
VALUES
(1, 'paul', 1),
(2, 'duncan', 1),
(3, 'paul', 2),
(4, 'duncan', 2),
(5, 'paul', 3),
(6, 'duncan', 3)
;
CREATE TABLE account
(`id` int, `display_name` varchar(16), `person_id` int)
;
INSERT INTO account
(`id`, `display_name`, `person_id`)
VALUES
(1, 'Muad''Dib', 1),
(2, 'Kwisatz Haderach', 1),
(3, 'Duncan', 2),
(4, 'Duncan Idaho', 2),
(5, 'Muad''Dib', 3),
(6, 'Kwisatz Haderach', 3),
(7, 'Duncan', 4),
(8, 'Duncan Idaho', 4),
(9, 'Muad''Dib', 5),
(10, 'Kwisatz Haderach', 5),
(11, 'Duncan', 6),
(12, 'Duncan Idaho', 6)
;
CREATE TABLE organization
(`id` int, `name` varchar(17))
;
INSERT INTO organization
(`id`, `name`)
VALUES
(1, 'atreides'),
(2, 'atreides_dev'),
(3, 'atreides_research')
;
CREATE TABLE rule
(`id` int, `name` varchar(14))
;
INSERT INTO rule
(`id`, `name`)
VALUES
(1, 'bug'),
(2, 'false_positive')
;
Conceptually, the system in question generates a notification when an event occurs that is considered relevant to person. Whether or not a notification gets generated is determined by rule (the relation between person and rule is defined elsewhere, but that's outside the scope of this question). Every notification that gets generated for a person is specifically related to an account owned by that person, and it also contains the (denormalized) account_display_name of the account.
Notice that a person is also related to exactly one organization.
Currently, I have a set of notifications (with related accounts) for multiple persons in a particular organization. But I need to move these notifications to different replica organizations (with replica persons and accounts) based on the rule that caused the notification.
Below is an example of the type of migration I'm trying to perform.
State of related tables
person
+----+--------+-----------------+
| id | name | organization_id |
+----+--------+-----------------+
| 1 | paul | 1 |
| 2 | duncan | 1 |
| 3 | paul | 2 |
| 4 | duncan | 2 |
| 5 | paul | 3 |
| 6 | duncan | 3 |
+----+--------+-----------------+
organization
+----+-------------------+
| id | name |
+----+-------------------+
| 1 | atreides |
| 2 | atreides_dev |
| 3 | atreides_research |
+----+-------------------+
account
+----+------------------+-----------+
| id | display_name | person_id |
+----+------------------+-----------+
| 1 | Muad'Dib | 1 |
| 2 | Kwisatz Haderach | 1 |
| 3 | Duncan | 2 |
| 4 | Duncan Idaho | 2 |
| 5 | Muad'Dib | 3 |
| 6 | Kwisatz Haderach | 3 |
| 7 | Duncan | 4 |
| 8 | Duncan Idaho | 4 |
| 9 | Muad'Dib | 5 |
| 10 | Kwisatz Haderach | 5 |
| 11 | Duncan | 6 |
| 12 | Duncan Idaho | 6 |
+----+------------------+-----------+
rule
+----+----------------+
| id | name |
+----+----------------+
| 1 | bug |
| 2 | false_positive |
+----+----------------+
Initial state of notifications
+----+-----------+---------+------------+----------------------+
| id | person_id | rule_id | account_id | account_display_name |
+----+-----------+---------+------------+----------------------+
| 1 | 1 | 1 | 1 | Muad'Dib |
| 2 | 1 | 1 | 2 | Kwisatz Haderach |
| 3 | 1 | 2 | 2 | Kwisatz Haderach |
| 4 | 2 | 1 | 3 | Duncan |
| 5 | 2 | 2 | 4 | Duncan Idaho |
+----+-----------+---------+------------+----------------------+
End state of notifications
+----+-----------+---------+------------+----------------------+
| id | person_id | rule_id | account_id | account_display_name |
+----+-----------+---------+------------+----------------------+
| 1 | 3 | 1 | 5 | Muad'Dib |
| 2 | 3 | 1 | 6 | Kwisatz Haderach |
| 3 | 5 | 2 | 10 | Kwisatz Haderach |
| 4 | 4 | 1 | 7 | Duncan |
| 5 | 6 | 2 | 12 | Duncan Idaho |
+----+-----------+---------+------------+----------------------+
As you can see, the person_id and account_id of the notifications have been updated based on the notification's rule_id. Specifically:
Every notification generated by the bug rule (ID 1) was moved to the corresponding person and account replicas in the atreides_dev organization (ID 2).
Every notification generated by the false_positive rule (ID 2) was moved to the corresponding person and account replicas in the atreides_research organization (ID 3).
I've written a SQL query that (I believe) selects the correct notification.person_id and notification.account_id based on notification.rule_id, but I'm unsure of the best way to use this in an UPDATE statement, such that every notification gets updated correctly.
Here is the SELECT:
select p_replica.id, acc_replica.id as account_id
from (select p.name, person_id, rule_id, account_id, account_display_name from notification n
inner join person p
on n.person_id = p.id
where p.organization_id = 1
and n.rule_id in (1)) as notifications
inner join person p_replica
on p_replica.name = notifications.name
and p_replica.organization_id = 2
inner join account acc_replica
on acc_replica.person_id = p_replica.id
and acc_replica.display_name = notifications.account_display_name;
Notice that the SELECT specifies an origin organization (ID 1), a destination organization (ID 2), and a rule whose notifications need to be migrated. Recall that this is based on a mapping of rule to destination organization. In other words, selecting all updated notifications would require one execution of this query per mapping of rule -> organization.
What would an UPDATE query for performing this migration look like?
Here is a SQL Fiddle to play around with.
I have 3 tables. Table A stores stock and category relationship. Add date indicates when the stock was added to the category and remove date means the day which the stock was removed. if remove date is null , it means the stock is still in the category.
+---+----------+ ------- +------------+------------+
|id | Stock_id |Category | Add Date | Remove Date|
+---+----------+-------- +------------+------------+
| 1 | 1 | CategoryA| 2017-09-03 | 2017-09-07 |
| 2 | 1 | CategoryA|2017-09-11 | null |
| 3 | 2 | CategoryA| 2017-09-06 | null |
+---+----------+-------- +------------+------------+
Table B stores stock transaction amount by days.
+---+------------+----------+-------------+
|id | Stock_id | amount | Date |
+---+------------+----------+-------------+
| 1 | 1 | 100 | 2017-09-04 |
| 2 | 1 | 100 | 2017-09-05 |
| 3 | 1 | 100 | 2017-09-06 |
| 4 | 1 | 100 | 2017-09-07 |
| 5 | 1 | 100 | 2017-09-08 |
| 6 | 1 | 100 | 2017-09-09 |
| 7 | 2 | 100 | 2017-09-05 |
| 8 | 2 | 200 | 2017-09-06 |
....
| 2 | 2 | 200 | 2017-09-10 |
+---+------------+----------+-------------+
Table C stores category transaction amount by days.
+---+------------+----------+-------------+
|id | Category | amount | Date |
+---+------------+----------+-------------+
| 1 | A | 300 | 2017-09-04 |
| 2 | A | 300 | 2017-09-05 |
| 3 | A | 300 | 2017-09-06 |
| 4 | A | 300 | 2017-09-07 |
| 5 | A | 300 | 2017-09-08 |
+---+------------+----------+-------------+
What I want to do is in a given period, such as from 2017-09-04 to 2017-09-08, 1)sum the category amount in the period, 2)and then sum all the stock's amount by id which is in the category in the period. 3) the divide the 2 by 1 to calculate the ratio. for STOCK ID1, for it was removed on 09-07, sql should only calculate it's amount for 3 days records(09-03/09.06). for STOCK ID2, it was added on 09-06, sql should only calculate it's amount for 3 days from 09.06 to 09.08. The sum amount for category in Table C is simple, just sum 5 days.
The result What I expect is
+---+----------+ ------- +
|id | Stockid |Result |
+---+----------+-------- +
| 1 | 1 | 0.2 | # (100+100+100)/300*5
| 2 | 2 | 0.4 | # (200+200+200)/300*5
How can I do it? Thank you all!
I found the code example a bit tricky to follow but here's an attempt. Note that although this may not solve your problem directly, hopefully you will be able to apply the approach in general to solve your issue.
CREATE TABLE tableA(id INT, stockId INT, category CHAR(1), addDate DATE, removeDate DATE);
INSERT INTO tableA VALUES
(1, 1, 'A', '2017-09-03', '2017-09-07'),
(2, 1, 'A', '2017-09-11', null),
(3, 2, 'A', '2017-09-06', null);
CREATE TABLE tableB(id INT, stockId INT, amount INT, `date` DATE);
INSERT INTO tableB VALUES
(1, 1, 100, '2017-09-04'),
(2, 1, 100, '2017-09-05'),
(3, 1, 100, '2017-09-06'),
(4, 1, 100, '2017-09-07'),
(5, 1, 100, '2017-09-08'),
(6, 1, 100, '2017-09-09'),
(7, 2, 100, '2017-09-05'),
(8, 2, 200, '2017-09-06'),
(9, 2, 200, '2017-09-06'),
(10, 2, 200, '2017-09-10');
CREATE TABLE tableC (id INT, category CHAR(1), amount INT, `date` DATE);
INSERT INTO tableC VALUES
(1, 'A', 300, '2017-09-04'),
(2, 'A', 300, '2017-09-05'),
(3, 'A', 300, '2017-09-06'),
(4, 'A', 300, '2017-09-07'),
(5, 'A', 300, '2017-09-08');
SELECT table_AB.tableAB_SUM / table_B.tableB_SUM * TIMESTAMPDIFF(DAY, '2017-09-04', '2017-09-08') FROM
(
SELECT tableB.stockId, SUM(tableB.amount) tableAB_SUM FROM
tableB
LEFT OUTER JOIN
tableA
ON tableB.stockId = tableA.stockId
WHERE tableB.`date` BETWEEN '2017-09-04' AND '2017-09-08' AND (NOT tableA.addDate > '2017-09-08' OR NOT IFNULL(tableA.removeDate, CURDATE()) < '2017-09-08')
GROUP BY tableA.stockId
) table_AB
LEFT OUTER JOIN
(
SELECT stockId, SUM(amount) tableB_SUM FROM tableB WHERE `date` BETWEEN '2017-09-04' AND '2017-09-08' GROUP BY stockId
) table_B
ON table_AB.stockID = table_B.stockID;
Let me know if anythings unclear.
Regards,
James
Here is my table and the data contained in it:
Table: first
+----------+------+
| first_id | data |
+----------+------+
| 1 | 5 |
| 2 | 6 |
| 3 | 7 |
| 4 | 6 |
| 5 | 7 |
| 6 | 5 |
| 7 | 7 |
| 8 | 6 |
| 9 | 5 |
| 10 | 7 |
+----------+------+
Table: second
+-----------+----------+----------+
| second_id | first_id | third_id |
+-----------+----------+----------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 3 | 4 |
| 4 | 4 | 2 |
| 5 | 5 | 3 |
| 6 | 6 | 4 |
| 7 | 7 | 2 |
| 8 | 8 | 2 |
| 9 | 9 | 4 |
| 10 | 10 | 4 |
+-----------+----------+----------+
My intention is to get the list of third_ids ordered by data field. Now, I ran the following query for that.
SELECT
third_id, data
FROM
first f JOIN second s ON ( s.first_id = f.first_id )
ORDER BY
data ASC;
And I get the following result as expected.
+----------+------+
| third_id | data |
+----------+------+
| 4 | 5 |
| 2 | 5 |
| 4 | 5 |
| 2 | 6 |
| 3 | 6 |
| 2 | 6 |
| 2 | 7 |
| 4 | 7 |
| 4 | 7 |
| 3 | 7 |
+----------+------+
The following query is also work as expected.
SELECT
third_id
FROM
first f JOIN second s ON ( s.first_id = f.first_id )
ORDER BY
data ASC;
with output
+----------+
| third_id |
+----------+
| 4 |
| 2 |
| 4 |
| 2 |
| 3 |
| 2 |
| 2 |
| 4 |
| 4 |
| 3 |
+----------+
Then I ran the following.
SELECT DISTINCT
third_id
FROM
first f JOIN second s ON ( s.first_id = f.first_id )
ORDER BY
data ASC;
But, here I get an unexpected result:
+----------+
| third_id |
+----------+
| 2 |
| 3 |
| 4 |
+----------+
Here, 3 must be after 2 and 4, since I am ordering on the data field. What am I doing wrong? Or do I have to go for a different strategy.
Note:
This scenario happens on my project. The tables provided here doesn't belong to original database. It is created by me to explain the problem. Original tables contain thousands of rows.
I am inserting database dump if you would like to experiment with the data:
--
-- Table structure for table `first`
--
CREATE TABLE IF NOT EXISTS `first` (
`first_id` int(11) NOT NULL AUTO_INCREMENT,
`data` int(11) NOT NULL,
PRIMARY KEY (`first_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `first`
--
INSERT INTO `first` (`first_id`, `data`) VALUES
(1, 5),
(2, 6),
(3, 7),
(4, 6),
(5, 7),
(6, 5),
(7, 7),
(8, 6),
(9, 5),
(10, 7);
--
-- Table structure for table `second`
--
CREATE TABLE IF NOT EXISTS `second` (
`second_id` int(11) NOT NULL AUTO_INCREMENT,
`first_id` int(11) NOT NULL,
`third_id` int(11) NOT NULL,
PRIMARY KEY (`second_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=11 ;
--
-- Dumping data for table `second`
--
INSERT INTO `second` (`second_id`, `first_id`, `third_id`) VALUES
(1, 1, 2),
(2, 2, 3),
(3, 3, 4),
(4, 4, 2),
(5, 5, 3),
(6, 6, 4),
(7, 7, 2),
(8, 8, 2),
(9, 9, 4),
(10, 10, 4);
You probably want to do something like
SELECT third_id
FROM first JOIN second USING (first_id)
GROUP BY third_id
ORDER BY aggregatesomething(data)
that is min(data) or max(data) or whatever.
Doing a SELECT DISTINCT requires the database to order the values in the column(s) as that is the most efficient way to find the distinct values. As far as I'm aware ORDER BY clauses that do not contain columns that are outputted in the query do not get honoured (SQL SERVER won't accept the query) as it is not clear what it would mean to order by something that did not participate.
You may use a subquery -
SELECT DISTINCT third_id FROM (
SELECT
third_id
FROM
first f JOIN second s ON ( s.first_id = f.first_id )
ORDER BY
data ASC
) t;
It will help to select and sort all data firstly, then to select distinct values.
I had this exact problem before. I finally came up with a simple solution, almost seems too simple. You need to use a subquery as a column of the select query. In that subquery is where you will do the ordering by date. When you do it all in a single query with ORDER BY happens before the JOIN. You want to order first, so go with the subquery. http://nathansnoggin.blogspot.com/2009/04/select-distinct-with-order-by.html
I have a table that tracks contact class state changes by date. The question that I am trying to answer is what is the current state of all contacts on a certain date.
DROP TABLE IF EXISTS `contact_class_state`;
CREATE TABLE `contact_class_state` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`contact_id` int unsigned DEFAULT NULL, -- the contact
`contact_class` int unsigned,
`state_date` date,
PRIMARY KEY (`id`),
INDEX (`contact_id`)
) DEFAULT CHARSET=utf8;
INSERT INTO `contact_class_state` (`contact_id`, `contact_class`, `state_date`) VALUES
(1, 1, '2011-01-01'),
(2, 1, '2011-01-01'),
(3, 1, '2011-01-01'),
(4, 1, '2011-01-01'),
(5, 1, '2011-01-01'),
(1, 2, '2011-02-01'),
(3, 2, '2011-02-01'),
(5, 2, '2011-02-01'),
(1, 1, '2011-02-15'),
(5, 3, '2011-03-01');
For example, the following query:
SELECT contact_id, contact_class, state_date
FROM contact_class_state
WHERE state_date <= '2011-02-27'
ORDER BY contact_id, state_date DESC
returns
+------------+---------------+------------+
| contact_id | contact_class | state_date |
+------------+---------------+------------+
| 1 | 1 | 2011-02-15 |
| 1 | 2 | 2011-02-01 |
| 1 | 1 | 2011-01-01 |
| 2 | 1 | 2011-01-01 |
| 3 | 2 | 2011-02-01 |
| 3 | 1 | 2011-01-01 |
| 4 | 1 | 2011-01-01 |
| 5 | 2 | 2011-02-01 |
| 5 | 1 | 2011-01-01 |
+------------+---------------+------------+
While this is technically correct, I only need the first (or last if sorted ASC) row for each contact_id as the latest date will always give me current state of the contact, per the below:
+------------+---------------+------------+
| contact_id | contact_class | state_date |
+------------+---------------+------------+
| 1 | 1 | 2011-02-15 |
| 2 | 1 | 2011-01-01 |
| 3 | 2 | 2011-02-01 |
| 4 | 1 | 2011-01-01 |
| 5 | 2 | 2011-02-01 |
+------------+---------------+------------+
I am pretty sure a sub or a complex query would do the trick but I am having a mental block with the SQL. I am also open to other approaches to solve this issue.
Thanks!
If your query is in fact what you want (except then grouped by contact_id), then do exactly that.
SELECT * FROM
(SELECT contact_id, contact_class, state_date
FROM contact_class_state
WHERE state_date <= '2011-02-27'
ORDER BY contact_id, state_date DESC) table1
GROUP BY contact_id
This is tested and works perfect.