get a previous row in oracle on conditition - oracle10g

if I have a table which has the following table structure (id,username,send_date, message) how do I get a previous message for the different user via Oracle SQL
suppose I have following data
id, username, send_date ,message
1, user A , 12.02.2018 10:08:05 ,'problem 1'
2, user B , 12.02.2018 11:34:12 ,'what ?'
3, user B , 12.02.2018 14:12:02 ,'try this'
4, user A , 13.02.2018 09:29:23 ,'see here'
5, user B , 13.02.2018 13:34:12 ,'do this'
how do I get this instead
id, username, send_date ,message ,reply_to
1, user A , 12.02.2018 10:08:05 ,'problem 1' ,null
2, user B , 12.02.2018 11:34:12 ,'what ?' ,'problem1'
3, user B , 12.02.2018 14:12:02 ,'try this' ,'problem 1'
4, user A , 13.02.2018 09:29:23 ,'see here' ,'try this'
5, user B , 13.02.2018 13:34:12 ,'do this' ,'see here'
My query
select m.id, m.username, send_date, m.message,
lag(m.message) over (order by M.SEND_DATE) reply_to
from ts_messages m
order by m.send_date
returns this
id, username ,send_date ,message ,reply_to
1, user A ,12.02.2018 10:08:05 ,'problem 1' ,null
2, user B ,12.02.2018 11:34:12 ,'what ?' ,'problem1'
3, user B ,12.02.2018 14:12:02 ,'try this' ,'**what?**'
4, user A ,13.02.2018 09:29:23 ,'see here' ,'try this'
5, user B ,13.02.2018 13:34:12 ,'do this' ,'see here'
Basically, I need to display a message which I am replying to while writing a response, which is supposed to be the previous message written by someone other than myself

Here's one option which returns the desired result; though, doesn't seem to be very efficient as it accesses the source table several times. Though, if there aren't that many rows there, you probably won't see any difference from a hypothetical better solution (which I was unable to produce, but would really love to see).
SQL> with test (id, username, send_date, message) as
2 (select 1, 'user A', to_date('12.02.2018 10:08', 'dd.mm.yyyy hh24:mi'), 'problem 1' from dual union
3 select 2, 'user B', to_date('12.02.2018 11:34', 'dd.mm.yyyy hh24:mi'), 'what?' from dual union
4 select 3, 'user B', to_date('12.02.2018 14:12', 'dd.mm.yyyy hh24:mi'), 'try this' from dual union
5 select 4, 'user A', to_date('13.02.2018 09:29', 'dd.mm.yyyy hh24:mi'), 'see here' from dual union
6 select 5, 'user B', to_date('13.02.2018 13:34', 'dd.mm.yyyy hh24:mi'), 'do this' from dual
7 )
8 select
9 t.id,
10 t.username,
11 t.send_date,
12 t.message,
13 --
14 (select t2.message
15 from test t2
16 where t2.username <> t.username
17 and t2.id = (select max(t3.id)
18 from test t3
19 where t3.username <> t.username
20 and t3.id < t.id
21 )
22 ) reply_to
23 from test t
24 order by t.id;
ID USERNA SEND_DATE MESSAGE REPLY_TO
---------- ------ ---------------- --------- ---------
1 user A 12.02.2018 10:08 problem 1
2 user B 12.02.2018 11:34 what? problem 1
3 user B 12.02.2018 14:12 try this problem 1
4 user A 13.02.2018 09:29 see here try this
5 user B 13.02.2018 13:34 do this see here
SQL>

Related

Oracle SQL return value from child table with minimum row number with values in specific list

I have a need to select all rows from a table (main table) and join to another table (child table). In the results set, I want to include one column from the child table, that is only the first row / line number with a column value in a specified list. If there is no match for the specified list, it should be (null)
Desired Result:
ORDER_NO
ORDER_DATE
ORDER CUST
ORDER_VALUE
ITEM
1
02/14/2022
12345
$1,000.00
APPLES
2
02/13/2022
67890
$5,000.00
(null)
3
02/12/2022
45678
$100.00
PEARS
Example:
Main Table: Order Table
Order Number (Handle)
Order Date,
Order Customer,
Order Value
ORDER_NO
ORDER_DATE
ORDER CUST
ORDER_VALUE
1
02/14/2022
12345
$1,000.00
2
02/13/2022
67890
$5,000.00
3
02/12/2022
45678
$100.00
Child Table: Order Details Tbl
Order Number (Handle)
Line Number = Order Line No
Ordered Item,
Ordered Qty
ORDER_NO
LINE_NO
ITEM
1
10
APPLES
1
20
ORANGES
1
30
LETTUCE
2
10
BROCCOLI
2
20
CAULIFLOWER
2
30
LETTUCE
3
10
KALE
3
20
RADISHES
3
30
PEARS
In this example, the returned column is essentially the first line of the order that is a fruit, not a vegetable. And if the order includes no matching fruit, null is returned.
What my code is thus far:
SELECT
MAIN.ORDER_NO,
MAIN.ORDER_DATE,
MAIN.ORDER_CUST,
MAIN.ORDER_VALUE,
B.ITEM
FROM
MAIN
LEFT JOIN
(
SELECT
CHILD.ORDER_NO,
CHILD.LINE_NO,
CHILD.ITEM
FROM
CHILD
WHERE
CHILD.ORDER_NO||'_'||LINE_NO IN
(
SELECT
CHILD.ORDER_NO||'_'||MIN(LINE_NO) AS ORDER_LINE_NO
FROM
CHILD
WHERE
CHILD.ITEM IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
GROUP BY
CHILD.ORDER_NO
)
) B ON MAIN.ORDER_NO = B.ORDER_NO
'''
This code is of course not working as desired, as table 'B' is including all results from CHILD.
From Oracle 12, you can use:
SELECT o.*,
d.item
FROM orders o
LEFT OUTER JOIN LATERAL(
SELECT *
FROM order_details d
WHERE o.order_no = d.order_no
AND item IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
ORDER BY line_no ASC
FETCH FIRST ROW ONLY
) d
ON (1 = 1)
In earlier versions you can use:
SELECT o.*,
d.item
FROM orders o
LEFT OUTER JOIN(
SELECT d.*,
ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY line_no ASC)
AS rn
FROM order_details d
WHERE item IN ('APPLES','ORANGES','PEACHES','PEARS','GRAPES')
) d
ON (o.order_no = d.order_no AND rn = 1)
Which, for the sample data:
CREATE TABLE orders (ORDER_NO, ORDER_DATE, ORDER_CUST, ORDER_VALUE) AS
SELECT 1, DATE '2022-02-14', 12345, 1000.00 FROM DUAL UNION ALL
SELECT 2, DATE '2022-02-13', 67890, 5000.00 FROM DUAL UNION ALL
SELECT 3, DATE '2022-02-12', 45678, 100.00 FROM DUAL;
CREATE TABLE Order_Details (ORDER_NO, LINE_NO, ITEM) AS
SELECT 1, 10, 'APPLES' FROM DUAL UNION ALL
SELECT 1, 20, 'ORANGES' FROM DUAL UNION ALL
SELECT 1, 30, 'LETTUCE' FROM DUAL UNION ALL
SELECT 2, 10, 'BROCCOLI' FROM DUAL UNION ALL
SELECT 2, 20, 'CAULIFLOWER' FROM DUAL UNION ALL
SELECT 2, 30, 'LETTUCE' FROM DUAL UNION ALL
SELECT 3, 10, 'KALE' FROM DUAL UNION ALL
SELECT 3, 20, 'RADISHES' FROM DUAL UNION ALL
SELECT 3, 30, 'PEARS' FROM DUAL;
Both output:
ORDER_NO
ORDER_DATE
ORDER_CUST
ORDER_VALUE
ITEM
1
2022-02-14 00:00:00
12345
1000
APPLES
2
2022-02-13 00:00:00
67890
5000
null
3
2022-02-12 00:00:00
45678
100
PEARS
db<>fiddle here

Oracle Apex Bar Chart with Days of Week Aggregate

I'm trying to create a bar chart (with Oracle Apex v21.1.0) showing employee absences broken down by days of the week. i.e. a count of absences for each day of the week.
I'm having problems getting the 7 bars for the days of the week to show in chronological order.
This is my SQL:
-- sql for chart showing count of days absent for each day of week
select
TO_CHAR(ABD_DATE, 'D') as Day_Index_DOW,
TO_CHAR(ABD_DATE, 'DY') as Day_Name_DOW,
COUNT(TO_CHAR(ABD_DATE, 'D')) as Count_DOW
from F_ABSENT_DAYS
where ABD_EMP_ID = :P410_EMP_ID
group by TO_CHAR(ABD_DATE, 'D'), TO_CHAR(ABD_DATE, 'DY')
order by TO_CHAR(ABD_DATE, 'D')
Chart settings are:
Series Name is set to Day_Index_DOW
Label is set to Day_Name_DOW
Value is set to Count_DOW
This produces the 7 bars with the correct counts for the days of week.
The problem is, they are sorted in alphabetical order (FRI, MON, SAT...).
I'm trying to get them in chronological order (MON, TUE, WED...).
Is the error in the SQL or a setting in the designer?
I created sample data to demo your example.
SQL Query:
select day_number, day_name, sum(count_dow)
from (
select TO_CHAR(sysdate, 'D') day_number, TO_CHAR(sysdate, 'DY') day_name, 5 count_dow
from dual
union
select TO_CHAR(sysdate+1, 'D') day_number, TO_CHAR(sysdate+1, 'DY') day_name, 4 count_dow
from dual
union
select TO_CHAR(sysdate+2, 'D') day_number, TO_CHAR(sysdate+2, 'DY')day_name, 3 count_dow
from dual
union
select TO_CHAR(sysdate+3, 'D') day_number, TO_CHAR(sysdate+3, 'DY')day_name, 2 count_dow
from dual
union
select TO_CHAR(sysdate+4, 'D') day_number, TO_CHAR(sysdate+4, 'DY')day_name, 1 count_dow
from dual
union
select TO_CHAR(sysdate+5, 'D') day_number, TO_CHAR(sysdate+5, 'DY')day_name, 4 count_dow
from dual
union
select TO_CHAR(sysdate+6, 'D') day_number, TO_CHAR(sysdate+6, 'DY')day_name, 8 count_dow
from dual
union
select TO_CHAR(sysdate+3, 'D') day_number, TO_CHAR(sysdate+3, 'DY')day_name, 2 count_dow
from dual
union
select TO_CHAR(sysdate+4, 'D') day_number, TO_CHAR(sysdate+4, 'DY')day_name, 1 count_dow
from dual
union
select TO_CHAR(sysdate+5, 'D') day_number, TO_CHAR(sysdate+5, 'DY')day_name, 4 count_dow
from dual
union
select TO_CHAR(sysdate+6, 'D') day_number, TO_CHAR(sysdate+6, 'DY')day_name, 8 count_dow
from dual
)
group by day_number, day_name
order by day_number;
I set the label and value properties:
This is the resulting chart:
According to NLS_TERRITORY parameter for my DB, the first day of the week is Sunday, can vary on yours.

SELECT, format rows with different columns into a single row that share an ID

I'm trying to format one SELECT statement so that it outputs a resultset with combined values over a few columns.
I have a resultset like this:
ID VID PID VALUE
1 x 1 a
2 y 1 A
3 y 2 B
4 x 2 b
5 y 3 C
6 x 3 c
7 x 4 d
8 y 4 D
9 x 5 e
10 y 5 E
Can I format one SELECT statement to effectively join the values with duplicate PIDs into a single row? I'm only really interested in PID and VALUE, e.g.
PID VALUE1 VALUE2
1 a A
2 b B
3 c C
4 d D
5 e E
Otherwise, should I be using actual JOINs with queries acting on the same table?
I tried to use CASE but can get up to a resultset like this:
ID VID PID VALUE1 VALUE2
1 x 1 a NULL
2 y 1 NULL A
3 y 2 NULL B
4 x 2 b NULL
5 y 3 NULL C
6 x 3 c NULL
7 x 4 d NULL
8 y 4 NULL D
9 x 5 e NULL
10 y 5 NULL E
The query I'm using looks somewhat like this.
SELECT
ID,
VID,
PID,
CASE WHEN VID = 'x' THEN VALUE END VALUE1,
CASE WHEN VID = 'y' THEN VALUE END VALUE2
FROM BIGTABLE
WHERE PID IN (1, 2, 3, 4, 5)
AND VID IN ('x', 'y')
There's a lot of values of PID and VID that aren't just 1-5 and x & y so I'm selecting them that way from the whole table.
Do you mean like this? It's called "conditional aggregation."
with
resultset ( id, vid, pid, value ) as (
select 1, 'x', 1, 'a' from dual union all
select 2, 'y', 1, 'A' from dual union all
select 3, 'y', 2, 'B' from dual union all
select 4, 'x', 2, 'b' from dual union all
select 5, 'y', 3, 'C' from dual union all
select 6, 'x', 3, 'c' from dual union all
select 7, 'x', 4, 'd' from dual union all
select 8, 'y', 4, 'D' from dual union all
select 9, 'x', 5, 'e' from dual union all
select 10, 'y', 5, 'E' from dual
)
-- End of simulated resultset (for testing purposes only, not part of the solution).
-- SQL query begins below this line.
select pid,
min(case when vid = 'x' then value end) as value1,
min(case when vid = 'y' then value end) as value2
from resultset
-- WHERE conditions, if any are needed - as in your attempt
group by pid
order by pid
;
PID VALUE1 VALUE2
--- ------ ------
1 a A
2 b B
3 c C
4 d D
5 e E

Selecting specific row from a sub query depending on lowest priority

I have a table with Clients and their Insurance Providers. There is a column called Priority that ranges from 1-8. I want to be able to select the lowest priority insurance into my 'master table' I have a query that provides Fees, Dates, Doctors etc. and I need a subquery that I can join to the Main query on Client_ID The priority doesn't always start with 1. The Insurance Table is the Many side of the relationship
Row# Client_id Insurance_id Priority active?
1 333 A 1 Y
2 333 B 2 Y
3 333 C 1 N
4 222 D 6 Y
5 222 A 8 Y
6 444 C 4 Y
7 444 A 5 Y
8 444 B 6 Y
Answer should be
Client_id Insurance_id Priority
333 A 1
222 D 6
444 C 4
I was able to achieve the results I think you're asking for pretty easily utilizing SQL's ROW_NUMBER() function:
declare #tbl table
(
Id int identity,
ClientId int,
InsuranceId char(1),
[Priority] int,
Active bit
)
insert into #tbl (ClientId, InsuranceId, [Priority], Active)
values (1, 'A', 1, 1),
(1, 'A', 2, 1),
(1, 'B', 3, 1),
(1, 'B', 4, 1),
(1, 'C', 1, 1),
(1, 'C', 2, 0),
(2, 'C', 1, 1),
(2, 'C', 2, 1)
select Id, ClientId, InsuranceId, [Priority]
from
(
select Id,
ClientId,
InsuranceId,
[Priority],
ROW_NUMBER() OVER (PARTITION BY ClientId, InsuranceId ORDER BY [Priority] desc) as RowNum
from #tbl
where Active = 1
) x
where x.RowNum = 1
Results:
(8 row(s) affected)
Id ClientId InsuranceId Priority
----------- ----------- ----------- -----------
2 1 A 2
4 1 B 4
5 1 C 1
8 2 C 2
(4 row(s) affected)

T-SQL: finding rows in a different table without joins

I have two tables, I'll call TableA and TableB
TableA:
StartNumber EndNumber Country
1 10 USA
11 20 USA
21 30 Canada
31 40 France
41 50 France
51 60 Germany
TableB:
SomeNumber
5
15
55
22
35
46
49
For each number in TableB, I want to find the corresponding row in TableA where the number is between the StartNumber and EndNumber and return the name of the country. I then want to group these results on the country column and return the number of times each country appears. So the results would look like this:
Country Occurrences
USA 2
Germany 1
Canada 1
France 3
Not sure how to do this.
Here the query.
Select A.Country, count(*) as Occurrences
from
tableA A
inner join
tableB B
on B.someNumber between a.startnumber and b.endnumber
group by A.country
This should do the trick (but does use a join):
declare #TableA table (StartNumber int, EndNumber int, Country varchar(16));
insert into #TableA (StartNumber, EndNumber, Country)
select 1, 10, 'USA' union
select 11, 20, 'USA' union
select 21, 30, 'Canada' union
select 31, 40, 'France' union
select 41, 50, 'France' union
select 51, 60, 'Germany';
declare #TableB table (SomeNumber int);
insert into #TableB (SomeNumber)
select 5 union
select 15 union
select 55 union
select 22 union
select 35 union
select 46 union
select 49;
select
a.Country, count(*) Occurrences
from
#TableA a inner join
#TableB b on b.SomeNumber between a.StartNumber and a.EndNumber
group by
a.Country;