SQL: finding differences between rows - mysql

I want to count how many times each user has rows within '5' of eachother.
For example, Don - 501 and Don - 504 should be counted, while Don - 501 and Don - 1600 should not be counted.
Start:
Name value
_________ ______________
Don 1235
Don 6012
Don 6014
Don 6300
James 9000
James 9502
James 9600
Sarah 1110
Sarah 1111
Sarah 1112
Sarah 1500
Becca 0500
Becca 0508
Becca 0709
Finish:
Name difference_5
__________ _____________
Don 1
James 0
Sarah 2
Becca 0

Use the ABS() function, in conjunction with a self-join in a subquery:
So, something like:
SELECT name, COUNT(*) / 2 AS difference_5
FROM (
SELECT a.name name, ABS(a.value - b.value)
FROM tbl a JOIN tbl b USING(name)
WHERE ABS(a.value - b.value) BETWEEN 1 AND 5
) AS t GROUP BY name
edited as per Andreas' comment.

Assuming that each name -> value pair is unique, this will get you the count of times the value is within 5 per name:
SELECT a.name,
COUNT(b.name) / 2 AS difference_5
FROM tbl a
LEFT JOIN tbl b ON a.name = b.name AND
a.value <> b.value AND
ABS(a.value - b.value) <= 5
GROUP BY a.name
As you'll notice, we also have to exclude the pairs that are equal to themselves.
But if you wanted to count the number of times each name's values came within 5 of any value in the table, you can use:
SELECT a.name,
COUNT(b.name) / 2 AS difference_5
FROM tbl a
LEFT JOIN tbl b ON NOT (a.name = b.name AND a.value = b.value) AND
ABS(a.value - b.value) <= 5
GROUP BY a.name
See the SQLFiddle Demo for both solutions.

Because the OP also wants de zero counts, we'll need a self- left join. Extra logic is needed if one person has two exactly the same values, these should also be counted only once.
WITH cnts AS (
WITH pair AS (
SELECT t1.zname,t1.zvalue
FROM ztable t1
JOIN ztable t2
ON t1.zname = t2.zname
WHERE ( t1.zvalue < t2.zvalue
AND t1.zvalue >= t2.zvalue - 5 )
OR (t1.zvalue = t2.zvalue AND t1.ctid < t2.ctid)
)
SELECT DISTINCT zname
, COUNT(*) AS znumber
FROM pair
GROUP BY zname
)
, names AS (
SELECT distinct zname AS zname
FROM ztable
GROUP BY zname
)
SELECT n.zname
, COALESCE(c.znumber,0) AS znumber
FROM names n
LEFT JOIN cnts c ON n.zname = c.zname
;
RESULT:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 14
zname | znumber
-------+---------
Sarah | 3
Don | 1
Becca | 0
James | 0
(4 rows)
NOTE: sorry for the CTE, I had not seen th mysql tag,I just liked the problem ;-)

SELECT
A.Name,
SUM(CASE WHEN (A.Value < B.Value) AND (A.Value >= B.Value - 5) THEN 1 ELSE 0 END) Difference_5
FROM
tbl A INNER JOIN
tbl B USING(Name)
GROUP BY
A.Name

Related

need help writing a query (restructuring the table)

I need to write a select statement that will rewrite the table in the following manner... I'm not sure how to go about this using MySQL.
Example of table
user_id date a b c
123456 2020-01-01 1 1 1
234567 2020-03-04 1 0 0
453576 2020-05-05 1 0 1
Desired result
user_id date results
123456 2020-01-01 a
123456 2020-01-01 b
123456 2020-01-01 c
234567 2020-03-04 a
453576 2020-05-05 a
453576 2020-05-05 c
In MySQL you can unpivot with union all, while filtering on 1 values:
select user_id, date, 'a' as result from mytable where a = 1
union all select user_id, date, 'b' from mytable where b = 1
union all select user_id, date, 'c' from mytable where c = 1
order by user_id, date, result
If you have a large amount of data or your "table" is really a complex query (say a subquery or view), then unpivoting is usually faster with cross join than with union all:
select t.user_id, t.date, r.result
from t cross join
(select 'a' as result union all
select 'b' as result union all
select 'c' as result
) r
where (t.a = 1 and r.result = 'a') or
(t.b = 1 and r.result = 'b') or
(t.c = 1 and r.result = 'c') ;
For a single smallish table, performance probably doesn't matter.

change the mysql order of the result with union

So i get 10 results from my first select and 1 from the other one after union like this:
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
i would like to get the result of the second select as the second last result like this
********
name_id 100
name_id 99
name_id 98
name_id 97
name_id 96
name_id 95
name_id 94
name_id 93
name_id 92
name_id 1 <- second select result as second last result
name_id 91
********
Can someone help pls?
Synthesize a row number column for the query as it stands and shuffle positions as needed.
SELECT x.name
, x.name_id
FROM (
SELECT #rownum:=#rownum + 1 as row_number,
t.name,
t.name_id
FROM (
-- original query from the question starts here
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
) t,
(SELECT #rownum := 0) r
) x
ORDER BY CASE row_number
WHEN 10 THEN 11
WHEN 11 THEN 10
ELSE row_number
END
;
(Note that the query has been sightly modified to avoid syntax errors / support the demo: table all has been named allx, explicit projections of the union's subqueries).
That gets complicated quickly thus next to ad hoc reporting it is preferable to synthesize an attribute in the subqueries of the union that reflects a global order.
Demo here (SQL fiddle)
Credits
Row number synthesizing taken from this SO answer
Interesting question given
+----+--------+
| id | sname |
+----+--------+
| 1 | sname1 |
| 2 | sname2 |
| 3 | sname3 |
| 4 | sname4 |
| 5 | sname5 |
| 6 | sname6 |
+----+--------+
6 rows in set (0.001 sec)
(select id,sname,#r:=#r+1 rn
from users
cross join(select #r:=0) r
order by sname desc limit 3
)
union
(
select u.id,u.sname,
#r:=#r - .9
from users u
left join (select id from users order by sname desc limit 3) u1 on u1.id = u.id
where u1.id is null
order by u.id asc limit 0,1
)
order by rn;
Where a variable is used to calculate a row number in the first sub query, since this variable is not reset in the second query a simple piece of arithmetic works out where to position the second sub query result. Note the second sub query uses a left join to check that the result has not already appeared in the first sub query,
I would suggest union all and three selects:
SELECT an.*
FROM ((SELECT a.*, n.*, 1 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9
) UNION ALL
(SELECT a.*, n.*, 3 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9 OFFSET 9
) UNION ALL
(SELECT a.*, b.*
FROM all a JOIN
names n
WHERE n.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1
)
) an
ORDER BY ord, name_id;

left join two tables where my variable is less than 5

Im creating a query that select two tables and create a total variable by count a field in one table.
Example:
Table A:
ID | email
1 | test#test
2 | test2#test
3 | test3#test
Table B
ID | email_id | username_id
1 | 1 | 11
2 | 1 | 22
3 | 2 | 33
My query:
select a.id, a.email, count(c.id) as total
from tableA a
left join tableC c on c.email_id = a.id AND total <= 5
group by a.email LIMIT 1
Output:
Unknown column 'total' in 'on clause
I need to select the first "a.id" that has total <= 5. How can I do it?
Logically Select is processed after the Where clause so you cannot use Alias name in same Where clause.
Use HAVING clause
select a.id, a.email, count(c.id) as total
from tableA a
left join tableC c on c.email_id = a.id
group by a.email
Having count(c.id) <= 5
LIMIT 1
I think Mysql allows you do this as well
Having total <= 5
Try HAVING Count(c.id) <= 5
Just to make this a bit clearer, since the correct answer has already been provided - You don't have to use the HAVING clause, and the HAVING clause is not always the solution for this problem.
The HAVING clause is usually used to place filters on aggregated columns (sum,count,max,min etc..) , but when you have a calculated column (colA + colB as calc_column for example) , then another approach , which should work here as well is to wrap the query with another select, and then the new column will be available on the WHERE :
SELECT *
FROM (The query here ) s
WHERE s.total <= 5

MySQL select upto first occurrence of condition matching

Id | Price
----------------
1 | 10
2 | 20
3 | 40
4 | 10
I need to select ids where first occurrence of summation of price is greater than or equal 55 matching from the bottom. At this case --
I will have 4,3,2 ids selected.
Well, this is kinda tricky for MySQL since it doesn't support any window fuctions and becuase you want to include the first occurrence as well. You can try this:
SELECT * FROM (
SELECT t.id,
(SELECT sum(s.price) FROM YourTable s
WHERE s.id <= t.id) as cuml_sum
FROM YourTable t) ss
WHERE ss.cuml_sum < 55
--Will select all the record will the sum < 55
UNION ALL
SELECT * FROM (
SELECT t.id,
(SELECT sum(s.price) FROM YourTable s
WHERE s.id <= t.id) as cuml_sum
FROM YourTable t) tt
WHERE tt.cuml_sum >= 55
ORDER BY tt.cuml_sum
LIMIT 1
--Will select the first record that have sum >= 55

Mysql group_concat for count in sub query using parent filed is not allowed

I need to process album count for each of the country per artist; however, I have a problem once I do group_concat for count in mysql, I search a bit in stackoverflow, I found I have to do sub select for group_concat. The problem is once I do the sub select in from I can not use a.id from the parent from filed table. I got error like following Unknown column 'a.id' in 'where clause'
This is the query:
SELECT a.seq_id, a.id
(SELECT GROUP_CONCAT(cnt) AS cnt FROM (
SELECT CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.media_id
WHERE ma.artist_id = a.id
GROUP BY mgr.country_code
) count_table
) AS album_count
FROM music_artist AS a
WHERE a.seq_id > 0 and a.seq_id < 10000
The sample data in tables:
music_artists:
seq_id id name
1 1 Hola
2 2 Vivi
music_album_artists:
id artist_id album_id
1 1 1
2 1 2
3 1 5
4 1 10
5 2 2
6 2 10
6 2 1
media_geo_restrict:
album_id country_code
1 BE
1 CA
1 DE
1 US
2 CH
2 CA
2 CH
5 DE
10 US
The result I would like to have
seq_id id album_count
1 1 BE--1,CA--2,CH--1,DE--1,US--1
2 2 CA--1,US--2,CH--1
Here is what you need:
select seq_id, id, group_concat(concat(country_code, '--', qtd))
from (
select ma.seq_id, ma.id,
mgr.country_code, count(*) qtd
from music_artists ma
inner join music_album_artists maa
on ma.id = maa.artist_id
inner join media_geo_restrict mgr
on maa.album_id = mgr.album_id
where ma.seq_id > 0 and ma.seq_id < 10000
group by ma.seq_id, ma.id, ma.name,
mgr.country_code
) tb
group by seq_id, id
Here is the working sample: http://sqlfiddle.com/#!9/ff8b5/8
Try this and tell me:
SELECT a.seq_id, a.id, GROUP_CONCAT(cnt) AS cnt
FROM music_artist AS a,
(
SELECT ma.artist_id, CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.album_id
GROUP BY mgr.country_code
) AS count_table
WHERE a.seq_id > 0 and a.seq_id < 10000
and a.id=count_table.artist_id
group by a.id