How to select rows with matching fields in MySQL? - mysql

I have 2 tables, one with price overrides and one with minute overrides, each per user and product. How can I select them so that I have a record for each user and product?
Table 1: Override Prices
+--------+-----------+---------------+
| userID | productID | overridePrice |
+--------+-----------+---------------+
| 4 | 53 | 99.99 |
| 4 | 13 | 99.99 |
| 4 | 55 | 99.99 |
+--------+-----------+---------------+
Table 2: Override Minutes
+--------+-----------+---------------+
| userID | productID | overrideMin |
+--------+-----------+---------------+
| 4 | 18 | 23 |
| 4 | 55 | 4 |
| 50 | 55 | 2 |
+--------+-----------+---------------+
The table I want it to produce:
Table 2: All overrides
+--------+-----------+-------------+---------------+
| userID | productID | overrideMin | overridePrice |
+--------+-----------+-------------+---------------+
| 4 | 13 | null | 99.99 |
| 4 | 18 | 23 | null |
| 4 | 53 | null | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | null |
+--------+-----------+-------------+---------------+
I've attempted to GROUP BY userID, productID, but because product IDs may exist in table 1 that don't exist in table 2, I get different results depending on which productID I group by.

Use UNION:
SELECT userID, productID,
MAX(overrideMin) AS overrideMin,
MAX(overridePrice) AS overridePrice
FROM
(
SELECT userID, productID, null AS overrideMin, overridePrice
FROM OverridePrices
UNION
SELECT userID, productID, overrideMin, null AS overridePrice
FROM OverrideMinutes
) AS t
GROUP BY userID, productID;
This will give you the exact results you are looking for:
| userID | productID | overrideMin | overridePrice |
|--------|-----------|-------------|---------------|
| 4 | 13 | (null) | 99.99 |
| 4 | 18 | 23 | (null) |
| 4 | 53 | (null) | 99.99 |
| 4 | 55 | 4 | 99.99 |
| 50 | 55 | 2 | (null) |

You have to use IF statement:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM OverridePrices AS t1
LEFT JOIN OverrideMinutes AS t2 ON t1.userID = t2.userID AND t1.productID = t2.productID;
But in case you can have different products in table1 and table2 you have to join to table which contains all products, like:
SELECT
t1.userID,
t1.productID,
if (t2.overrideMin IS NULL, NULL, t2.overrideMin) AS overrideMin,
if (t1.overridePrice IS NULL, NULL, t1.overridePrice) AS overridePrice
FROM (
SELECT userID, productID FROM OverridePrices
UNION
SELECT userID, productID FROM OverrideMinutes
) AS t0
LEFT JOIN OverridePrices AS t1 ON t0.userID = t1.userID AND t0.productID = t1.productID
LEFT JOIN OverrideMinutes AS t2 ON t0.userID = t2.userID AND t0.productID = t2.productID;
Also, now you can have GROUP, HAVING etc.

SELECT table1.userID,table1.productID,table2.overrideMin,table1.overridePrice FROM table1
LEFT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
UNION SELECT table2.userID,table2.productID,table2.overrideMin,table1.overridePrice FROM table1
RIGHT JOIN table2 ON table1.userID=table2.userID AND table1.productID = table2.productID
ORDER BY userID, productID
This is the OUTPUT

Related

MYSQL combine and select rows by two reference tables

I am losing my mind over this problem, research has not helped. Is this an anti-pattern and hard because of that, or is it just me...
The IDEA here is that we have a single table for every material we use, we have multiple storage shelf's in which that material is stored on. Every stocklocation has individual quantities and minimum limits.
What I want to achieve is a single SQL-query that outputs this:
+-------+------+---------------------+----------+-----------------+
| SL_Id | M_Id | TimeCreated | Quantity | MinimumQuantity |
+-------+------+---------------------+----------+-----------------+
| 1 | 2 | 2017-11-02 13:04:18 | 10 | 5 |
| 1 | 3 | NULL | NULL | 15 |
| 1 | 4 | 2017-11-02 15:56:56 | 7 | NULL |
+-------+------+---------------------+----------+-----------------+
This is where I am at the moment....
+-------+------+---------------------+----------+-----------------+
| SL_Id | M_Id | TimeCreated | Quantity | MinimumQuantity |
+-------+------+---------------------+----------+-----------------+
| 1 | 2 | 2017-11-02 13:04:18 | 10 | NULL |
| 1 | 3 | NULL | NULL | 15 |
| 1 | 4 | 2017-11-02 15:56:56 | 7 | NULL |
+-------+------+---------------------+----------+-----------------+
Here are the tables:
Material
+----+-----------+
| Id | OrderCode |
+----+-----------+
| 2 | asdf |
| 3 | 75424 |
| 4 | 45567 |
+----+-----------+
StockLocation
+----+-------+
| Id | Label |
+----+-------+
| 1 | asdf |
+----+-------+
Inventory
+----+------------------+-------------+---------------------+----------+
| Id | StockLocation_Id | Material_Id | TimeCreated | Quantity |
+----+------------------+-------------+---------------------+----------+
| 1 | 1 | 2 | 2017-11-02 13:04:18 | 10 |
| 2 | 1 | 4 | 2017-11-02 15:23:26 | 9 |
| 3 | 1 | 4 | 2017-11-02 15:56:56 | 7 |
+----+------------------+-------------+---------------------+----------+
StockLocationMaterialLimit
+----+------------------+-------------+-----------------+
| Id | StockLocation_Id | Material_Id | MinimumQuantity |
+----+------------------+-------------+-----------------+
| 1 | 1 | 2 | 5 |
| 2 | 1 | 3 | 15 |
+----+------------------+-------------+-----------------+
This is the monster behind failure,
SELECT
SL_Id,
M_Id,
TimeCreated,
Quantity,
MinimumQuantity
FROM (
SELECT
SL.Id AS SL_Id,
M.Id AS M_Id,
I.TimeCreated AS TimeCreated,
I.Quantity AS Quantity,
NULL AS MinimumQuantity
FROM StockLocation SL, Material M
JOIN Inventory I on I.Id = (
SELECT Id FROM Inventory I1 WHERE I1.StockLocation_Id=SL.Id AND I1.Material_Id=M.Id ORDER BY I1.TimeCreated DESC LIMIT 1
)
UNION
SELECT
SL.Id AS SL_Id,
M.Id AS M_Id,
NULL AS TimeCreated,
NULL AS Quantity,
SLML.MinimumQuantity AS MinimumQuantity
FROM StockLocation SL, Material M
JOIN StockLocationMaterialLimit SLML on SLML.Id = (
SELECT Id FROM StockLocationMaterialLimit SLML1 WHERE SLML1.StockLocation_Id=SL.Id AND SLML1.Material_Id=M.Id LIMIT 1
)
) tst GROUP BY SL_Id,M_Id
It turned out to be as easy as adding MAX() on those fields which produced NULL values.
SELECT
M_Id,
SL_Id,
TimeCreated,
MAX(Quantity) AS Quantity,
MAX(MinimumQuantity) AS MinimumQuantity
FROM (
SELECT
M.Id AS M_Id,
SL.Id AS SL_Id,
I.TimeCreated AS TimeCreated,
I.Quantity AS Quantity,
NULL AS MinimumQuantity
FROM StockLocation SL, Material M
INNER JOIN Inventory I on I.Id = (
SELECT Id FROM Inventory I1 WHERE I1.StockLocation_Id=SL.Id AND I1.Material_Id=M.Id ORDER BY I1.TimeCreated DESC LIMIT 1
)
UNION
SELECT
M.Id AS M_Id,
SL.Id AS SL_Id,
NULL AS TimeCreated,
NULL AS Quantity,
SLML.MinimumQuantity AS MinimumQuantity
FROM StockLocation SL, Material M
INNER JOIN StockLocationMaterialLimit SLML on SLML.Id = (
SELECT Id FROM StockLocationMaterialLimit SLML1 WHERE SLML1.StockLocation_Id=SL.Id AND SLML1.Material_Id=M.Id LIMIT 1
)
) tst GROUP BY SL_Id, M_Id;

Mysql count column values and merge columns

I was having problems in creating counting rows by grouping based on a given field value.
For example: I have a Table A structure like this:
+------+------------+
| id | Person |
+------+------------+
| 1 | "Sandy" |
| 2 | "Piper" |
| 3 | "Candy" |
| 4 | "Pendy" |
+------------+------+
Also I have a Table B structure like this:
+------+------------+---------+
| id | Person | Point |
+------+------------+---------+
| 1 | "Sandy" | 10 |
| 2 | "Piper" | 20 |
| 3 | "Candy" | 30 |
| 4 | "Sandy" | 10 |
| 5 | "Piper" | 20 |
| 6 | "Zafar" | 30 |
+------------+------+---------+
And needed a result like:
+------+------------+---------+
| id | Person | Point |
+------+------------+---------+
| 1 | "Piper" | 40 |
| 2 | "Candy" | 30 |
| 3 | "Zafar" | 30 |
| 4 | "Sandy" | 20 |
| 5 | "Pendy" | 0 |
+------------+------+---------+
I hope the table examples are itself self-explanatory.
SELECT person
, SUM(point) total
FROM
( SELECT person,point FROM table_b
UNION
ALL
SELECT person,0 FROM table_a
) x
GROUP
BY person
ORDER
BY total DESC;
It is a simple left join with a group by
select tableA.person, sum(tableB.points) from tableA left join tableB on tableA.person = tableB.person group by tableA.person
union
select tableB.person, sum(tableB.points) from tableB left join tableA on tableA.person = tableB.person where tableA.id is null group by tableA.person
I think below sql useful to you.
select a.id, a.Person,b.total_point from (
select id, Person from tablea) as a join
(select Person, sum(Point) as total_point from tableb group by person) as b on a.person =b.person
Thank you

MySQL: Get the totals from two columns organised by category

Considering the following tables in a MYSQL database:
table1:
+------+-----------+----------------------+
| id | atual | category_id | user |
+------+-----------+--------------|-------+
| 1 | 100 | 1 | 1 |
| 2 | 150 | 2 | 1 |
| 3 | 50 | 1 | 2 |
+------+-----------+--------------|-------+
table2:
+------+-----------+----------------------+
| id | budget | category_id | user |
+------+-----------+--------------|-------+
| 1 | 100 | 2 | 1 |
| 2 | 150 | 1 | 2 |
| 3 | 50 | 1 | 1 |
+------+-----------+--------------|-------+
table3:
+------+-----------+
| id | name |
+------+-----------+
| 1 | one |
| 2 | two |
| 3 | three |
+------+-----------+
I want to calculate the totals for 'atual' and 'budget' given in tables 1 and 2 for a given user (1 in my example), organized by category name:
I tried the following query, which is giving me the totals for atual and budget regardless of the categories:
SELECT table2.id, table3.name AS name_category, SUM( budget ) ,
(SELECT SUM( atual) FROM table1 WHERE user =1)
FROM table2 INNER JOIN table3
ON table2.category_id=table3.id
Here is a method:
select t3.id, t3.name, sum(actual) as actual, sum(budget) as budget
from ((select category_id, sum(actual) as actual, NULL as budget
from table1
where user = 1
group by category_id
) union all
(select category_id, NULL as actual, sum(budget) as budget
from table2
where user = 1
group by category_id
)
) ab join
table3 t3
on ab.category_id = t3.id
group by t3.id, t3.name;

MySQL - condition on the joined row from from right table

I have two tables:
mysql> select * from orders;
+------+---------------------+------------+---------+
| id | created_at | foreign_id | data |
+------+---------------------+------------+---------+
| 1 | 2010-10-10 10:10:10 | 3 | order 1 |
| 4 | 2010-10-10 00:00:00 | 6 | order 4 |
| 5 | 2010-10-10 00:00:00 | 7 | order 5 |
+------+---------------------+------------+---------+
mysql> select * from activities;
+------+---------------------+------------+------+
| id | created_at | foreign_id | verb |
+------+---------------------+------------+------+
| 1 | 2010-10-10 10:10:10 | 3 | get |
| 2 | 2010-10-10 10:10:15 | 3 | set |
| 3 | 2010-10-10 10:10:20 | 3 | put |
| 4 | 2010-10-10 00:00:00 | 6 | get |
| 5 | 2010-10-11 00:00:00 | 6 | set |
| 6 | 2010-10-12 00:00:00 | 6 | put |
+------+---------------------+------------+------+
Now I need to join activities with orders on foreign_id column: select only one activity (if exists) for every order such that ABS(TIMESTAMPDIFF(SECOND, orders.created_at, activities.created_at)) is minimal. E.g. the order and the activity were created approximately at the same time.
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+
The following query produces set of rows that includes the desired rows. If GROUP BY statement is included then it's not possible to control which row from activities is joined.
SELECT o.id AS order_id
, o.data AS data
, o.created_at AS order_created_at
, a.id AS activity_id
, a.verb AS verb
, a.created_at AS activity_created_at
FROM orders AS o
LEFT JOIN activities AS a ON a.foreign_id = o.foreign_id;
Is it possible to write such a query? Ideally I'd like to avoid using group by because this section is a part of larger reporting querty.
Because both tables reference some mysterious foreign key there's potential for errors with the query below, but it may give you a principle which you can adapt for your purposes...
DROP TABLE IF EXISTS orders;
CREATE TABLE orders
(id INT NOT NULL PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,data VARCHAR(20) NOT NULL
);
INSERT INTO orders VALUES
(1 ,'2010-10-10 10:10:10',3 ,'order 1'),
(4 ,'2010-10-10 00:00:00',6 ,'order 4'),
(5 ,'2010-10-10 00:00:00',7 ,'order 5');
DROP TABLE IF EXISTS activities;
CREATE TABLE activities
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,created_at DATETIME NOT NULL
,foreign_id INT NOT NULL
,verb VARCHAR(20) NOT NULL
);
INSERT INTO activities VALUES
(1,'2010-10-10 10:10:10',3,'get'),
(2,'2010-10-10 10:10:15',3,'set'),
(3,'2010-10-10 10:10:20',3,'put'),
(4,'2010-10-10 00:00:00',6,'get'),
(5,'2010-10-11 00:00:00',6,'set'),
(6,'2010-10-12 00:00:00',6,'put');
SELECT o.id order_id
, o.data
, o.created_at order_created_at
, a.id activity_id
, a.verb
, a.created_at activity_created_at
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
JOIN
( SELECT a.foreign_id
, MIN(ABS(TIMEDIFF(a.created_at,o.created_at))) x
FROM activities a
JOIN orders o
ON o.foreign_id = a.foreign_id
GROUP
BY a.foreign_id
) m
ON m.foreign_id = a.foreign_id
AND m.x = ABS(TIMEDIFF(a.created_at,o.created_at))
UNION DISTINCT
SELECT o.id
, o.data
, o.created_at
, a.id
, a.verb
, a.created_at
FROM orders o
LEFT
JOIN activities a
ON a.foreign_id = o.foreign_id
WHERE a.foreign_id IS NULL;
;
+----------+---------+---------------------+-------------+------+---------------------+
| order_id | data | order_created_at | activity_id | verb | activity_created_at |
+----------+---------+---------------------+-------------+------+---------------------+
| 1 | order 1 | 2010-10-10 10:10:10 | 1 | get | 2010-10-10 10:10:10 |
| 4 | order 4 | 2010-10-10 00:00:00 | 4 | get | 2010-10-10 00:00:00 |
| 5 | order 5 | 2010-10-10 00:00:00 | NULL | NULL | NULL |
+----------+---------+---------------------+-------------+------+---------------------+

Mysql Calculate rank of teams from different rows

I'm trying to build a kind of peddy paper. For that I have the following tables:
teams
CREATE TABLE `teams` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`creator_id` int(11) NOT NULL,
`friend_id` int(11) DEFAULT NULL,
`team_name` varchar(128) NOT NULL,
PRIMARY KEY (`id`)
);
team_log
CREATE TABLE IF NOT EXISTS `progress_tracker` (
`id` int(8) NOT NULL AUTO_INCREMENT,
`user_id` int(8) NOT NULL,
`team_id` int(11) NOT NULL,
`date` date NOT NULL,
`clues_found` int(11) NOT NULL,
`clues_to_find` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
Each team is composed by two users;
Each user starts out with a variable number of clues found;
clues_found can either increase or decrease. No guarantee that the highest number is the latest;
I need to get a rank of the teams (in percentage) based on the average of the number of clues the user found since they joined (for both users in a team) - clues_found on the row with biggest date minus clues_found on the record with lowest date).
For instance if I have the following data for each table:
teams table data
+--------+------------+------------+---------------+
| id | creator_id | friend_id | team_name |
+--------+------------+------------+---------------+
| 1 | 25 | 28 | Test1 |
| 2 | 31 | 5 | Test2 |
+--------+------------+------------+---------------+
team_log table data
+--------+---------+---------+------------+-------------+---------------+
| id | user_id | team_id | date | clues_found | clues_to_find |
+--------+---------+---------+------------+-------------+---------------+
| 1 | 25 | 1 | 2013-01-6 | 3 | 24 |
| 2 | 25 | 1 | 2013-01-8 | 7 | 24 |
| 3 | 25 | 1 | 2013-01-10 | 10 | 24 |
| 4 | 28 | 1 | 2013-01-8 | 5 | 30 |
| 5 | 28 | 1 | 2013-01-14 | 20 | 30 |
| 6 | 31 | 2 | 2013-01-11 | 6 | 14 |
| 7 | 5 | 2 | 2013-01-9 | 2 | 20 |
| 8 | 5 | 2 | 2013-01-10 | 10 | 20 |
| 9 | 5 | 2 | 2013-01-12 | 14 | 20 |
+--------+---------+---------+------------+-------------+---------------+
Desired Result
+-------------+---------------------+
| team_id | team_percentage |
+-------------+---------------------+
| 1 | 39,58333333 |
| 2 | 30 |
+-------------+---------------------+
As a reference this is an intermediate representation which might help to understand:
+-------------+---------+---------------------+
| user_id | team_id | precentage_per_user |
+-------------+---------+---------------------+
| 25 | 1 | 29,16666667 |
| 28 | 1 | 50 |
| 31 | 2 | 0 |
| 5 | 2 | 60 |
+-------------+---------+---------------------+
So far I have the following sql:
SELECT STRAIGHT_JOIN
tl2.team_id, (tl2.weight - tl1.weight)*100/tl2.clues_to_find
from
( select
team_id,user_id,clues_found
FROM
`team_log`
where 1
group by
team_id, user_id
order by
`date` ) base
join (select team_id, user_id, clues_found, clues_to_find from `team_log` where user_id = base.user_id and team_id = base.team_id group by team_id, user_id order by `date` desc) tl2
But this returns an error as I'm not allowed to use base.user_id inside the second query. I'm also not very sure I'm heading in the right direction.
Can anyone help please?
Here's another query that will produce the correct result:
SELECT calc.team_id, AVG((calc.end_clues - calc.start_clues)/calc.total_clues*100) as team_percentage
FROM
(SELECT log1.user_id, log1.team_id, log1.clues_found as start_clues, log2.clues_found as end_clues, log2.clues_to_find as total_clues FROM team_log log1
JOIN
(SELECT MIN(id) as start_id, MAX(id) as end_id FROM team_log GROUP BY user_id) ids
ON ids.start_id = log1.id
JOIN team_log log2 ON ids.end_id = log2.id) calc
GROUP BY team_id
ORDER BY team_id;
And the SQL Fiddle-link...
Please take a look at this and comment:
SQLFIDDLE DEMO
Team pct:
select z.team_id, avg(z.pct) as teampct
from (
select x.user_id, y.team_id, x.mndate,
y.mxdate, x.mnclues_found,
y.mxclues_found,
(((y.mxclues_found - x.mnclues_found)*100)
/y.mxclues_tofind) pct
from
(select user_id, team_id, min(date) mndate,
min(clues_found) as mnclues_found
from team_log
group by user_id, team_id) x
left join
(select user_id, team_id, max(date) mxdate,
max(clues_found) as mxclues_found,
max(clues_to_find) as mxclues_tofind
from team_log
group by user_id, team_id) y
on x.user_id = y.user_id and
x.team_id = y.team_id) z
group by z.team_id
;
Results 1:
| USER_ID | TEAM_ID | MNDATE | MXDATE | MNCLUES_FOUND | MXCLUES_FOUND | PCT |
-------------------------------------------------------------------------------------
| 5 | 2 | 13-01-09 | 13-01-12 | 2 | 14 | 60 |
| 25 | 1 | 13-01-06 | 13-01-10 | 3 | 10 | 29.1667 |
| 28 | 1 | 13-01-08 | 13-01-14 | 5 | 20 | 50 |
| 31 | 2 | 13-01-11 | 13-01-11 | 6 | 6 | 0 |
Results final:
| TEAM_ID | TEAMPCT |
----------------------
| 1 | 39.58335 |
| 2 | 30 |
This is a bit ugly, but should work:
select
team_id,
AVG(percentage_per_user) as team_percentage
from (select
team_id,
user_id,
((select clues_found from progress_tracker as x
where x.user_id = m.user_id order by x.date desc limit 0, 1)
- (select clues_found from progress_tracker as y
where y.user_id = m.user_id order by y.date asc limit 0, 1))
/ MAX(clues_to_find)
as percentage_per_user
from progress_tracker as m
group by team_id, user_id
) as userScore
group by team_id
order by team_percentage desc;
Note the inner query run by itself will yield your intermediate "per-user" result.
SQLFiddle
SELECT `team_id`,
(SUM(CASE WHEN b.`date` IS NULL THEN 0 ELSE `clues_found` * 100 / `clues_to_find` END) -
SUM(CASE WHEN c.`date` IS NULL THEN 0 ELSE `clues_found` * 100 / `clues_to_find` END)) / 2
FROM `team_log` a
LEFT JOIN (
SELECT `team_id`, `user_id`, MAX(date) AS `date`
FROM `team_log`
GROUP BY `team_id`, `user_id`) b
USING (`team_id`, `user_id`, `date`)
LEFT JOIN (
SELECT `team_id`, `user_id`, MIN(date) AS `date`
FROM `team_log`
GROUP BY `team_id`, `user_id`) c
USING (`team_id`, `user_id`, `date`)
GROUP BY `team_id`
Since you say there are always two team members, I've used /2. It would be slightly more complex for variable-sized teams.