Order by the difference of two selects in MySQL - mysql

I have a table like this:
groupX groupY quantity
A B 10
A C 2
C D 7
B A 13
C B 1
D B 9
So, the same individuals appear at columns groupX and groupY. I would like to write a Select that makes the following:
Select
(Select groupX, sum(quantity) group by groupX) as M
- (Select groupY, sum(quantity)
group by groupY) as N
ORDER BY M-N Desc
I mean, I need to sum the quantities for each individual when they appear at groupX and when they appear at groupY and then calculate the difference for each individual between first and second quantity. Finally I need the query to order the individuals by that difference.
Of course the query I wrote does not work.

With this table as source
CREATE TABLE tableA
(`groupX` varchar(1), `groupY` varchar(1), `quantity` int)
;
INSERT INTO tableA
(`groupX`, `groupY`, `quantity`)
VALUES
('A', 'B', 10),
('A', 'C', 2),
('C', 'D', 7),
('B', 'A', 13),
('C', 'B', 1),
('D', 'B', 9)
;
You get with this statement
SELECT T1.groupX groupname, (sum1 - sum2) as res
From
(SELECT groupX ,Sum(quantity) sum1 From tableA Group by groupX) T1
inner join (SELECT groupY,Sum(quantity) sum2 From tableA Group by groupY) T2
On T1.groupX = T2.groupY
ORDER by res;
You will get
groupname res
B -7
A -1
D 2
C 6
As i don't know all your data, it can be that it is better ti use Left join, which would include all unique memebers of groupX that have no corresponding group in groupY.
With right join it is vice versa
See example here https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=6c2facaaf6564d4025f24f6aab35adf7

Related

Select sum with input from grouped query

I need to make a query that selects a grouped collection of rows from a table based on user input conditions, and then in the select i will sum data from a subset of the rows.
The setup is rather expansive to describe in a post, so here is a demostration of the problem in the simplest way i can make it:
We have this table: DemoTable
ID
StaticKey
GroupKey
Value
1
A
A
2
2
A
A
2
3
A
B
2
4
A
B
2
5
A
C
2
6
A
C
2
I make a select and groups on "StaticKey".
What i would then like to do, is to, in the select clause, to select the sum of a subset of the values from the groupped data:
select
DT.GroupKey,
(select sum(D.Value) from DemoTable D where D.ID in (DT.ID) and D.GroupKey = 'A') as 'Sum of A''s',
(select COUNT(D.ID) from DemoTable D where D.ID in (DT.ID) and D.GroupKey = 'A') as 'Count of A''s'
from DemoTable DT
group by DT.StaticKey;
I hoped that the sum would result in a sum of 4 and a count of 2, but i get 2 and 1. So the input to the "select sum" seems to be just one id and not the collected ids.
GroupKey
Sum of A's
Count of A's
A
2
1
If i add a group_concat of DT.ID i get them comma separated - but is it posible to get them as a collection i can use as input to the selects?
Heres sql to create the table and queries:
CREATE TABLE DemoTable
(
ID INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
GroupKey varchar(200) null default null,
StaticKey varchar(200) not null default 'A',
Value varchar(200) null default null,
PRIMARY KEY (ID)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
insert into DemoTable (GroupKey, Value) values ('A', 2);
insert into DemoTable (GroupKey, Value) values ('A', 2);
insert into DemoTable (GroupKey, Value) values ('B', 2);
insert into DemoTable (GroupKey, Value) values ('B', 2);
insert into DemoTable (GroupKey, Value) values ('C', 2);
insert into DemoTable (GroupKey, Value) values ('C', 2);
select DT.GroupKey,
(select sum(D.Value) from DemoTable D where D.ID in (DT.ID) and D.GroupKey = 'A') as 'Sum of A''s',
(select COUNT(D.ID) from DemoTable D where D.ID in (DT.ID) and D.GroupKey = 'A') as 'Count of A''s'
from DemoTable DT
group by DT.StaticKey;
DROP TABLE DemoTable;
More simple:
select GroupKey,
sum(Value) as sum_of_A,
sum(GroupKey='A') as count_of_A
from DemoTable
where GroupKey='A'
group by GroupKey;
https://dbfiddle.uk/sdYlTw57
Isn't it always D.ID in (DT.ID)?
select
DT.GroupKey,
(select sum(D.Value) from DemoTable D where D.GroupKey = 'A') as 'Sum of A''s',
(select COUNT(D.ID) from DemoTable D where D.GroupKey = 'A') as 'Count of A''s'
from DemoTable DT
group by DT.StaticKey;
It does the job but perhaps it's too simple...
You can also try this.
SELECT DT.GroupKey,
SUM(DT.Value) AS 'Sum of A''s',
COUNT(DT.ID) AS 'Count of A''s'
FROM DemoTable DT
WHERE DT.GroupKey = 'A'
GROUP BY DT.StaticKey;

Mysql Query of 2 tables with values using commas , matching same values using comma separator

I continue the work of a project from the previous programmer, there is a problem in the query because the string data type is separated by commas, and must be in the query.
Is it possible to query from 2 tables with results like this?
Table User
id
name
Skill
1
Person A
skill A, skill B, skill C
2
Person B
skill B, skill C, skill D
3
Person C
skill C, skill D
4
Person D
skill E, skill F
5
Person E
skill A, skill E
6
Person F
skill A, skill C
7
Person G
skill G, skill H, skill X, skill Y, skill Z
Table Jobs
id
jobs skill
1
skill C, skill D, skill G
Result Query
name
Person A
Person B
Person C
Person F
Person G
If it weren't for the "unlimited skill" expectation, this could be a relatively simple query. That said, here's something that will get you the result that you seek.
Replace the desired skill expectations midway down in the line that starts WHERE zz.skill IN:
SELECT DISTINCT zz.`name`
FROM (SELECT DISTINCT uu.`name`, TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(uu.`skill`, ',', num.`idx`), ',', -1)) as `skill`
FROM `User` uu CROSS JOIN
(SELECT (h*100+t*10+u+1) as `idx`
FROM (SELECT 0 h UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c) num) zz
WHERE zz.`skill` IN (SELECT DISTINCT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX('skill A, skill B, skill C', ',', num.`idx`), ',', -1)) as `skill`
FROM (SELECT (h*100+t*10+u+1) as `idx`
FROM (SELECT 0 h UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
(SELECT 0 t UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b,
(SELECT 0 u UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) c) num
HAVING `skill` <> '')
ORDER BY zz.`name`;
Caveats:
You do not technically have unlimited skill options here. Each record can have up to 1,000. If you need more, you will need to expand the ugly CROSS JOIN number generator.
This is not the most memory-efficient design. It is not recommended to run on tens of thousands of records at a time.
Source Data:
DROP TABLE IF EXISTS `User`;
CREATE TABLE IF NOT EXISTS `User` (
`id` int(11) UNSIGNED NOT NULL ,
`name` varchar(40) NOT NULL DEFAULT '',
`skill` varchar(256) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `User` (`id`, `name`, `skill`)
VALUES (1, 'Person A', 'skill A, skill B, skill C'),
(2, 'Person B', 'skill B, skill C, skill D'),
(3, 'Person C', 'skill C, skill D'),
(4, 'Person D', 'skill E, skill F'),
(5, 'Person E', 'skill A, skill E'),
(6, 'Person F', 'skill A, skill C'),
(7, 'Person G', 'skill G, skill H, skill X, skill Y, skill Z');
The key to solving this problem is to split each skill into separate lines, which does not require hard coding. However, no matter what method you use, you must first have a large enough digital table. There are many ways to create it like this
create table bignum as
select #rn:=#rn+1 rn
from information_schema.COLUMNS t1,
(select #rn:=0) t2
limit 100;
Then you can do this
DROP TABLE IF EXISTS `User`;
CREATE TABLE IF NOT EXISTS `User` (
`id` int(11) UNSIGNED NOT NULL ,
`name` varchar(40) NOT NULL DEFAULT '',
`skill` varchar(256) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `User` (`id`, `name`, `skill`)
VALUES (1, 'Person A', 'skill A, skill B, skill C'),
(2, 'Person B', 'skill B, skill C, skill D'),
(3, 'Person C', 'skill C, skill D'),
(4, 'Person D', 'skill E, skill F'),
(5, 'Person E', 'skill A, skill E'),
(6, 'Person F', 'skill A, skill C'),
(7, 'Person G', 'skill G, skill H, skill X, skill Y, skill Z');
select t1.*,
trim(SUBSTRING_INDEX(SUBSTRING_INDEX(t1.`skill`, ',', t2.rn), ',', -1))
from user t1,
bignum t2
where length(t1.skill) - length(replace(t1.skill, ',', '')) + 1 >= t2.rn
order by t1.id
;
As for the rest, I believe you can do it yourself.
First, you should fix your data model! Storing multiple values in a string is just a bad way to store data. For many reasons. That should be where your effort goes.
Because you are looking for any match, you are a bit lucky, because you can use regular expressions:
select u.*
from user u join
jobs j
on u.skill regexp replace(j.skill, ', ', '|');
Note: This assumes that the skill lists do not contain any special regular expression characters. It also assumes that the skills don't "overlap", so you one skill does not match another. An example of a problem might be SQL and MySQL. This can be fixed by making the regular expression a bit more complicated. These problems can be fixed by doctoring the regular expression, but I don't know if that complication is necessary.

Group and subquery issue

This is my sample data
CREATE TABLE customer1
(
rating int(9),
genre varchar(100),
title varchar(100)
);
INSERT INTO customer1 (rating, genre, title)
VALUES
(2, 'A', 'abc'),
(4, 'A', 'abc1'),
(2, 'B', 'abc2'),
(3, 'B', 'abc3'),
(2, 'C', 'abc4'),
(5, 'C', 'abc5');
I need to find the title with max rating in each genre.
Thanks for the help.
One option uses a subquery for filtering:
select c.*
from customer1
where c.rating = (select max(c1.rating) from customer1 c1 where c1.genre = c.genre)
This would take advantage of an index on (genre, rating).
In MySQL 8.0, you can also use window functions:
select *
from (
select c.*,
rank() over(partition by genre order by rating desc) rn
from customer1 c
) c
where rn = 1

SQL subquery to fetch rows by column values

I have a PostgreSQL table like this:
CREATE TABLE foo(man_id, subgroup, power, grp)
AS VALUES
(1, 'Sub_A', 4, 'Group_A'),
(2, 'Sub_B', -1, 'Group_A'),
(3, 'Sub_A', -1, 'Group_B'),
(4, 'Sub_B', 6, 'Group_B'),
(5, 'Sub_A', 5, 'Group_A'),
(6, 'Sub_B', 1, 'Group_A'),
(7, 'Sub_A', -1, 'Group_B'),
(8, 'Sub_B', 2, 'Group_B'),
(9, 'Sub_C', 2, 'Group_B');
The power calculation works like this:
Total Power of Subgroup Sub_A in the grp Group_A is (4 + 5 ) = 9
Total Power of Subgroup Sub_B in the grp Group_A is ((-1) + 1 ) = 0
Total Power of Subgroup Sub_A in the grp Group_B is ((-1) + (-1) ) = -2
Total Power of Subgroup Sub_B in the grp Group_B is (6 + 2 ) = 8
So the power of Sub_A in the Group_A is not equal to power of Sub_A in the Group_B
So the power of Sub_B in the Group_A is not equal to power of Sub_B in the Group_B
I want to query the database and fetch the rows where, for a same subgroup name total power is not equal across all the other grp names.
What would be the recommended way to do this?
I can find the sum of total power:
SELECT sum(power) AS total_power
FROM foo
GROUP BY grp
MySQL solution will be accepted as well.
One way:
SELECT f.*
FROM (
SELECT subgroup
FROM (
SELECT subgroup, grp, sum(power) AS total_power
FROM foo
GROUP BY subgroup, grp
) sub
GROUP BY 1
HAVING min(total_power) <> max(total_power) -- can fail for NULL values;
) sg
JOIN foo f USING (subgroup);
In your example all rows qualify except for the last one with 'Sub_C'.
Closely related to your previous question:
Do all groups have equal total power for given subgroup?
Similar explanation and considerations.
db<>fiddle here
I think a way to phrase your problem is that you want to total the power for subgroup in a group, then find if a subgroup with the same name exists in another group with a different power.
The first step is to total the powers like you want:
SELECT grp, subgroup, sum(power) as power
FROM foo
GROUP BY grp, subgroup
That should give you a result like:
grp subgroup power
------- -------- -----
Group_A Sub_A 9
Group_A Sub_B 0
Group_B Sub_A -2
Group_B Sub_B 8
Group_B Sub_C 2
Once you have that, you can use a CTE to join the results with itself for the comparison to get what you want. You don't specify whether you want Sub_C to appear, if 'not existing' qualifies as having a 'different total power', then you would want to use a left join and check for nulls in alias b. The < in the join makes it so that each difference only appears once with the lower order group as grp1.
WITH totals AS (
SELECT grp, subgroup, sum(power) as power
FROM foo
GROUP BY grp, subgroup
ORDER BY grp, subgroup
)
SELECT a.subgroup,
a.grp as grp1, a.power as Power1,
b.grp as grp2, b.power as Power2
FROM totals a
INNER JOIN totals b ON b.subgroup = a.subgroup
and a.grp < b.grp
WHERE b.power <> a.power
ORDER BY a.subgroup, a.grp, b.grp
with totals as (
select grp, subgroup, sum(power) as total_power
from foo
group by grp, subgroup
)
select * from totals t1
where t1.total_power <> all (
select t2.total_power from totals t2
where t2.subgroup = t.subgroup and t2.grp <> t1.grp
)
or
with totals as (
select grp, subgroup, sum(power) as total_power
from foo
group by grp, subgroup
), matches as (
select grp, subgroup, count(*) over (partition by subgroup, total_power) as matches
)
select * from counts where matches = 1;
I would use window functions:
select f.*
from (select f.*,
min(sum_value)) over (partition by group) as min_sum_value,
max(sum_value)) over (partition by group) as max_sum_value,
from (select f.*,
sum(value) over (partition by subgroup, group) as sum_value
from foo f
) f
) f
where min_sum_value <> max_sum_value;

How to find all child of a node in a special level ? - SQL SERVER

I'm somewhat new to SQL.
There is a chart like below picture and it was saved into table with id , pid (parent id) , level .
How can I find all child of node in a special level?
For example: All child of c in level 4 = F , G , H
Could you help me, please?
It solved by following code:
;WITH r as (
SELECT *
FROM Chart
WHERE ParentID = 3
UNION ALL
SELECT d.*
FROM Chart d
INNER JOIN r
ON d.ParentID = r.KID
)
SELECT *
FROM r
where KLevel = 4
It is good to have additional table with node relations. E.g. table successors with columns pid and sid (successor id). There you have rows with pid=3 (C) and sid=4,5,6,7,8. Then you run simple query:
SELECT s.sid
FROM table t JOIN successor s ON t.id=s.pid JOIN table t2 ON t2.id=s.sid
WHERE t.id=3 AND t2.level=4;
You can use a recursive query which will look for child rows.
Then you only have to SELECT value with the requiered level.
Query:
with cte(id) as(
SELECT id FROM #data WHERE name = #parent
UNION ALL
SELECT d.id FROM #data d
INNER JOIN cte c ON c.id = d.pid
WHERE d.[level] <= #level
)
SELECT d.name, d.id, d.pid, d.level FROM #data d
INNER JOIN cte c ON c.id = d.id
WHERE d.[level] = #level
Output:
name id pid level
g 7 5 4
h 8 5 4
f 6 4 4
Your data:
Declare #parent char(1) = 'C';
Declare #level int = 4;
Declare #data table(id int, pid int, name char(1), [level] int);
INSERT INTO #data(id, pid, name, [level]) values
(1, 0, 'a', 1)
, (2, 1, 'b', 2)
, (3, 1, 'c', 2)
, (4, 3, 'd', 3)
, (5, 3, 'e', 3)
, (6, 4, 'f', 4)
, (7, 5, 'g', 4)
, (8, 5, 'h', 4)
;