I have a simple table and I need to identified groups of four rows (the groups aren't consecutives), but each rows of each row has a +1 in the value. For example:
----------------------
| language | id |
----------------------
| C | 16 |
| C++ | 17 |
| Java | 18 |
| Python | 19 |
| HTML | 65 |
| JavaScript | 66 |
| PHP | 67 |
| Perl | 68 |
----------------------
I want to add a column that indicates the group or set, how is possible to get this output using MySQL?:
----------------------------
| language | id | set |
----------------------------
| C | 16 | 1 |
| C++ | 17 | 1 |
| Java | 18 | 1 |
| Python | 19 | 1 |
| HTML | 65 | 2 |
| JavaScript | 66 | 2 |
| PHP | 67 | 2 |
| Perl | 68 | 2 |
----------------------------
Note that in this examples is only 2 sets (it could be 1 or more sets) and they didn't start in 16 (such values are not knowledged, but the restriction is that each id value of each row has this form n, n+1, n+2 and n+3).
I've been investigating about Gaps & Islands problem but didn't figure how to solve it by using their solutions. Also I search on stackoverflow but the closest question that I found was How to find gaps in sequential numbering in mysql?
Thanks
SELECT language,id,g
FROM (
SELECT language,id,
CASE WHEN id=#lastid+1 THEN #n ELSE #n:=#n+1 END AS g,
#lastid := id As b
FROM
t, (SELECT #n:=0) r
ORDER BY
id
) s
EDIT
In case you want just 4 per group add a row number variable:
SELECT language,id,g,rn
FROM (
SELECT language,id,
CASE WHEN id=#lastid+1 THEN #n ELSE #n:=#n+1 END AS g,
#rn := IF(#lastid+1 = id, #rn + 1, 1) AS rn,
#lastid := id As dt
FROM
t, (SELECT #n:=0) r
ORDER BY
id
) s
Where rn <=4
FIDDLE
select language,
#n:=if(#m+1=id, #n, #n+1) `set`,
(#m:=id) id
from t1,
(select #n:=0) n,
(select #m:=0) m
Demo on sqlfiddle
You can use the following query:
SELECT l.*, s.rn
FROM languages AS l
INNER JOIN (
SELECT minID, #rn2:=#rn2+1 AS rn
FROM (
SELECT MIN(id) AS minID
FROM (
SELECT id,
id - IF (true, #rn1:=#rn1+1, 0) AS grp
FROM languages
CROSS JOIN (SELECT #rn1:=0) AS var1
ORDER BY id) t
GROUP BY grp
HAVING COUNT(grp) = 4 ) u
CROSS JOIN (SELECT #rn2:=0) AS var2
) s ON l.id BETWEEN minID AND minID + 3
The above query identifies islands of exactly 4 consecutive records and returns there records only. It is easily modifiable to account for a different number of consecutive records.
Please also note the usage of IF conditional: it guarantees that #rn1 is first initialized and then used in order to calculate grp field.
Demo here
Related
I am struggling with this complex query. I am trying to insert the order position of some products.
For example,
I have currently table 1 with a position of NULL, I want to group each Product ID and assign each size a menu position based on ProductID group and using this FIND_IN_SET:
FIND_IN_SET(size,"UNI,XS,S,M,L,XL,XXL,3XL,4XL,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60") asc;
In other words, I want it to look like Table2.
Table1
ID | ProductID | Size | Menu_position
1 | 100 | S | NULL
2 | 100 | M | NULL
3 | 100 | L | NULL
4 | 101 | 40 | NULL
5 | 101 | 41 | NULL
6 | 101 | 42 | NULL
7 | 102 | XS | NULL
8 | 102 | L | NULL
Table2
ID | ProductID | Size | Menu_position
1 | 100 | S | 1
2 | 100 | M | 2
3 | 100 | L | 3
4 | 101 | 40 | 1
5 | 101 | 41 | 2
6 | 101 | 42 | 3
7 | 102 | XS | 1
8 | 102 | L | 2
What I collected so far:
Number of products Group:select count(distinct ProductID) from Table1
Sort size based on specific order: SELECT * FROM Table1 ORDER BY FIND_IN_SET(size,"UNI,XS,S,M,L,XL,XXL,3XL,4XL,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60") asc;
You can use variables in pre-MySQL 8.0:
SELECT t1.*,
(#rn := if(#p = productid, #rn + 1,
if(#p := productid, 1, 1)
)
) as menu_position
FROM (SELECT t1.*
FROM Table1 t1
ORDER BY ProductId,
FIND_IN_SET(size, 'UNI,XS,S,M,L,XL,XXL,3XL,4XL,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60') asc
) AS alias CROSS JOIN
(SELECT #p := -1, #rn := 0) params;
In MySQL 8+, this is much simpler:
select t1.*,
row_number() over (partition by productid order by FIND_IN_SET(size, 'UNI,XS,S,M,L,XL,XXL,3XL,4XL,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60')) as menu_position
from table1 t1
Create a two-column table containing all the the Size values in one column and the integer order of those sizes in the second column--call that table menu_pos. Join this to your Table on size, to produce a table or view (call this product_pos) containing columns product_id, size, and menu_pos. Then modify the menu_pos values to ensure that they are strictly sequential using a window function, such as:
select
product_id,
size,
rank() over (partition by product_id order by menu_pos) as new_menu_pos
from
product_pos;
Window functions require MySQL 8.
Lets say I have a table with few fields, e.g. A, B, C, D
I need to group by field A, and select most occuring values from B, C, D.
Example:
+---+---+---+----+
| A | B | C | D |
+---+---+---+----+
| 1 | 3 | 5 | 15 |
+---+---+---+----+
| 1 | 5 | 6 | 32 |
+---+---+---+----+
| 1 | 5 | 6 | 34 |
+---+---+---+----+
| 2 | 7 | 5 | 50 |
+---+---+---+----+
| 2 | 8 | 1 | 32 |
+---+---+---+----+
Expected result:
+---+---+---+----+
| A | B | C | D |
+---+---+---+----+
| 1 | 5 | 6 | 15 |
+---+---+---+----+
| 2 | 7 | 5 | 50 |
+---+---+---+----+
I saw a lot of examples how to select most occurring value from one column by using COUNT(*) and than using MAX on top of that. But what to do in this case ?
The query looks a bit complicated because you have to do it for 3 columns. The idea is to rank the rows by counts grouping by combinations of a-b,a-c,a-d and getting the first row for each combination. This is done using variables. In case of ties for counts the lowest value of b,c or d is returned. (this can be changed if the ordering needs to be reversed.) Finally one more aggregation is required to get corresponding values on to one row.
SQL Fiddle Demo
select a,max(b),max(c),max(d)
from (
select a,b,c,d
from (select a,b,c,d,
#rn:=case when #prev=a then #rn+1 else 1 end as rank,
#prev:=a
from (select a,b,null as c,null as d,count(*) as cnt
from tbl
group by a,b
) t
cross join (select #rn:=0,#prev:='') r
order by a,cnt desc,b
) t
where rank = 1
union all
select a,b,c,d
from (select a,b,c,d,
#rn:=case when #prev=a then #rn+1 else 1 end as rank,
#prev:=a
from (select a,null as b,c,null as d,count(*) as cnt
from tbl
group by a,c
) t
cross join (select #rn:=0,#prev:='') r
order by a,cnt desc,c
) t
where rank = 1
union all
select a,b,c,d
from (select a,b,c,d,
#rn:=case when #prev=a then #rn+1 else 1 end as rank,
#prev:=a
from (select a,null as b,null as c,d,count(*) as cnt
from tbl
group by a,d
) t
cross join (select #rn:=0,#prev:='') r
order by a,cnt desc,d
) t
where rank = 1
) t
group by a
Let's say I've a table
+----+------------+
| id | condition |
+----+------------+
| 1 | open |
+----+------------+
| 2 | content |
+----+------------+
| 3 | content |
+----+------------+
| 4 | close |
+----+------------+
| 5 | nocontentx |
+----+------------+
| 6 | nocontenty |
+----+------------+
| 7 | open |
+----+------------+
| 8 | content |
+----+------------+
| 9 | close |
+----+------------+
| 10 | nocontentz |
+----+------------+
| 11 | open |
+----+------------+
| 12 | content |
+----+------------+
and want to get a new table where I get the IDs (the first and the last) of the values between "close" and "open". Note that the values between this two conditions are dynamic (I can't search by "nocontent"whatever)
Such as I get this table:
+----+----------+--------+
| id | start_id | end_id |
+----+----------+--------+
| 1 | 5 | 6 |
+----+----------+--------+
| 2 | 10 | 10 |
+----+----------+--------+
Thanks in advance!
http://sqlfiddle.com/#!2/c255c8/2
You can do this using a correlated subquery:
select (#rn := #rn + 1) as id,
id as startid,
(select id
from atable a2
where a2.id > a.id and
a2.condition = 'close'
order by a2.id asc
limit 1
) as end_id
from atable a cross join
(select #rn := 0) vars
where a.condition = 'open';
The working SQL Fiddle is here.
Note this returns the third open as well. If you don't want it, then add having end_id is not null to the end of the query.
EDIT:
If you know the ids are sequential, you can just add and subtract 1 from the above query:
select (#rn := #rn + 1) as id,
id+1 as startid,
(select id
from atable a2
where a2.id > a.id and
a2.condition = 'open'
order by a2.id asc
limit 1
) - 1 as end_id
from atable a cross join
(select #rn := 0) vars
where a.condition = 'close';
You can also do this in a different way, which is by counting the number of open and closes before any given row and using this as a group identifier. The way your data is structured, every other group is what you are looking for:
select grp, min(id), max(id)
from (select t.*,
(select sum(t2.condition in ('open', 'close'))
from t t2
where t2.id <= t.id
) as grp
from t
) t
where t.condition not in ('open', 'close') and
grp % 2 = 0
group by grp;
I have data which is formed like this:
+----------+-------+-------+
| DAY | VALUE | Name |
+----------+-------+-------+
| 01/01/14 | 1030 | BOB
| 01/02/14 | 1020 | BOB
| 01/03/14 | 1080 | BOB
| 01/04/14 | 1090 | BOB
| 01/05/14 | 1040 | BOB
| 01/08/14 | 1030 | BOB
| 01/11/14 | 4030 | BOB
| 01/12/14 | 5000 | BOB
| 01/13/14 | 6000 | BOB
| 01/14/14 | 1096 | BOB
| 01/14/14 | 1200 | MIKE
| 01/15/14 | 1040 | MIKE
| 01/16/14 | 1600 | MIKE
| 01/17/14 | 1070 | MIKE
| 01/18/14 | 1340 | MIKE
| 01/19/14 | 1060 | MIKE
| 01/01/14 | 6000 | JANE
| 01/02/14 | 1700 | JANE
| 01/03/14 | 1070 | JANE
| 01/04/14 | 8000 | JANE
+----------+-------+------+
For each name there needs to be a row for the dates between 01/01/14 to 02/01/14 (1 month). As you can see Bob, Mike, and Jane (although in my real database there are thousands of names) are all missing dates between this time period. I would like to somehow insert the missing rows by interpolation of some sort. For example Bob is missing 01/06/14 and 01/07/14. I would like it to interpolate by adding these two dates and then the values to be the average of the two field between so these two missing fields would both have the value ((1040+1030)/2) = 1035. If there is no data before like for MIKE (starts at 01/14/14) I would like all the new rows to have 01/14/14 value now. I have tried various different techniques such as using coalesce command, cursors, but can't get it to work. Also I am not set on having these EXACT values, if there is some sort of math library which can interpolate I would be open to this as well. Thanks.
You have two problems, generating the rows and interpolating the values. You can generate the rows with this SQL:
select d.day, n.name, t.value
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day;
Doing the interpolation is troublesome. You can do this using variables and multiple sorting. Here is logic:
select day, name, value, prev_value,
#value as next_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select d.day, n.name, t.value,
#value as prev_value,
#value := if(#name = name and value is not null, value, #value),
#name := name
from (select distinct name from table t) n cross join
(select distinct day from table t) d left outer join
table t
on t.name = n.name and t.day = d.day cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day
) t cross join
(select #name := '', #value := NULL) vars
order by n.name, d.day desc;
This will probably work for you, but it is depending on MySQL evaluating the expressions in order in each select (for the assignment of variables). You can make the syntax more complicated to fix this, but that would hide the logic. You can now implement the logic that you want:
select day, name,
(case when value is not null then value
when prev_value is not null and next_value is not null
then (prev_value + next_value) / 2
when prev_value is null then next_value
else prev_value
end) as value
from (<previous query here>) t;
I have a table structure as given below and what I'd like to be able to do is retrieve the top three records with the highest value for each Company code.
I've googled and I couldn't find a better way so hopefully you guys can help me out.
By the way, I'm attempting this in MySQL and SAP HANA. But I am hoping that I can grab the "structure" if the query for HANA if I can get help for only MySQL
Thanks much!
Here's the table:
http://pastebin.com/xgzCgpKL
In MySQL you can do
To get exactly three records per group (company) no matter ties emulating ROW_NUMBER() analytic function. Records with the same value get the same rank.
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, #n + 1, 1) rnum, #g := company
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
Get all records per group that have a rank from 1 to 3 emulating DENSE_RANK() analytic function
SELECT company, plant, value
FROM
(
SELECT company, plant, value, #n := IF(#g = company, IF(#v = value, #n, #n + 1), 1) rnum, #g := company, #v := value
FROM table1 CROSS JOIN (SELECT #n := 0, #g := NULL, #v := NULL) i
ORDER BY company, value DESC, plant
) q
WHERE rnum <= 3;
Output:
| COMPANY | PLANT | VALUE |
|---------|-------|-------|
| 1 | C | 5 |
| 1 | B | 4 |
| 1 | A | 3 |
| 1 | E | 3 |
| 1 | G | 3 |
| 2 | G | 6 |
| 2 | C | 5 |
| 2 | D | 3 |
| 3 | E | 8 |
| 3 | A | 7 |
| 3 | B | 3 |
| 3 | G | 3 |
Here is SQLFiddle demo
UPDATE: Now it looks like HANA supports analytic functions so the queries will look like
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
ROW_NUMBER() OVER (PARTITION BY company ORDER BY value DESC) rnum
FROM table1
)
WHERE rnum <= 3;
SELECT company, plant, value
FROM
(
SELECT company, plant, value,
DENSE_RANK() OVER (PARTITION BY company ORDER BY value DESC) rank
FROM table1
)
WHERE rank <= 3;
Here is SQLFiddle demo It's for Oracle but I believe it will work for HANA too