How check value by consecutive date variable - sql

I have database table in SNOWFLAKE, where I need check for each customer if there is FLAG_1 == 1 at minimum 3 days in row. Flag_1 indicates whether the order contained any specific goods. And create new table with customer_id and flag_2. I really don't know how to handle this problem.
Sample table:
CREATE TABLE TMP_TEST
(
CUSTOMER_ID INT,
ORDER_DATE DATE,
FLAG_1 INT
);
INSERT INTO TMP_TEST (CUSTOMER_ID, ORDER_DATE, FLAG_1)
VALUES
(001, '2020-04-01', 0),
(001, '2020-04-02', 1),
(001, '2020-04-03', 1),
(001, '2020-04-04', 1),
(001, '2020-04-05', 1),
(001, '2020-04-06', 0),
(001, '2020-04-07', 0),
(001, '2020-04-08', 0),
(001, '2020-04-09', 1),
(002, '2020-04-10', 1),
(002, '2020-04-11', 0),
(002, '2020-04-12', 0),
(002, '2020-04-13', 1),
(002, '2020-04-14', 1),
(002, '2020-04-15', 0),
(002, '2020-04-16', 1),
(002, '2020-04-17', 1);
Expected output table:
CUSTOMER_ID FLAG_2
001 1
002 0

Maybe this can be help:
with calcflag as (
select customer_id, IFF( sum(flag_1) over (PARTITION by customer_id order by order_date rows between 3 preceding and 1 preceding) = 3, 1, 0 ) as new_flag
from tmp_Test)
select customer_id, max(new_flag) flag_2
from calcflag
group by 1
order by 1;
+-------------+--------+
| CUSTOMER_ID | FLAG_2 |
+-------------+--------+
| 1 | 1 |
| 2 | 0 |
+-------------+--------+

using COUNT_IF also works:
with calcflag as (
select
customer_id,
IFF(
count_if(flag_1 = 1) over (
PARTITION by customer_id
order by order_date
rows between 2 preceding and current row
) = 3, 1, 0
) as new_flag
from tmp_Test
)
select
customer_id,
max(new_flag) flag_2
from calcflag
group by 1
+-------------+--------+
| CUSTOMER_ID | FLAG_2 |
|-------------+--------|
| 1 | 1 |
| 2 | 0 |
+-------------+--------+

Snowflake supports MATCH_RECOGNIZE which is the easiest way to detect advanced patterns across multiple rows:
To find 3 or more occurences the pattern is PATTERN ( a{3,}):
SELECT *
FROM TMP_TEST
MATCH_RECOGNIZE (
PARTITION BY CUSTOMER_ID
ORDER BY ORDER_DATE
MEASURES MATCH_NUMBER() AS mn
ALL ROWS PER MATCH WITH UNMATCHED ROWS
PATTERN ( a{3,} )
DEFINE a AS FLAG_1 = 1
) mr
ORDER BY CUSTOMER_ID, ORDER_DATE;
Output:
Collapsing to single row per group:
SELECT CUSTOMER_ID, COALESCE(MIN(MN),0) AS FLAG_2
FROM TMP_TEST
MATCH_RECOGNIZE (
PARTITION BY CUSTOMER_ID
ORDER BY ORDER_DATE
MEASURES MATCH_NUMBER() AS mn
ALL ROWS PER MATCH WITH UNMATCHED ROWS
PATTERN ( a{3,})
DEFINE a AS FLAG_1 = 1
) mr
GROUP BY CUSTOMER_ID;
Output:
The power of this solution lies at the PATTERN part which could be easily extended with new conditions. For instance:
PATTERN ( a b{1,2} a )
DEFINE a AS FLAG_1 = 1,
b AS FLAT_2 = 0;
Here: Find sequence of flag = 1, followed by one or two occurences of flag = 0 and ended by flag = 1.

Related

SQL Occurrence of Sequence Number

I want to find if any Name has straight 4 or more occurrences of SeqNo in consecutive sequence only.
If there is a break in seqNo but 4 or more rows are consecutive then also i need that Name.
Example:
SeqNo Name
10 | A
15 | A
16 | A
17 | A
18 | A
9 | B
10 | B
13 | B
14 | B
6 | C
7 | C
9 | C
10 | C
OUTPUT:
A
BELOW IS SCRIPT FOR ANYONE HELPING.
create table testseq (Id int, Name char)
INSERT into testseq values
(10, 'A'),
(15, 'A'),
(16, 'A'),
(17, 'A'),
(18, 'A'),
(9, 'B'),
(10, 'B'),
(13, 'B'),
(14, 'B'),
(6, 'C'),
(7, 'C'),
(9, 'C'),
(10, 'C')
SELECT * FROM testseq
You can use some gaps-and-islands techniques for this.
If you want names that have at least 4 consecutive records where seqno is increasing by 1, then you can use the difference between seqno androw_number()` to define the groups, and then aggregate:
select distinct name
from (
select t.*, row_number() over(partition by name order by seqno) rn
from testseq t
) t
group by name, rn - seqno
having count(*) >= 4
Note that for your sample data, this returns no rows. A has 3 consecutive records where seqno is incrementing by 1, B and C have two.
I don't really view this as a "gaps-and-islands" problem. You are just looking for a minimum number of adjacent rows. This is easily handled using lag() or lead():
select t.*
from (select t.*,
lead(seqno, 3) over (partition by name order by seqno) as seqno_name_3
from t
) t
where seqno_name_3 = seqno + 3;
This checks the third sequence number on the same name. The third one after means that four names are the same in a row.
If you just want the name and to handle duplicates:
select distinct name
from (select t.*,
lead(seqno, 3) over (partition by name order by seqno) as seqno_name_3
from t
) t
where seqno_name_3 = seqno + 3;
If the sequence numbers can have gaps (but are otherwise adjacent):
select distinct name
from (select t.*,
lead(seqno, 3) over (partition by name order by seqno) as seqno_name_3,
lead(seqno, 3) over (order by seqno) as seqno_3
from t
) t
where seqno_name_3 = seqno_3;
A solution in plain SQL, no LAG() or LEAD() or ROW_NUMBER():
SELECT t1.Name
FROM testseq t1
WHERE (
SELECT count(t2.Id)
FROM testseq t2
WHERE t2.Name=t1.Name
and t2.Id between t1.Id and t1.Id+3
GROUP BY t2.Name)>=4
GROUP BY t1.Name;

Unexpected behavior of window function first_value

I have 2 columns - order no, value. Table value constructor:
(1, null)
,(2, 5)
,(3, null)
,(4, null)
,(5, 2)
,(6, 1)
I need to get
(1, 5) -- i.e. first nonnull Value if I go from current row and order by OrderNo
,(2, 5)
,(3, 2) -- i.e. first nonnull Value if I go from current row and order by OrderNo
,(4, 2) -- analogous
,(5, 2)
,(6, 1)
This is query that I think should work.
;with SourceTable as (
select *
from (values
(1, null)
,(2, 5)
,(3, null)
,(4, null)
,(5, 2)
,(6, 1)
) as T(OrderNo, Value)
)
select
*
,first_value(Value) over (
order by
case when Value is not null then 0 else 1 end
, OrderNo
rows between current row and unbounded following
) as X
from SourceTable
order by OrderNo
The issue is that it returns exactly same resultset as SourceTable. I don't understand why. E.g., if first row is processed (OrderNo = 1) I'd expect column X returns 5 because frame should include all rows (current row and unbound following) and it orders by Value - nonnulls first, then by OrderNo. So first row in frame should be OrderNo=2. Obviously it doesn't work like that but I don't get why.
Much appreciated if someone explains how is constructed the first frame. I need this for SQL Server and also Postgresql.
Many thanks
Although probably more expensive than two window functions, you can do this without a subquery using arrays:
with SourceTable as (
select *
from (values (1, null),
(2, 5),
(3, null),
(4, null),
(5, 2),
(6, 1)
) T(OrderNo, Value)
)
select st.*,
(array_remove(array_agg(value) over (order by orderno rows between current row and unbounded following), null))[1] as x
from SourceTable st
order by OrderNo;
Here is the db<>fiddle.
Or using a lateral join:
select st.*, st2.value
from SourceTable st left join lateral
(select st2.*
from SourceTable st2
where st2.value is not null and st2.orderno >= st.orderno
order by st2.orderno asc
limit 1
) st2
on 1=1
order by OrderNo;
With the right indexes on the source table, the lateral join might be the best solution from a performance perspective (I have been surprised by the performance of lateral joins under the right circumstances).
It's pretty easy to see why first_value doesn't work if you order the results by case when Value is not null then 0 else 1 end, orderno
orderno | value | x
---------+-------+---
2 | 5 | 5
5 | 2 | 2
6 | 1 | 1
1 | |
3 | |
4 | |
(6 rows)
For orderno=1, there's nothing after it in the frame that would be not-null.
Instead, we can arrange the orders into groups using count as a window function in a sub-query. We then use max as a window function over that group (this is arbitrary, min would work just as well) to get the one non-null value in that group:
with SourceTable as (
select *
from (values
(1, null)
,(2, 5)
,(3, null)
,(4, null)
,(5, 2)
,(6, 1)
) as T(OrderNo, Value)
)
select orderno, order_group, max(value) OVER (PARTITION BY order_group) FROM (
SELECT *,
count(value) OVER (ORDER BY orderno DESC) as order_group
from SourceTable
) as sub
order by orderno;
orderno | order_group | max
---------+-------------+-----
1 | 3 | 5
2 | 3 | 5
3 | 2 | 2
4 | 2 | 2
5 | 2 | 2
6 | 1 | 1
(6 rows)

Treat NULL as largest possible value

I want to get the row with maximum Transaction number Grouped on the basis of Code.
CREATE TABLE SaleOrder
(
TransactionNo Int,
SaleOrderDate DATE,
Code VARCHAR(25),
Quantity INT,
TotalAmount Numeric(18,2),
Remarks VARCHAR(25)
)
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-001-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (1, '2018-10-01', 'SO-001-OCT-18', 8, 2600, 'Hello');
INSERT INTO SaleOrder VALUES (2, '2018-10-01', 'SO-001-OCT-18', 12, 3400, 'Hello');
INSERT INTO SaleOrder VALUES (3, '2018-10-01', 'SO-001-OCT-18', 9, 2900, 'Hello');
INSERT INTO SaleOrder VALUES (4, '2018-10-01', 'SO-001-OCT-18', 2, 900, 'Hello');
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-002-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (NULL, '2018-10-01', 'SO-003-OCT-18', 6, 2500, 'Hello');
INSERT INTO SaleOrder VALUES (0, '2018-10-01', 'SO-004-OCT-18', 6, 2500, 'Hello');
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT MAX(ISNULL(TransactionNo, 1)) FROM SaleOrder GROUP BY Code)
Here when TransactionNo is NULL it's not returning any record against it while it should return that too.
There is absolutely no reason to treat NULL as largest possible value. You can always use the ROW_NUMBER trick:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY Code ORDER BY TransactionNo DESC) AS RN
FROM SaleOrder
)
SELECT * FROM cte
WHERE RN = 1
Result:
| TransactionNo | SaleOrderDate | Code | Quantity | TotalAmount | Remarks | RN |
|---------------|---------------|---------------|----------|-------------|---------|----|
| 4 | 2018-10-01 | SO-001-OCT-18 | 2 | 900.00 | Hello | 1 |
| NULL | 2018-10-01 | SO-002-OCT-18 | 6 | 2500.00 | Hello | 1 |
| NULL | 2018-10-01 | SO-003-OCT-18 | 6 | 2500.00 | Hello | 1 |
| 0 | 2018-10-01 | SO-004-OCT-18 | 6 | 2500.00 | Hello | 1 |
When TransactionNo is NULL and query returns more than one row that could not assigned to the filter
The below may help
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT TOP 1 MAX(ISNULL(NULL, 1)) FROM SaleOrder GROUP BY Code)
Note that this could take any record with TransactionNo having a NULL value.
Segregating the logic for TransactionNo filter would be easier to extend and maintain. Example below:
DECLARE #TransactionNo int
SELECT TOP 1 #TransactionNo = MAX(ISNULL(TransactionNo, 1)) FROM SaleOrder GROUP BY Code -- (OR) Logic here
SELECT * FROM SaleOrder O
WHERE TransactionNo = #TransactionNo
when you use = select in your where clause its totally wrong because this is possible you have multiple records,so you have to change your code like this:
SELECT MAX(ISNULL(TransactionNo, 1)),code FROM SaleOrder O
GROUP BY Code
but if you want to return only one record you can use it like this:
SELECT * FROM SaleOrder O
WHERE TransactionNo = (SELECT TOP 1 MAX(ISNULL(NULL, 1)) FROM SaleOrder GROUP BY Code)
I think this ISNULL check should solve your problem and replace = with IN subquery can return multiple records
WHERE ISNULL(TransactionNo, 1) IN
Try this:
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks from (
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks,
row_number() over (partition by code order by transactionno desc) rn
from (
select TransactionNo,SaleOrderDate,Code,Quantity,TotalAmount,Remarks,
coalesce(transactionno, count(*) over (partition by code) + 1) transactionno2
from SaleOrder
) a
) a where rn = 1
Explanation:
With this line coalesce(transactionno, count(*) over (partition by code) + 1) transactionno2 I assign greatest value per group (partitioned by code) where it's null. But be aware when you have two NULLs, rows will be tied in this case and it would be non-deterministic.
Below code will give you much more info than you requested, you can play with it, add some comment if have any questions.
CREATE TABLE #SaleOrder
(
TransactionNo Int,
#SaleOrderDate DATE,
Code VARCHAR(25),
Quantity INT,
TotalAmount Numeric(18,2),
Remarks VARCHAR(25)
)
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-001-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (1, '2018-10-01', 'SO-001-OCT-18', 8, '2600', 'Hello');
INSERT INTO #SaleOrder VALUES (2, '2018-10-01', 'SO-001-OCT-18', 12, '3400', 'Hello');
INSERT INTO #SaleOrder VALUES (3, '2018-10-01', 'SO-001-OCT-18', 9, '2900', 'Hello');
INSERT INTO #SaleOrder VALUES (4, '2018-10-01', 'SO-001-OCT-18', 2, '900', 'Hello');
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-002-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (NULL, '2018-10-01', 'SO-003-OCT-18', 6, '2500', 'Hello');
INSERT INTO #SaleOrder VALUES (0, '2018-10-01', 'SO-004-OCT-18', 6, '2500', 'Hello');
-- final select
SELECT top 1 -- optional, if you want to return 1 record
Code,
sum(Quantity) as totalQuantity,
sum(TotalAmount) as totallAmount,
count(1) as totalOrdersPerCode
FROM #SaleOrder O
group by Code
order by count(1) desc
-- drop temp table
drop table #SaleOrder

Remove duplicates values when all values are the same

I am using SQL workbench/J connecting to amazon redshift.
I have the following data in a table (there are more columns that need to be kept but are all the exact same values for each unique claim_id regardless of line number):
Member ID | Claim_ID | Line_Number |
1 100 1
1 100 2
1 100 1
1 100 2
2 101 13
2 101 13
2 101 13
2 101 13
3 102 12
3 102 12
1 103 2
1 103 2
I want it to become the following which will remove any duplicates based on claim_id (it does not matter which line number is kept):
Member ID | Claim_ID | Line_Number |
1 100 1
2 101 13
3 102 12
1 103 2
I have tried the following:
select er_main.member_id, er_main.claim_id, er_main.line_number,
temp.claim_id, temp.line_number
from OK_ER_30 er_main
inner join (
select row_number() over (partition by claim_id order by line_number desc) as seqnum
from
OK_ER_30 temp) temp
ON er_main.claim_id = temp.claim_id and seqnum = 1
Order by er_main.claim_id, temp.line_number
and this:
select * from ok_er_30
where claim_id in
(select distinct claim_id
from ok_er_30
group by claim_id
)
order by claim_id desc
I have checked many other ways of pulling only one row per distinct claim_id but nothing has worked.
try this
select Distant(Member_ID,Claim_ID,max(Line_Number)) group by Member_ID,Claim_ID
Check out the following code.
declare #OK_ER_30 table(Member_ID int, Claim_ID int, Line_Number int);
insert #OK_ER_30 values
(1, 100, 1),
(1, 100, 2),
(1, 100, 1),
(1, 100, 2),
(2, 101, 13),
(2, 101, 13),
(2, 101, 13),
(2, 101, 13),
(3, 102, 12),
(3, 102, 12),
(1, 103, 2),
(1, 103, 2);
with
t as(
select *, row_number() over(
partition by Member_ID, Claim_ID order by (select 0)
) rn
from #OK_ER_30
)
delete from t where rn > 1;
select * from #OK_ER_30;
Try this,
select Member_ID,Claim_ID,max(Line_Number) group by Member_ID,Claim_ID

Multiple Ranks in one table

I need the following, Can anyone please help me do it.
Rank Cust_Type Cust_Name Revenue
1 Top A 10000
2 Top B 9000
3 Top C 8000
1 Bottom X 5000
2 Bottom Y 6000
3 Bottom Z 7000
I need separate ranks for Top and Bottom Cust_Type and all this is in MySQL.
This is a bit tricky. You may want to use variables, such as in the following example:
SELECT (
CASE cust_type
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := cust_type END
) + 1 AS rank,
cust_type,
cust_name,
revenue
FROM sales,
(SELECT #curRow := 0, #curType := '') r
ORDER BY cust_type DESC, revenue DESC;
The (SELECT #curRow := 0, #curType := '') r part allows the variable initialization without requiring a separate SET command.
Test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Top', 'A', 10000);
INSERT INTO sales VALUES ('Top', 'B', 9000);
INSERT INTO sales VALUES ('Top', 'C', 8000);
INSERT INTO sales VALUES ('Bottom', 'X', 5000);
INSERT INTO sales VALUES ('Bottom', 'Y', 6000);
INSERT INTO sales VALUES ('Bottom', 'Z', 7000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 1 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 3 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
6 rows in set (0.00 sec)
Another test case:
CREATE TABLE sales (cust_type varchar(10), cust_name varchar(10), revenue int);
INSERT INTO sales VALUES ('Type X', 'A', 7000);
INSERT INTO sales VALUES ('Type X', 'B', 8000);
INSERT INTO sales VALUES ('Type Y', 'C', 5000);
INSERT INTO sales VALUES ('Type Y', 'D', 6000);
INSERT INTO sales VALUES ('Type Y', 'E', 4000);
INSERT INTO sales VALUES ('Type Z', 'F', 4000);
INSERT INTO sales VALUES ('Type Z', 'G', 3000);
Result:
+------+-----------+-----------+---------+
| rank | cust_type | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Type Z | F | 4000 |
| 2 | Type Z | G | 3000 |
| 1 | Type Y | D | 6000 |
| 2 | Type Y | C | 5000 |
| 3 | Type Y | E | 4000 |
| 1 | Type X | B | 8000 |
| 2 | Type X | A | 7000 |
+------+-----------+-----------+---------+
7 rows in set (0.00 sec)
You can obviously order the cust_type in ascending order instead of descending. I used descending just to have Top before Bottom in the original test case.
I found a problem with the solution using CASE, #curRow, and #curType. It depends on the execution plan MySQL uses to process the query. For example, it shows up if you add a join to the query. Then there is no guarantee that the rank is going to be computed correctly.
Making a slight change to the answer:
CREATE TABLE sales (cust_type_id int, cust_name varchar(10), revenue int);
CREATE TABLE cust_type (cust_type_id int, type_name varchar(10));
INSERT INTO cust_type VALUES (1, 'Bottom');
INSERT INTO cust_type VALUES (2, 'Top');
INSERT INTO sales VALUES (2, 'A', 10000);
INSERT INTO sales VALUES (2, 'B', 9000);
INSERT INTO sales VALUES (2, 'C', 8000);
INSERT INTO sales VALUES (1, 'X', 5000);
INSERT INTO sales VALUES (1, 'Y', 6000);
INSERT INTO sales VALUES (1, 'Z', 7000);
If I query only the sales table I get the rank in the correct order, but if I join to the cust_type table the rank values are no longer correct
SELECT (
CASE s.cust_type_id
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := s.cust_type_id END
) AS rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s,
cust_type t,
(SELECT #curRow := 0, #curType := 0) r
WHERE s.cust_type_id = t.cust_type_id
ORDER BY t.type_name DESC, s.revenue DESC;
Result:
+------+-----------+-----------+---------+
| rank | type_name | cust_name | revenue |
+------+-----------+-----------+---------+
| 1 | Top | A | 10000 |
| 2 | Top | B | 9000 |
| 3 | Top | C | 8000 |
| 3 | Bottom | Z | 7000 |
| 2 | Bottom | Y | 6000 |
| 1 | Bottom | X | 5000 |
+------+-----------+-----------+---------+
MySQL is running the initial query into a temp table and then the ORDER BY is executing against the temp table after a rank was already computed.
This is similar to Thomas's answer, but a little simpler:
SELECT (SELECT COUNT(Cust_Type) FROM sales
WHERE Cust_Type = S.Cust_Type AND Revenue >= S.Revenue) AS Rank,
Cust_Type,
Cust_Name,
Revenue
FROM sales AS S
ORDER BY Cust_Type DESC, Rank;
I'm not sure how the performance compares to the Daniel's solution, particularly on very large data sets, or if you have to use complex joins.
What is not exactly clear is how the items should be ranked (I assumed by Revenue) or whether you are only pulling a certain number of values (e.g. the top 3 and the bottom 3) so I assumed you wanted all values. Given those assumptions,
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Top'
Union All
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As Rank
From Table As T
Where Cust_Type = 'Bottom'
If you were trying to do this in a single non-union query you could do:
Select Cust_Name, Cust_Type
, Case Z.Cust_Type
When 'Top' Then Z.TopRank
Else Z.BottomRank
End As Rank
From (
Select Cust_Name, Cust_Type
, (Select Count(*)
From Table As T1
Where T1.Revenue > T.Revenue ) + 1 As TopRank
, (Select Count(*)
From Table As T1
Where T1.Revenue < T.Revenue ) + 1 As BottomRank
From Table As T
) As Z
This works for me by keeping ranking of sales revenue and ordering separate .
SELECT
(Select count(s1.revenue)+1 from sales s1 where s.cust_type_id = s1.cust_type_id and s1.revenue > s.revenue)
As rank,
t.type_name,
s.cust_name,
s.revenue
FROM sales s LEFT JOIN
cust_type t USING(cust_type_id)
Group by t.type_name,s.cust_name,s.revenue DESC order by s.revenue DESC;
For JOIN tables problem, I found a solution.
I create a temporary table, this way MySQL maintain the order of value that I want to rank.
DROP TEMPORARY TABLE IF EXISTS tmp_mytable;
CREATE TEMPORARY TABLE tmp_mytable ENGINE = MEMORY
SELECT mytable.id AS id,
mytable.login AS Login,
cliente.myrank_id AS id_myrank,
mytable.rankvalue AS rankvalue
FROM mytable
INNER JOIN myjoin ON (mytable.id_myjoin = myjoin.id)
ORDER BY 3, 4 DESC;
SELECT id, login, IFNULL(id_myrank, 0) AS id_myrank, rankvalue,
#rank := IF(#prev_born = IFNULL(id_myrank, 0), #rank + 1, 1) AS ranking,
#prev_Born := IFNULL(id_myrank, 0) AS fake_field
FROM tmp_mytable, (select #prev_born := 0, #rank := 0) r
-- HAVING ranking < 20;
*PS: I tried with View creation, but insn't work too.
Here are solutions for MySQL 5.7 and MySQL 8.0
MySQL 5.7 solution
SELECT cust_type,
cust_name,
revenue,
#new_rank := IF(#current_type = cust_type, #new_rank + 1, 1),
#current_type := cust_type
FROM rank_test,
(SELECT #new_rank := null, #current_type := null) r
ORDER BY cust_type DESC, revenue DESC;
MySQL 8.0 solution
SELECT cust_type,
cust_name,
revenue,
RANK() OVER (
PARTITION BY
cust_type
ORDER BY
revenue DESC
) new_rank
FROM rank_test;
Here is schema to try on dbfiddle
CREATE TABLE rank_test (
cust_type varchar(20),
cust_name varchar(20),
revenue numeric(19, 6)
);
INSERT INTO rank_test VALUES ('TOP', 'A', 10000);
INSERT INTO rank_test VALUES ('TOP', 'B', 9000);
INSERT INTO rank_test VALUES ('TOP', 'C', 8000);
INSERT INTO rank_test VALUES ('BOTTOM', 'A', 10000);
INSERT INTO rank_test VALUES ('BOTTOM', 'B', 9000);
INSERT INTO rank_test VALUES ('BOTTOM', 'C', 8000);