I have a very unique query that I must write, and hoping to do it all in 1 query, but not sure if I can.
I have a list of Articles, each article has a unique ID. The application passes this ID to the stored procedure. Then, I am to retrieve that article, AND, the next & previous articles. So, the list is sorted by date, and I can get the next & previous.
I do this via LEAD & LAG. It works in one query. However, in some cases, that next or previous article has a NULL in one of the fields. If that field is NULL, I am basically to get the next article where that field is NOT NULL.
Then there is one more thing. The article passed from the application has a category assigned to it. The next & previous articles must be of the same category.
The query is pretty big now, but it work getting the next & previous to the article ID passed as the subquery sorts everything by date. But with these 2 new criteria, the NULL factor and category factor, I do not see how it is possible to do this in one query.
Any thoughts? Or need some examples, or my existing query?
Thanks for all your time.
Oracle Setup:
CREATE TABLE articles ( id, category, value, dt ) AS
SELECT 1, 1, 1, DATE '2017-01-01' FROM DUAL UNION ALL
SELECT 2, 1, 2, DATE '2017-01-02' FROM DUAL UNION ALL -- Previous row
SELECT 3, 1, NULL, DATE '2017-01-03' FROM DUAL UNION ALL -- Ignored as value is null
SELECT 4, 1, 1, DATE '2017-01-04' FROM DUAL UNION ALL -- Chosen id
SELECT 5, 2, 3, DATE '2017-01-05' FROM DUAL UNION ALL -- Ignored as different category
SELECT 6, 1, 5, DATE '2017-01-06' FROM DUAL; -- Next row
Query:
SELECT *
FROM (
SELECT a.*,
LAG( CASE WHEN value IS NOT NULL THEN id END ) IGNORE NULLS OVER ( PARTITION BY category ORDER BY dt ) AS prv,
LEAD( CASE WHEN value IS NOT NULL THEN id END ) IGNORE NULLS OVER ( PARTITION BY category ORDER BY dt ) AS nxt
FROM articles a
)
WHERE :your_id IN ( id, nxt, prv )
AND ( id = :your_id OR value IS NOT NULL )
ORDER BY dt;
(:your_id is set to 4 in the example output below.)
Output:
ID CATEGORY VALUE DT PRV NXT
---------- ---------- ---------- ------------------- ---------- ----------
2 1 2 2017-01-02 00:00:00 1 4
4 1 1 2017-01-04 00:00:00 2 6
6 1 5 2017-01-06 00:00:00 4
Related
How can I make index column start over after reaching 5th row? I can't do that with a window function as there are no groups, I just need an index with max number of 5 like this:
date
index
01.01.21
1
02.01.21
2
03.01.21
3
04.01.21
4
05.01.21
5
06.01.21
1
07.01.21
2
and so on.
Appreciate any ideas.
You can use below solution for that purpose.
First, rank (row_number analytic function)the rows in your table within inline view
Then, use again the row_number function with partition by clause to group the previously ranked rows by TRUNC((rnb - 1)/5)
SELECT t."DATE"
, row_number()over(PARTITION BY TRUNC((rnb - 1)/5) ORDER BY rnb) as "INDEX"
FROM (
select "DATE", row_number()OVER(ORDER BY "DATE") rnb
from Your_table
) t
ORDER BY 1
;
demo on db<>fiddle
Your comment about using analytic functions is wrong; you can use analytic functions even when there are no "groups" (or "partitions"). Here you do need an analytic function, to order the rows (even if you don't need to partition them).
Here is a very simple solution, using just row_number(). Note the with clause, which is not part of the solution; I included it just for testing. In your real-life case, remove the with clause, and use your actual table and column names. The use of mod(... , 5) is pretty much obvious; it looks a little odd (subtracting 1, taking the modulus, then adding 1) because in Oracle we seem to count from 1 in all cases, instead of the much more natural counting from 0 common in other languages (like C).
Note that both date and index are reserved keywords, which shouldn't be used as column names. I used one common way to address that - I added an underscore at the end.
alter session set nls_date_format = 'dd.mm.rr';
with
sample_inputs (date_) as (
select date '2021-01-01' from dual union all
select date '2021-01-02' from dual union all
select date '2021-01-03' from dual union all
select date '2021-01-04' from dual union all
select date '2021-01-05' from dual union all
select date '2021-01-06' from dual union all
select date '2021-01-07' from dual
)
select date_, 1 + mod(row_number() over (order by date_) - 1, 5) as index_
from sample_inputs
;
DATE_ INDEX_
-------- ----------
01.01.21 1
02.01.21 2
03.01.21 3
04.01.21 4
05.01.21 5
06.01.21 1
07.01.21 2
You can combine MOD() with ROW_NUMBER() to get the index you want. For example:
select date, 1 + mod(row_number() over(order by date) - 1, 5) as idx from t
Good day!
Folks, I have an issue when I want to compare multiple values.
For example I have the Log Data for today, 23rd April. This is related to yesterday's job(22nd April) with specific name.
In my comparison, I should select all logs with their job dates with this specific name. I say that logs' dates must be bigger than our job with this specific name. There are other jobs earlier done with this specific name.
How can I choose only the last nearest one?
Please could you advise?
For example:
Table: Job
id
name
job start date
Table: Log
id
type
Log date
We have 3 different jobs with name "Coding"
and 3 different logs.
Jobs:
1, coding, 21-Apr-10
2, coding, 21-Apr-14
3, coding, 21-Apr-18
Logs:
1. 1, error, 23-Apr-10
2. 2, error, 23-Apr-15
3. 3, error, 20-Apr-18
4. 4, error, 23-Apr-18
And there, when I want to execute logs and jobs, I must look to their dates. For example, error with id 1 is related to job with id 1, because log's date is bigger than job's date.
Or also, log with id 3 should be related to job with id 2.
Generally speaking, something like this might do the job:
select *
from some_table t
where t.date_column = (select max(t1.date_column)
from some_table t1
where t1.id = t.id
)
If it doesn't, please, provide test case (CREATE TABLE and INSERT INTO sample data, which would be the input), and - based on that, tell us what you expect as the output.
[EDIT]
This is somewhat strange data model; jobs and logs aren't related by anything but dates ... anyway, here's code that returns what you described (at least, how I understood it).
SQL> with tjob (id, name, job_start) as
2 (select 1, 'coding', to_date('21.04.2010', 'dd.mm.yyyy') from dual union
3 select 2, 'coding', to_date('21.04.2014', 'dd.mm.yyyy') from dual union
4 select 3, 'coding', to_date('21.04.2018', 'dd.mm.yyyy') from dual
5 ),
6 logs (id, type, log_date) as
7 (select 1, 'error', to_date('23.04.2010', 'dd.mm.yyyy') from dual union
8 select 2, 'error', to_date('23.04.2015', 'dd.mm.yyyy') from dual union
9 select 3, 'error', to_date('20.04.2018', 'dd.mm.yyyy') from dual union
10 select 4, 'error', to_date('23.04.2018', 'dd.mm.yyyy') from dual
11 )
12 select j.id job_id
13 from tjob j
14 where j.job_start = (select max(j1.job_start)
15 from tjob j1
16 where j1.job_start < (select l.log_date
17 from logs l
18 where l.id = &log_id));
Enter value for log_id: 1
JOB_ID
----------
1
SQL> /
Enter value for log_id: 3
JOB_ID
----------
2
SQL>
i need a help to get solution to my problem, Please.
I have a table like this :
ID Number
|6 |20.90 |
|7 |45.00 |
|8 |52.00 |
|9 |68.00 |
|10 |120.00 |
|11 |220.00 |
|12 |250.00 |
The first range is 0 - 20.90.
When the value is in the half, the value id is for the max range.
When i got value 20.91, i want to get "ID = 6".
If the value is 31.00, i want to get "ID = 6"
If the value is
33.95, i want to get "ID = 7".
if the value is 44.99, i want to get ID = 7
How i can do it? Is there a function that will do what I need?
If you want the record with a number that is closest to your input, then you can use this:
select *
from (
select *
from mytable
order by abs(number - my_input_number), id
)
where rownum < 2
The inner query selects all records, but orders them by the distance they have from your input number. This distance can be calculated with number - my_input_number. But that could be negative, so we take the absolute value of that. This result is not output; it is just used to order by. So records with smaller distances will come first.
Now we need just the first of those records, and that is what the outer query does with the typical Oracle reserved word rownum: it represents a sequence number for every record of the final result set (1, 2, 3, ...). The where clause will effectively filter away all records we do not want to see, leaving only one (with smallest distance).
As mathguy suggested in comments, the order by now also has a second value to order by in case the input value is right at the mid point between the two closest records. In that case the record with the lowest id value will be chosen.
This is a good illustration of the power of analytic functions:
with mytable ( id, value ) as (
select 6, 20.90 from dual union all
select 7, 45.00 from dual union all
select 8, 52.00 from dual union all
select 9, 68.00 from dual union all
select 10, 120.00 from dual union all
select 11, 220.00 from dual union all
select 12, 250.00 from dual
),
inputs ( x ) as (
select 0.00 from dual union all
select 20.91 from dual union all
select 31.00 from dual union all
select 33.95 from dual union all
select 44.99 from dual union all
select 68.00 from dual union all
select 32.95 from dual union all
select 400.11 from dual
)
-- End of test data (not part of the solution). SQL query begins BELOW THIS LINE
select val as x, new_id as closest_id
from (
select id, val,
last_value(id ignore nulls) over (order by val desc) as new_id
from (
select id, (value + lead(value) over (order by value))/2 as val
from mytable
union all
select null, x
from inputs
)
)
where id is null
order by x -- if needed
;
Output:
X CLOSEST_ID
------ ----------
0 6
20.91 6
31 6
32.95 6
33.95 7
44.99 7
68 9
400.11 12
hoping I might be able to get some advise regarding Oracle SQL…
I have a table roughly as follows (there are more columns, but not necessary for this example)…
LOCATION USER VALUE
1 1 10
1 2 20
1 3 30
2 4 10
2 5 10
2 6 20
1 60
2 40
100
I’ve used rollup to get subtotals.
What I need to do is get the max(value) row for each location and express the max(value) as a percentage or fraction of the subtotal for each location
ie:
LOCATION USER FRAC
1 3 0.5
2 6 0.5
I could probably solve this using my limited knowledge of select queries, but am guessing there must be a fairly quick and slick method..
Thanks in advance :)
Solution using analytic functions
(Please note the WITH MY_TABLE AS serving only as dummy datasource)
WITH MY_TABLE AS
( SELECT 1 AS LOC_ID,1 AS USER_ID, 10 AS VAL FROM DUAL
UNION
SELECT 1,2,20 FROM DUAL
UNION
SELECT 1,3,30 FROM DUAL
UNION
SELECT 2,4,10 FROM DUAL
UNION
SELECT 2,5,10 FROM DUAL
UNION
SELECT 2,6,20 FROM DUAL
)
SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
RATIO_IN_LOC,
RANK() OVER (PARTITION BY LOC_ID ORDER BY RATIO_IN_LOC DESC) AS ORDER_IN_LOC
FROM
(SELECT LOC_ID,
USER_ID,
VAL,
VAL/SUM(VAL) OVER (PARTITION BY LOC_ID) AS RATIO_IN_LOC
FROM MY_TABLE
)
)
WHERE ORDER_IN_LOC = 1
ORDER BY LOC_ID,
USER_ID;
Result
LOC_ID USER_ID RATIO_IN_LOC
1 3 0,5
2 6 0,5
with inputs ( location, person, value ) as (
select 1, 1, 10 from dual union all
select 1, 2, 20 from dual union all
select 1, 3, 30 from dual union all
select 2, 4, 10 from dual union all
select 2, 5, 10 from dual union all
select 2, 6, 20 from dual
),
prep ( location, person, value, m_value, total ) as (
select location, person, value,
max(value) over (partition by location),
sum(value) over (partition by location)
from inputs
)
select location, person, round(value/total, 2) as frac
from prep
where value = m_value;
Notes: Your table exists already? Then skip everything from "inputs" to the comma; your query should begin with with prep (...) as ( ...
I changed user to person since user is a keyword in Oracle, you shouldn't use it for table or column names (actually you can't unless you use double quotes, which is a very poor practice).
The query will output two or three or more rows per location if there are ties at the top. Presumably this is what you desire.
Output:
LOCATION PERSON FRAC
---------- ---------- ----------
1 3 .5
2 6 .5
Id Field_id Field_value
------------------------------
1 10 'A'
1 11 'B'
1 12 'C'
I want to make rows like
Id Field_id Field_value data_1 data_2
--------------------------------------
1 10 'A' 'B' 'C'
Pl help.
Take a look at this:
with t (Id, Field_id, Field_value) as (
select 1, 10, 'A' from dual union all
select 1, 11, 'B' from dual union all
select 1, 12, 'C' from dual
)
select FIELD_ID1, "10_FIELD_ID", "11_FIELD_ID","12_FIELD_ID"
from (select id, field_id, min(field_id) over() field_id1, field_value from t)
pivot (
max(field_value) field_id
for field_id in (10, 11, 12)
)
FIELD_ID1 10_FIELD_ID 11_FIELD_ID 12_FIELD_ID
---------------------------------------------------
10 A B C
Read more about pivot here
Normally, people look for PIVOT query, however, your input data is not at all duplicate as already mentioned in comments.
What you could do is, using subquery factoring, select id, 10 as field_id, field_value, thus making field_it static value as 10. Then use LISTAGG. But, the grouped rows would be a single column as delimeter seperated values.
I hope your requirement is exactly what you stated and not a typo or a mistake while posting.