MySQL SELECT BETWEEN two points - mysql

Table places
pid pname
1 Amsterdam
2 London
3 Miami
4 Bonn
5 Oslo
6 Madrid
7 Lisbon
Table roots
id from to projectid
2 1 3 1 //Project#1 starts from Amsterdam
3 3 2 1 //Project#1 ends at London
4 3 5 2 //Project#2 starts from Miami and ends at Oslo
5 3 5 3
6 5 6 3
7 4 2 4
8 2 4 5
9 6 4 6
10 4 5 6
I need a resultset that contains all start and end points, ie:
start end projectid
1 2 1 //Amsterdam London
3 5 2 //Miami Oslo
3 6 3
4 2 4
2 4 5
6 5 6
And so, I need a list will show all projects between two cities, for example between London and Bonn:
4 2 4
2 4 5

This assume the id are in sequential order
Using variables you assign a rowid to know what is the first and the last entry of each project.
JOIN both together choose the from from F and the to from T
SQL Fiddle Demo
SELECT F.`from`, T.`to`, F.`projectid`
FROM (
SELECT `id`, `from`, `to`, `projectid`,
#row := IF(#prev = `projectid`,
#row + 1,
IF( #prev := `projectid`, 1, 1)
) as rn
FROM roots R
CROSS JOIN (SELECT #row = 0 , #prev = 0) x
ORDER BY `projectid`, `id`
) F
JOIN
(
SELECT `id`, `from`, `to`, `projectid`,
#row := IF(#prev = `projectid`,
#row + 1,
IF( #prev := `projectid`, 1, 1)
) as rn
FROM roots R
CROSS JOIN (SELECT #row = 0 , #prev = 0) x
ORDER BY `projectid`, `id` DESC -- HERE ID is DESC to get last entry
) T
ON F.`projectid` = T.`projectid`
and F.`rn` = 1
and T.`rn` = 1
OUTPUT
| from | to | projectid |
|------|----|-----------|
| 1 | 2 | 1 |
| 3 | 5 | 2 |
| 3 | 6 | 3 |
| 4 | 2 | 4 |
| 2 | 4 | 5 |
| 6 | 5 | 6 |
NOTE
Change demo query to SELECT * so you can check what is happening. Sometimes last entry equal to first entry.
For your second question depend on if you only count when city are in the first or end.
SELECT *
FROM <previous query>
WHERE (`from` = #CityA and `to`= #CityB )
OR (`from` = #CityB and `to`= #CityA )
Because if you want something considering cityes in between is much more complicated

Related

How to get just changed rows in mysql?

I have a table that has followed rows:
ID price rowNo
1 100 1
1 100 2
1 200 3
1 100 4
1 300 5
1 100 6
1 100 7
2 500 9
2 500 10
2 500 11
2 500 12
2 500 13
2 500 14
3 400 15
I want to get rows for each ID that the price has been changed. the output will be as follow:
ID price rowNo
1 100 1
1 200 3
1 100 4
1 300 5
1 100 6
2 500 9
3 400 15
You could use correlated sub queries in the where clause to test previous value or for existence
drop table if exists t;
create table t
(ID int, price int, rowNo int);
insert into t values
(1 , 100 , 1),
(1 , 100 , 2),
(1 , 200 , 3),
(1 , 100 , 4),
(1 , 300 , 5),
(1 , 100 , 6),
(1 , 100 , 7),
(2 , 500 , 9),
(2 , 500 , 10),
(2 , 500 , 11),
(2 , 500 , 12),
(2 , 500 , 13),
(2 , 500 , 14),
(3 , 400 , 15);
select t.*
from t
where t.price <> (select t1.price from t t1 where t1.id = t.id and t1.rowno < t.rowno order by t1.rowno desc limit 1) or
(select t1.price from t t1 where t1.id = t.id and t1.rowno < t.rowno order by t1.rowno desc limit 1) is null;
+------+-------+-------+
| ID | price | rowNo |
+------+-------+-------+
| 1 | 100 | 1 |
| 1 | 200 | 3 |
| 1 | 100 | 4 |
| 1 | 300 | 5 |
| 1 | 100 | 6 |
| 2 | 500 | 9 |
| 3 | 400 | 15 |
+------+-------+-------+
7 rows in set (0.003 sec)
**
All credits to user :#1000111
Mysql select row when column value changed
**
For older MySQL version that does not support window function:
SELECT id,price,rowNo
FROM ( SELECT *,
IF(#prevprice = YT.price, #rn := #rn + 1,
IF(#prevprice := YT.price, #rn := 1, #rn := 1)
) AS rn
FROM test_tbl YT
CROSS JOIN
(
SELECT #prevprice := -1, #rn := 1
) AS var
ORDER BY YT.id
) AS t
WHERE t.rn = 1
ORDER BY t.id
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=a0deed41b868781e7b7a84b69556769e
Result:
id price rowNo
1 100 1
1 200 3
1 100 4
1 300 5
1 100 6
2 500 9
3 400 15

Getting row number based on cate (MySql)

I have the following table and data using a query
SELECT b.*, (#rownum :=#rownum +1) AS row_number
FROM notices b, (SELECT #rownum := 0 ) row
Result:
id cate rownumber
-------------------
1 5 1
2 5 2
3 6 3
4 5 4
5 5 5
6 5 6
7 5 7
I want to output the result below without using row_number() or other function since my rdbms cannot support these.
id cate rownumber
-------------------
1 5 1
2 5 2
3 6 1
4 5 3
5 5 4
6 5 5
7 5 6
To simulate ROW_NUMBER with a partition in MySQL you can simply introduce another session variable to keep track of the cate group value as you iterate over the records in notices.
SET #rn = NULL;
SET #cate = NULL;
SELECT id, cate, rn
FROM
(
SELECT
#rn:=CASE WHEN #cate = cate THEN #rn + 1 ELSE 1 END AS rn,
#cate:=cate,
id,
cate
FROM notices
ORDER BY
cate, id
) t
ORDER BY cate, rn;
Demo here:
Rextester

Get the latest record for each employee older than a given date

I have a status history table that also includes future dated records.
Example: employee_jobs
id | employee_id | division_id | department_id | job_id | effective_date
1 100 1 1 1 2015-01-01
2 100 1 1 2 2016-01-01
3 100 1 2 4 2017-01-01
4 200 1 3 5 2016-01-01
5 300 1 3 6 2015-01-01
6 300 1 3 7 2016-05-25
I need a preforming SQL that will show a given employee_id's current live record when given a date: Example Date = 2016-08-15
The result set should be:
id | employee_id | division_id | department_id | job_id | effective_date
2 100 1 1 2 2016-01-01
4 200 1 3 5 2016-01-01
6 300 1 3 7 2016-05-25
I guess you want records for each employee having the latest effective_date with a constraint
(effective_date must be less than or equal to a given date)
SELECT
*
FROM
(
SELECT
*,
IF(#sameEmployee = employee_id, #rn := #rn + 1,
IF(#sameEmployee := employee_id, #rn := 1, #rn := 1)
) AS row_number
FROM employee_jobs
CROSS JOIN (SELECT #sameEmployee := 0, #rn := 1) var
WHERE effective_date <= '2016-08-15'
ORDER BY employee_id, effective_date DESC
) AS t
WHERE t.row_number = 1
ORDER BY t.employee_id

previous item in a list ordered on two columns

Let's say we have a table with columns DAY and NUMERO.
There can be numerous rows with the same value of DAY but NUMERO is always unique.
What could be the most efficient way to get the item immediately preceding an existing couple (DAY, NUMERO) in a list ordered by DAY, NUMERO ?
I precise that I need this for mysql and I don't want to add a specific index (that's the reason why I don't simply use a linear function of DAY and NUMERO).
Here's an ordered test case :
DAY | NUMERO
1 | 11
1 | 12
1 | 15
4 | 7
4 | 9
4 | 14
5 | 8
6 | 10
6 | 19
My request must do this :
(1,11) => nothing
(1,15) => (1,12)
(4,7) => (1,15)
(4,9) => (4,7)
(4,14) => (4,9)
EDIT :
my current best solution is to have two successive queries :
select * from item where day=? and numero<? order by day desc, numero desc limit 1;
select * from item where day<? order by day desc, numero desc limit 1;
If the first query gives a result, I don't have to run the second one.
A similar solution would be to use a union but mysql doesn't seem to authorize unions with more than one column.
Both solutions look too heavy for a problem seeming so simple...
UPDATE
Here is a solution that actually gives correct results!
SELECT T1.day,
T1.numero,
COALESCE(MAX(T2.[day]), MAX(T3.[day])) AS prev_day,
COALESCE(MAX(T2.numero), MAX(T3.numero)) AS prev_numero
FROM #table AS T1
LEFT JOIN #table AS T2 ON
(
T2.[day] = T1.[day] AND T2.numero < T1.numero
)
LEFT JOIN #table AS T3 ON
(
T3.[day] < T1.[day]
AND NOT EXISTS(
SELECT *
FROM #table AS T4
WHERE T4.[day] > T3.[day] AND T4.[day] < T1.[day]
)
)
-- Add where clause like so to get specific values
-- WHERE T1.day = 4 AND T1.numero = 7
GROUP BY T1.day, T1.numero
ORDER BY T1.day, T1.numero
Results:
day numero prev_day prev_numero
----------- ----------- ----------- -----------
1 11 NULL NULL
1 12 1 11
1 15 1 12
4 7 1 15
4 9 4 7
4 11 4 9
5 8 4 11
6 10 5 8
6 19 6 10
If you are only retrieving the previous pair based on a single pair as opposed to trying to do the entire table, as others have alluded to, you could try the following simple query -
:day = 4
:numero = 9
SELECT day, numero
FROM table
WHERE (day = :day AND numero < :numero)
OR (day < :day)
ORDER BY day DESC, numero DESC
LIMIT 1
This is an vanilla query, avoiding CTE's, aggregates or window functions.
-- (a Before b) := (a.day < b.day
-- OR (a.day = b.day AND a.numero < b.numero))
SELECT d1.day AS DAY
, d1.numero AS numero
, d0.day AS day0
, d0.numero AS numero0
FROM tmp.lutser d1
LEFT JOIN tmp.lutser d0
ON (d0.day < d1.day OR (d0.day = d1.day AND d0.numero < d1.numero ))
WHERE NOT EXISTS (SELECT *
FROM tmp.lutser d
WHERE (dx.day < d1.day OR (dx.day = d1.day AND dx.numero < d1.numero ))
AND (dx.day > d0.day OR (dx.day = d0.day AND dx.numero > d0.numero ))
)
ORDER BY day,numero
;
For reference, this is the query using window functions:
SELECT day
, numero
, lag(day) OVER (w1)
, lag(numero) OVER (w1)
FROM tmp.lutser
WINDOW w1 AS (ORDER BY day, numero)
;
Result:
day | numero | day0 | numero0
-----+--------+------+---------
1 | 11 | |
1 | 12 | 1 | 11
1 | 15 | 1 | 12
4 | 7 | 1 | 15
4 | 9 | 4 | 7
4 | 11 | 4 | 9
5 | 8 | 4 | 11
6 | 10 | 5 | 8
6 | 19 | 6 | 10
(9 rows)

How to select all records where a field is of a certain value until a record shows up that has a different value?

Let's say that we have a table with COLUMN1 and COLUMN 2. Here's a sample of the records:
COLUMN 1 | COLUMN 2
124 | 12
124 | 11
124 | 10
124 | 9
26 | 8
65 | 7
65 | 6
65 | 5
65 | 4
23 | 3
124 | 2
124 | 1
124 | 0
There is absolutely no pattern to this, but what I'd like to do is get:
COUNT(*) | COLUMN 1 | Smallest Column 2
4 | 124 | 9
1 | 26 | 8
4 | 65 | 4
1 | 23 | 3
3 | 124 | 0
So far, I've been doing this with PHP, but I'd like to find a way to do this in MySQL, as I'm sure it'd be a lot more efficient. The problem is, I can't even think of where to start with this. A regular GROUP BY COLUMN 1 wouldn't work because I want two results for 124, since it appears in two different instances. I've been fiddling around for hours and looking into the documentation and Google, but I haven't been able to find anything yet, and I was wondering if any of you would be able to point me in the right direction. Is this even possible with MySQL?
Well, it took a bit of fiddling, but here it is!
This assumes you have an id column in your table that you order by to get a consistent ordering (if you don't have an id column, order by timestamp or whatever in the inner query).
set #prev := '', #low := 0, #cnt := 0, #grp :=0;
select cnt, column1, low
from (
select
column2,
#low := if(#prev = column1, least(column2, #low), column2) low,
#cnt := if(#prev = column1, #cnt + 1, 1) cnt,
#grp := if(#prev = column1, #grp, #grp + 1) grp,
#prev := column1 column1
from (select column1, column2 from so9091342 order by id) x
order by grp, cnt desc) y
group by grp;
Here's the sql needed to set up a table for testing:
create table so9091342 (id int primary key auto_increment, column1 int, column2 int);
insert into so9091342 (column1, column2) values (124,12),(124,11),(124,10),(124,9),(26,8),(65,7),(65,6),(65,5),(65,4),(23,3),(124,2),(124,1),(124,0);
Output of above query:
+------+---------+------+
| cnt | column1 | low |
+------+---------+------+
| 4 | 124 | 9 |
| 1 | 26 | 8 |
| 4 | 65 | 4 |
| 1 | 23 | 3 |
| 3 | 124 | 0 |
+------+---------+------+
p.s. I named the table so9091342 because this is SO question ID #9091342.
Interesting question. I know Oracle much better than MySQL so I was able to get it working in Oracle. Might be a better way but this is what I came up with.
select count(col1) as cnt, col1, min(col2) as smallestCol2
from (
select col1, col2, col2-rnk as rnk
from
(
select col1, col2, RANK() OVER (PARTITION by col1 order by col2 asc) as rnk
from tmp_tbl
)
)
group by col1,rnk
order by min(col2) desc
I'm not quite sure how rank and partition work in MySQL but this might be helpful:
Rank function in MySQL
EDIT: To clarify what is going on in my query:
The inner query assigns a unique counter (RNK) to each value in column 1. The result of the most inner query is:
COL1 COL2 RNK
23 3 1
26 8 1
65 4 1
65 5 2
65 6 3
65 7 4
124 0 1
124 1 2
124 2 3
124 9 4
124 10 5
124 11 6
124 12 7
By subtracting the rank from column 2, you can get a unique value for each grouping of column 1 values. The result of the second nested query is:
COL1 COL2 RNK
23 3 2
26 8 7
65 4 3
65 5 3
65 6 3
65 7 3
124 0 -1
124 1 -1
124 2 -1
124 9 5
124 10 5
124 11 5
124 12 5
Then you can group on column 1 and that unique value. The final result:
CNT COL1 SMALLESTCOL2
4 124 9
1 26 8
4 65 4
1 23 3
3 124 0