SQL Server 2012 - Find a steadily rising value of a column - sql

I have a table like below:
ID | Name | Ratio | Miles
____________________________________
1 | ABC | 45 | 21
1 | ABC | 46 | 24
1 | ABC | 46 | 25
2 | PQR | 41 | 19
2 | PQR | 39 | 17
3 | XYZ | 27 | 13
3 | XYZ | 26 | 11
4 | DEF | 40 | 18
4 | DEF | 40 | 18
4 | DEF | 42 | 20
I want to write a query that will find an ID whose Miles value has been steadily rising.
For instance,
Miles values of Name 'ABC' and 'DEF' are steadily rising.
It's fine if the Miles value drops by up to 5% and rises again.
It should also include this Name.
I tried self join on this table but it gives me Cartesian product.
Can anyone help me with this?
I am using SQL server 2012.
Thanks in advance!

SQL tables represent unordered sets. Let me assume that you have a column that specifies the ordering. Then, you can use lag() and some logic:
select id, name
from (select t.*,
lag(miles) over (partition by id order by orderingcol) as prev_miles
from t
) t
group by id, name
having min(case when prev_miles is null or miles >= prev_miles * 0.95 then 1 else 0 end) = 1;
The having clause is simply determining if all the rows meet your specific condition.

try this:
Note: 5% case is not handled here
create table #tmp(ID INT,Name VARCHAR(50),Ratio INT,Miles INT)
INSERT INTO #tmp
SELECT 1,'ABC',45,21
union all
SELECT 1,'ABC',46,24
union all
SELECT 1,'ABC',46,25
union all
SELECT 2,'PQR',41,19
union all
SELECT 2,'PQR',39,17
union all
SELECT 3,'XYZ',27,13
union all
SELECT 3,'XYZ',26,11
union all
SELECT 4,'DEF',40,18
union all
SELECT 4,'DEF',40,18
union all
SELECT 4,'DEF',42,21
Select *,CASE WHEN Miles<=LEAD(Miles,1,Miles) OVER(partition by ID Order by ID) THEN 1
--NEED ADD 5%condition Here
ELSE 0 END AS nextMiles
into #tmp2
from #tmp
;with cte
AS(
select * , ROW_NUMBER() OVER (partition by ID,nextMiles order by ID) rn from #tmp2
)
SELECT DISTINCT ID,Name FROM cte WHERE rn>1
Drop table #tmp
Drop table #tmp2

Related

How to make sure the sql result is continued range?

I have table like:
id | low_number | high_number
-------------------------------
1 | 12 | 32
-------------------------------
2 | 13 | 33
-------------------------------
3 | 15 | 36
-------------------------------
4 | 33 | 50
-------------------------------
5 | 35 | 52
...
-------------------------------
17 | 52 | 80
I want to get result like:
id | low_number | high_number
-------------------------------
1 | 12 | 32
-------------------------------
4 | 33 | 50
-------------------------------
17 | 52 | 80
that is because the low_number bigger than the pervious row high_number.
How to write sql to get these result? I use postgresql
This seems like a recursive CTE problem. You want to choose the first row (by id) and then choose the next row based on that.
The idea is to cycle through the rows, one at a time. Then when the condition is met, transition to that row. And so on.
As a query, this looks like:
with recursive tt as (
select id, low_number, high_number, row_number() over (order by id) as seqnum
from t
),
cte as (
select id, low_number, high_number, seqnum, true as is_change, id as grouping_id
from tt
where seqnum = 1
union all
select tt.id, tt.low_number, tt.high_number, tt.seqnum, tt.low_number > t.high_number,
(case when tt.low_number > t.high_number then tt.id else cte.grouping_id end)
from cte join
t
on cte.grouping_id = t.id join
tt
on tt.seqnum = cte.seqnum + 1
)
select *
from cte
where is_change;
Here is a db<>fiddle.
Use the window function LAG() to get a value of a previous row, e.g.
WITH j AS (
SELECT
id,low_number,high_number,
LAG(high_number) OVER (ORDER BY id) AS prev_high_number
FROM t)
SELECT id,low_number,high_number FROM j
WHERE low_number > prev_high_number OR prev_high_number IS NULL;
Demo: db<>fiddle

quickly take all correlated rows from the table

I have a large table with multiple columns representing linked events. This includes columns id and nextId, where id means id of some event1, and nextId suggest in which another event this event1 was used. However, there is no column 'prev_id' which would say which event0 contributed to event1. Is it possible to build a query which will generate for me such a table without taking a very long running time?
Here is an example of what I mean:
id | nextId
10 | 34
5 | 67
22 | 23
2 | 10
16 | 22
4 | 5
What I want to have is the following:
prev_id | id | next_id
2 | 10 | 34
4 | 5 | 67
16 | 22 | 23
You can use a join:
select t.id as prev_id, t.nextid as id, tnext.nextid as next_id
from t join
t tnext
on tnext.id = t.nextid;
You only need a self join.
But for the sake of readability, I would recommend using a CTE:
with prevs as (
select nextid as id, id as previd from ids
)
select previd, id, nextid
from ids
join prevs using(id)
;
previd | id | nextid
--------+----+--------
4 | 5 | 67
2 | 10 | 34
16 | 22 | 23
(3 rows)
In addition to what others have said, you can also accomplish this using hierarchical queries.
WITH test_data AS (
SELECT 10 AS ID,34 AS nextID FROM DUAL
UNION SELECT 5,67 FROM DUAL
UNION SELECT 22,23 FROM DUAL
UNION SELECT 2,10 FROM DUAL
UNION SELECT 16,22 FROM DUAL
UNION SELECT 4,5 FROM DUAL
)
SELECT h.*
FROM (
SELECT PRIOR t.ID AS prevID,
t.ID,
t.nextID
FROM test_data t
CONNECT BY t.ID = PRIOR t.nextID
) h
WHERE h.prevID IS NOT NULL
ORDER BY h.prevID

Select Top 20 Distinct Rows in Each Category

I have a database table in the following format.
Product | Date | Score
A | 01/01/18 | 99
B | 01/01/18 | 98
C | 01/01/18 | 97
--------------------------
A | 02/01/18 | 99
B | 02/01/18 | 98
C | 02/01/18 | 97
--------------------------
D | 03/01/18 | 99
A | 03/01/18 | 98
B | 03/01/18 | 97
C | 03/01/18 | 96
I want to pick the first from every month such that there are no repeat products. For example, the output of the above table should be
Product | Date | Score
A | 01/01/18 | 99
B | 02/01/18 | 98
D | 03/01/18 | 99
How do I get this result with a single sql query? The actual table is much bigger than this and I want top 20 from every month without repetition.
This is a hard problem -- a type of subgraph problem that isn't really suitable to SQL. There is a brute force approach:
with jan as (
select *
from t
where date = '2018-01-01'
limit 1
),
feb as (
select *
from t
where date = '2018-02-01' and
product not in (select product from jan)
),
mar as (
select *
from t
where date = '2018-03-01' and
product not in (select product from jan) and
product not in (select product from feb)
)
select *
from jan
union all
select *
from feb
union all
select *
from mar;
You can generalize this with additional CTEs. But there is no guarantee that a month will have a product -- even when it could have had one.
It is possible by using row_number.
select * from (
select row_Number() over(partition by Product order by Product ) as rno,* from
Products
) as t where t.rno<=20
I think you want top 20 records every month without repeating products than below solution will be work.
select *
into #temp
from
(values
('A','01/01/18','99')
,('B','01/01/18','98')
,('C','01/01/18','97')
,('A','02/01/18','99')
,('B','02/01/18','98')
,('C','02/01/18','97')
,('D','03/01/18','99')
,('A','03/01/18','98')
,('B','03/01/18','97')
,('C','03/01/18','96')
) AS VTE (Product ,Date, Score )
select * from
(
select * , ROW_NUMBER() over (partition by date,product order by score ) as rn
from #TEMP
)
A where rn < 20

ORDER BY distance to another value

Lets say we have a table like that
id|value
--------
1 | 50
2 | 19
3 | 100
4 | 21
5 | -10
How can I use ORDER BY operator to order values by their distance to another value?
SELECT * FROM table ORDER BY nearest(value,30) DESC
To get this table:
id|value
--------
4 | 21
1 | 50
2 | 19
5 | -10
3 | 100
You may use:
SELECT * FROM table ORDER BY abs(value - 30) ASC
Not sure that all sql dialect accepts answer of Paul92.
Here is another solution:
SELECT *
FROM (
SELECT
t.*,
abs(value - 30) AS abs_value
FROM table t
) temp
ORDER BY abs_value

SQL query update by grouping

I'm dealing with some legacy data in an Oracle table and have the following
--------------------------------------------
| RefNo | ID |
--------------------------------------------
| FOO/BAR/BAZ/AAAAAAAAAA | 1 |
| FOO/BAR/BAZ/BBBBBBBBBB | 1 |
| FOO/BAR/BAZ/CCCCCCCCCC | 1 |
| FOO/BAR/BAZ/DDDDDDDDDD | 1 |
--------------------------------------------
For each of the /FOO/BAR/BAZ/% records I want to make the ID a Unique incrementing number.
Is there a method to do this in SQL?
Thanks in advance
EDIT
Sorry for not being specific. I have several groups of records /FOO/BAR/BAZ/, /FOO/ZZZ/YYY/. The same transformation needs to occur for each of these other (example) groups. The recnum can't be used I want ID to start from 1, incrementing, for each group of records I have to change.
Sorry for making a mess of my first post. Output should be
--------------------------------------------
| RefNo | ID |
--------------------------------------------
| FOO/BAR/BAZ/AAAAAAAAAA | 1 |
| FOO/BAR/BAZ/BBBBBBBBBB | 2 |
| FOO/BAR/BAZ/CCCCCCCCCC | 3 |
| FOO/BAR/BAZ/DDDDDDDDDD | 4 |
| FOO/ZZZ/YYY/AAAAAAAAAA | 1 |
| FOO/ZZZ/YYY/BBBBBBBBBB | 2 |
--------------------------------------------
Let's try something like this(Oracle version 10g and higher):
SQL> with t1 as(
2 select 'FOO/BAR/BAZ/AAAAAAAAAA' as RefNo, 1 as ID from dual union all
3 select 'FOO/BAR/BAZ/BBBBBBBBBB', 1 from dual union all
4 select 'FOO/BAR/BAZ/CCCCCCCCCC', 1 from dual union all
5 select 'FOO/BAR/BAZ/DDDDDDDDDD', 1 from dual union all
6 select 'FOO/ZZZ/YYY/AAAAAAAAAA', 1 from dual union all
7 select 'FOO/ZZZ/YYY/BBBBBBBBBB', 1 from dual union all
8 select 'FOO/ZZZ/YYY/CCCCCCCCCC', 1 from dual union all
9 select 'FOO/ZZZ/YYY/DDDDDDDDDD', 1 from dual
10 )
11 select row_number() over(partition by ComPart order by DifPart) as id
12 , RefNo
13 From (select regexp_substr(RefNo, '[[:alpha:]]+$') as DifPart
14 , regexp_substr(RefNo, '([[:alpha:]]+/)+') as ComPart
15 , RefNo
16 , Id
17 from t1
18 ) q
19 ;
ID REFNO
---------- -----------------------
1 FOO/BAR/BAZ/AAAAAAAAAA
2 FOO/BAR/BAZ/BBBBBBBBBB
3 FOO/BAR/BAZ/CCCCCCCCCC
4 FOO/BAR/BAZ/DDDDDDDDDD
1 FOO/ZZZ/YYY/AAAAAAAAAA
2 FOO/ZZZ/YYY/BBBBBBBBBB
3 FOO/ZZZ/YYY/CCCCCCCCCC
4 FOO/ZZZ/YYY/DDDDDDDDDD
I think that actual updating the ID column wouldn't be a good idea. Every time you add new groups of data you would have to run the update statement again. The better way would be creating a view and you will see desired output every time you query it.
rownum can be used as an incrementing ID?
UPDATE legacy_table
SET id = ROWNUM;
This will assign unique values to all records in the table. This link contains documentation about Oracle Pseudocolumn.
You can run the following:
update <table_name> set id = rownum where descr like 'FOO/BAR/BAZ/%'
This is pretty rough and I'm not sure if your RefNo is a single value column or you just made it like that for simplicity.
select
sub.RefNo
row_number() over (order by sub.RefNo) + (select max(id) from TABLE),
from (
select FOO+'/'+BAR+'/'+BAZ+'/'+OTHER as RefNo
from TABLE
group by FOO+'/'+BAR+'/'+BAZ+'/'+OTHER
) sub