Create a view with top N per group rows in mysql - mysql

I need to create a view that stores the top 200 rows for each userId from another table that has userId as one of its columns.
Found a way to do this using user-defined variables in SELECT, but then MySQL does not allow views with variables in the SELECT.
This is a part of the SELECT statement to be used in the view:
select *,#num:= if(#userId = userId, #num + 1, 1) as row_number,
#userId := userId as dummy from (SELECT #userId:=0, #num:=0) as init
Is it possible to replace #userId and #num with functions instead? Similar examples would of great help!!

Consider the following...
SELECT * FROM results;
+----+------------+--------+-------+-------+
| id | discipline | member | event | value |
+----+------------+--------+-------+-------+
| 1 | 1 | 2 | 4 | 10 |
| 2 | 1 | 1 | 4 | 8 |
| 3 | 1 | 2 | 5 | 9 |
| 4 | 2 | 3 | 5 | 9 |
| 5 | 1 | 2 | 6 | 11 |
| 6 | 1 | 2 | 7 | 11 |
| 7 | 1 | 2 | 1 | 11 |
| 8 | 1 | 2 | 3 | 7 |
| 9 | 1 | 1 | 8 | 8 |
+----+------------+--------+-------+-------+
Say I want to get the top 3 'value' results for each member. In the event of a tie, I'll take the lower 'event' first...
SELECT x.*
, COUNT(*) rank
FROM results x
JOIN results y
ON y.member = x.member
AND (y.value > x.value OR (y.value = x.value AND y.event <= x.event))
GROUP
BY x.member
, x.value
, x.event
HAVING COUNT(*) <=3;
+----+------------+--------+-------+-------+---------+
| id | discipline | member | event | value | rank |
+----+------------+--------+-------+-------+---------+
| 2 | 1 | 1 | 4 | 8 | 1 |
| 9 | 1 | 1 | 8 | 8 | 2 |
| 7 | 1 | 2 | 1 | 11 | 1 |
| 5 | 1 | 2 | 6 | 11 | 2 |
| 6 | 1 | 2 | 7 | 11 | 3 |
| 4 | 2 | 3 | 5 | 9 | 1 |
+----+------------+--------+-------+-------+---------+

Related

How to work out total distinct values minus grouped distinct values in MySQL

Given the sample table of:
+----+----------+---------+------+
| id | userName | storeId | cost |
+----+----------+---------+------+
| 1 | foo | 1 | 10 |
| 2 | bar | 1 | 10 |
| 3 | baz | 5 | 5 |
| 4 | baz | 3 | 20 |
| 5 | qux | 1 | 5 |
| 6 | qux | 4 | 20 |
| 7 | qux | 15 | 30 |
| 8 | qux | 17 | 40 |
| 9 | qux | 3 | 5 |
| 10 | quux | 6 | 20 |
+----+----------+---------+------+
I would like to work out how many people purchased at each store and how many did not. I want the report to display the results grouped by store.
I know the statement select storeId, count(distinct username) as total from purchases group by storeId provides me with how many people purchased in each store, but I want to subtract the result of the query select count(distinct userName) from purchases; in another column. I would expect the sample output to display as follows.
+---------+-----------+--------------+
| storeId | purchased | notPurchased |
+---------+-----------+--------------+
| 1 | 3 | 2 |
| 3 | 2 | 3 |
| 4 | 1 | 4 |
| 5 | 1 | 4 |
| 6 | 1 | 4 |
| 15 | 1 | 4 |
| 17 | 1 | 4 |
+---------+-----------+--------------+
You can use NOT condition with IN() function
As long a subse3lect gives back only only one,
you can use following
SELECT
storeId,
COUNT(DISTINCT username) AS total,
((SELECT
COUNT(DISTINCT userName)
FROM
purchases) - COUNT(DISTINCT username)) notPurchased
FROM
purchases
GROUP BY storeId
storeId | total | notPurchased
------: | ----: | -----------:
1 | 3 | 2
3 | 2 | 3
4 | 1 | 4
5 | 1 | 4
6 | 1 | 4
15 | 1 | 4
17 | 1 | 4
db<>fiddle here

Select ans specific value or, if not exists, the minimum one

I'm stuck trying to solve a problem using SQL (MySQL 5.6). Need to get the address which has ID 2 or, if not exists ID 2, the lower ID.
For example
|-----------------|---------------|
| CostumerID | AddressID |
|-----------------|---------------|
| 1 | 4 |
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
| 3 | 4 |
| 4 | 3 |
| 4 | 4 |
| 5 | 2 |
| 6 | 4 |
| 7 | 2 |
| 7 | 4 |
| 8 | 3 |
| 9 | 1 |
| 9 | 3 |
| 9 | 4 |
| 9 | 2 |
|-----------------|---------------|
If a costumerID have an AddressID 2, must get that. If not, must get the minimum AddressID.
The output must be like:
|-----------------|---------------|
| CostumerID | AddressID |
|-----------------|---------------|
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 | 3 |
| 5 | 2 |
| 6 | 4 |
| 7 | 2 |
| 8 | 3 |
| 9 | 2 |
|-----------------|---------------|
So far I've tried this:
SELECT distinct CostumerID,
if (AddressID= 2, AddressID,
(select min(b.AddressID) from Addresses b where b.AddressID= a.AddressID)) as tipus
FROM from Addresses a
but get duplicates at CostumerID.
Use aggregation with CASE logic:
SELECT
CostumerID,
CASE WHEN COUNT(CASE WHEN AddressID = 2 THEN 1 END) > 0
THEN 2 ELSE MIN(AddressID) END AS AddressID
FROM yourTable
GROUP BY
CostumerID;
SELECT CASE
WHEN EXISTS(SELECT *
FROM tbl_name
WHERE AddressID = 2)
THEN (SELECT *
FROM tbl_name
WHERE AddressID > 2 )
ELSE 'Default Value'
END

Similar SQLs with session variable produce different result

MariaDB 10.1.18
Table P (id int AUTO_INCREMENT, rownum int, code int, s int, PRIMARY KEY(id));
select id, rownum, code, s from P order by id;
+----+--------+------+------+
| id | rownum | code | s |
+----+--------+------+------+
| 1 | 1 | 5 | 1 |
| 2 | 2 | 5 | 1 |
| 3 | 3 | 5 | 1 |
| 4 | 4 | 5 | 1 |
| 5 | 5 | 5 | 1 |
| 6 | 6 | 7 | 1 |
| 7 | 7 | 7 | 1 |
| 8 | 8 | 7 | 1 |
| 9 | 9 | 7 | 1 |
| 10 | 10 | 7 | 1 |
+----+--------+------+------+
Issue: the following 2 queries are very similar: 1st query join on id, 2nd join on rownum; the id and rownum columns are having the same values (see table above), but the query result is different in calculated column N:
Query 1: join on id column
SELECT P.id, P.rownum, P2.s,
IF(P2.s IS NULL, #val:=#val+1, #val) as N
FROM P CROSS JOIN (SELECT #val:=0) init
LEFT JOIN P P2
ON (P.id+1=P2.id AND P.s=1 AND P2.s=1 AND P.code = P2.code)
ORDER BY P.id;
+----+--------+------+------+
| id | rownum | s | N |
+----+--------+------+------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 1 | 0 |
| 3 | 3 | 1 | 0 |
| 4 | 4 | 1 | 0 |
| 5 | 5 | NULL | 1 |
| 6 | 6 | 1 | 1 |
| 7 | 7 | 1 | 1 |
| 8 | 8 | 1 | 1 |
| 9 | 9 | 1 | 1 |
| 10 | 10 | NULL | 2 |
+----+--------+------+------+
Query 2: join on rownum column
SELECT P.id, P.rownum, P2.s,
IF(P2.s IS NULL, #val:=#val+1, #val) as N
FROM P CROSS JOIN (SELECT #val:=0) init
LEFT JOIN P P2
ON (P.rownum+1=P2.rownum AND P.s=1 AND P2.s=1 AND P.code = P2.code)
ORDER BY P.id;
+----+--------+------+------+
| id | rownum | s | N |
+----+--------+------+------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 1 | 0 |
| 3 | 3 | 1 | 0 |
| 4 | 4 | 1 | 0 |
| 5 | 5 | NULL | 1 |
| 6 | 6 | 1 | 0 |
| 7 | 7 | 1 | 0 |
| 8 | 8 | 1 | 0 |
| 9 | 9 | 1 | 0 |
| 10 | 10 | NULL | 2 |
+----+--------+------+------+
As explicitly documented both in MariaDB knowledge base and MySQL manual, you should not read a user-defined variable and set its value in the same statement, unless this statement is SET. For other statements it is unsafe and the result is not guaranteed, as your example clearly demonstrates.
Additional note: variables you are talking about in your question are more commonly referred to as 'user-defined variables' or 'user variables', as opposed to 'system variables', 'global variables' and 'session variables', each of which imply system-defined variables, either all of them or limited to the given scope.

How to get ranking of scores in MySQL with LIMIT?

This is what I currently have:
SELECT id, score, username,
CASE
WHEN #prevRank = score THEN #curRank
WHEN #prevRank := score THEN #curRank := #curRank + 1
END AS rank
FROM users u,
SELECT #curRank :=0, #prevRank := NULL) r
ORDER BY score DESC LIMIT 5, 5;
I want the next 5 ranks from row 5, but the ranking restarts at 1. How can I get the ranking to start at the correct ranking relative to all the rows?
Sample data: http://pastebin.com/i3PsRWdg
Everyone
+----+--------+-----------+------+
| id | scores | username | rank |
+----+--------+-----------+------+
| 24 | 23 | fl | 1 |
| 3 | 9 | test | 2 |
| 6 | 9 | usernine | 2 |
| 5 | 7 | test3 | 3 |
| 11 | 7 | test9 | 3 |
| 26 | 5 | tryagain | 4 |
| 12 | 5 | newUser | 4 |
| 7 | 3 | test6 | 5 |
| 14 | 1 | new3 | 6 |
| 4 | 1 | test2 | 6 |
| 13 | 0 | new2 | NULL |
| 25 | 0 | newu | 6 |
| 23 | 0 | new | 6 |
| 22 | 0 | usernine | 6 |
| 21 | 0 | usernine | 6 |
| 20 | 0 | test | 6 |
| 19 | 0 | usernine | 6 |
| 18 | 0 | usernine | 6 |
| 17 | 0 | usernine | 6 |
| 15 | 0 | usernine | 6 |
| 16 | 0 | test9 | 6 |
+----+--------+-----------+------+
LIMIT 1,5 -> correct
+----+--------+-----------+------+
| id | scores | username | rank |
+----+--------+-----------+------+
| 3 | 9 | test | 1 |
| 6 | 9 | usernine | 1 |
| 5 | 7 | test3 | 2 |
| 11 | 7 | test9 | 2 |
| 26 | 5 | tryagain | 3 |
+----+--------+-----------+------+
LIMIT 5,5 -> incorrect
+----+--------+----------+------+
| id | scores | username | rank |
+----+--------+----------+------+
| 12 | 5 | newUser | 1 |
| 26 | 5 | usernine | 1 |
| 7 | 3 | test6 | 2 |
| 4 | 1 | test2 | 3 |
| 14 | 1 | new3 | 3 |
+----+--------+----------+------+
Should be this:
+----+--------+----------+------+
| id | scores | username | rank |
+----+--------+----------+------+
| 12 | 5 | newUser | 4 |
| 26 | 5 | usernine | 4 |
| 7 | 3 | test6 | 5 |
| 4 | 1 | test2 | 6 |
| 14 | 1 | new3 | 6 |
+----+--------+----------+------+
You are trying to solve an issue at the wrong level. Your database does not think in rows, it thinks in datasets. It's world consists of neat rectangular tables, not rows or fields. The whole concept of 'adding per row' that you employ with the #curRank hack actually doesn't work in SQL Server for example because it correctly treats the whole set as an atomic entity.
Which brings us back to that your problem is architectural, not database related. The database is just responsible for storing, organizing, aggregating and returning data. Numbering the rows most certainly isn't, it belongs in either your business logic or your presentation logic.
To solve it in the business logic, which is where you'll be feeding the 5 offset to this query to begin with, you could store the resulting rows in a numbered array. In the presentation layer of a website for example, you would use an ordered list like <ol start="6">.
You can create one aditional variable to keep track of row_number
Also I use IF() instead of case because you version have a bug when score = 0 produce rank null and didnt create rank 7
SQL Fiddle Demo
SELECT *
FROM (
SELECT id, score, username,
#curRank := IF(#prevRank = score,
#curRank ,
IF(#prevRank := score, #curRank + 1, #curRank + 1 )
) as rank,
(#row := #row + 1) as rn
FROM users u
CROSS JOIN (SELECT #curRank := 0, #prevRank := NULL, #row := 0) r
ORDER BY score DESC
) T
WHERE rn > 5
AND rn <= 10
OUTPUT
| id | score | username | rank | rn |
|----|-------|----------|------|----|
| 26 | 5 | tryagain | 4 | 6 |
| 12 | 5 | newUser | 4 | 7 |
| 7 | 3 | test6 | 5 | 8 |
| 14 | 1 | new3 | 6 | 9 |
| 4 | 1 | test2 | 6 | 10 |

Updating multiple rows with first instance of record in same table

Assume following table:
+----+-----------+
| id | session |
+----+-----------+
| 1 | abcd1234 |
| 2 | abcd1234 |
| 3 | abcd1234 |
| 4 | qwert5678 |
| 5 | qwert5678 |
| 6 | abcd1234 |
| 7 | abcd1234 |
| 8 | qwert5678 |
| 9 | abcd1234 |
| 10 | qwert5678 |
| 11 | qwert5678 |
| 12 | qwert5678 |
+----+-----------+
Suppose we want to get the first id of a given session, then set every instance of that session to the id for all sessions, such that the table becomes:
+----+-----------+
| id | session |
+----+-----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 4 |
| 5 | 4 |
| 6 | 1 |
| 7 | 1 |
| 8 | 4 |
| 9 | 1 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |
+----+-----------+
We have a table with approximately 45M records, and are essentially changing every instance of column b to the value of min(column a) when grouped by column b.
Is there a way to do this in a single query? We have attempted several.
update example e
set session =
(select id from
(select id,min(session)
from example as first_id
group by session
) as this_id
);
...which errors out: "Subquery returns more than 1 row".
update example e
join
(select id
from
(select id,min(session)
from example as first_id
group by session
) as this_id
) as etable
set session = first_id;
...which errors out: "Unknown column 'first_id' in 'field list'". Also used 'this_id' to the same effect.
And other queries. Is this possible in a single query? Are we thinking about this incorrectly?
Query:
SQLFIDDLEExample
UPDATE example
SET session =(SELECT MIN(e2.ID)
FROM (SELECT *
FROM example) e2
WHERE e2.session = example.session)
Result:
| ID | SESSION |
----------------
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 4 |
| 5 | 4 |
| 6 | 1 |
| 7 | 1 |
| 8 | 4 |
| 9 | 1 |
| 10 | 4 |
| 11 | 4 |
| 12 | 4 |