Oracle double select issue - oracle

So I have these 2 tables on Oracle:
CLIENT
cl_id cl_name
1 John
2 Maria
PAYMENTS
pa_id pa_date pa_status cl_id
1 2017-01-01 1 1
2 2017-01-01 1 2
3 2017-02-01 1 1
4 2017-02-01 1 2
5 2017-03-01 0 1
6 2017-03-01 1 2
I need a select statemant that gives me the client ID, NAME and the STATUS of his last payment. So the end result of my select should be:
cl_id cl_name pa_status
1 John 0
2 Maria 1
This is the CLIENT select that works:
select cl_id, cl_name from CLIENT;
This is the last status of the PAYMENT select that works:
select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1;
So now, I need to make them work together. I tried 2 ways that didn't work:
select cl_id, cl_name, (select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1 and PAYMENT.cl_id = CLIENT.CL_ID) as last_status from CLIENT;
error: invalid identifier
AND this:
select cl_id, cl_name, (select * from (
select pa_status from PAYMENT ORDER BY PA_DATE DESC)
where rownum = 1 ) as last_status from CLIENT;
which don't give me any errors, but only shows the same last status of John that is the last record:
cl_id cl_name last_status
1 John 0
2 Maria 0
Can anyone give me a hint?
Thanks

you need to use analystic function.
This kind of functions let you split your data to some groups, and rank the data for each group as you wish.
In your case:
Select * from (
Select id, name, status, row_number () over (partition by p.cl_id order by p.pa_date desc) as rw
From client c join payments p on p.cl_id = c.cl_id)
Inn where inn.rw = 1;

First take the max of date from for each clientid.
Select cl_id, max(pa_date) as pa_date from PAYMENTS group by cl_id
Now you take ur client table and join with above subquery
select c.cl_id, c.cl_name,
(select pa_status from PAYMENT t where t.pa_date=p.pa_date and t.cl_id=p.cl_id)
from CLIENT c join (Select cl_id, max(pa_date) as pa_date from PAYMENTS group by cl_id) p on p.cl_id=c.cl_id

You can use Oracle's KEEP LAST here:
select cl_id, c.cl_name, last_payment.status
from client
join
(
select
cl_id,
max(pa_status) keep (dense_rank last order by pa_date) as status
from payments
group by cl_id
) last_payment using (cl_id);
(If you want to include clients without payments, change the join to LEFT OUTER JOIN.)

This gets the max date for the client
and then gets the highest payment id with that date.
with max_date as (
select max(date) as max_date, cl_id from payments group by cl_id
)
select c.cl_id, c.cl_name, p.pa_sttus from client c
join payments p
on c.cl_id = p.cl_id
where p.pa_id = (select max(p2.pa_id) from payments p2
join max_date md
on p2.cl_id = md.cl_id
where p.cl_id = p2.cl_id
and p2.pa_date = md.max_date
)

Related

Last record row count in one to many laravel

I have a one to many relationship
shipment
id
bill
1
2222
2
4255
shipment_status
id
status
1
created
1
Shipped
2
created
2
Shipped
2
Delivered
3
created
3
Shipped
4
Created
4
Shipped
4
Delivered
5
Created
What I want to get
Results
count
status
2
Shipped
2
Delivered
1
Created
What I've done :
SELECT a.status,
b.total_status
FROM hipmenthistories a
INNER JOIN (
SELECT status,
MAX(created_at) last_status,
COUNT(*) total_status
FROM shipmenthistories
GROUP BY status
) b ON a.status = b.status`
Actual data in the db fiddle
Use ROW_NUMBER() and order the results by shipment and most recent status date. Then filter on the most recent status row:
See also db<>fiddle
WITH CTE AS (
SELECT status
, shipment_id
, ROW_NUMBER() OVER (PARTITION BY shipment_id ORDER BY created_at DESC, id DESC) AS rowNum
FROM shipmenthistories
)
SELECT status, COUNT(shipment_id) numOfShipments
FROM CTE
WHERE RowNum = 1
GROUP BY status;

Count(*) in Group By - Returns 1, instead of '0'

I am using the below oracle query to get the count of rows.
SELECT T.ID,T.NAME,COUNT(*) AS NO_OF_STUDENTS FROM STUDENT S RIGHT JOIN
TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID,T.NAME ORDER BY T.ID
Actual Result Should be:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 0 STUDENT
The Result what i am getting is:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 1 STUDENT
Since TEACHER 3 is not having any student, the result should be 0 Student. But i am getting the result as 1 Student.
You need to count a specific column (not use *, which includes nulls) in the table being outer joined to - since that is the one that might not have matching data. So:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
The only difference is COUNT(S.ID) instead of COUNT(*).
Simple demo with made-up data provided via CTEs:
with teacher (id, name) as (
select 1, 'Teacher 1' from dual
union all select 2, 'Teacher 2' from dual
union all select 3, 'Teacher 3' from dual
),
student (id, teacher_id) as (
select level, 1 from dual connect by level <= 10
union all
select level + 10, 2 from dual connect by level <= 5
)
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID;
ID NAME NO_OF_STUDENTS
---------- --------- --------------
1 Teacher 1 10
2 Teacher 2 5
3 Teacher 3 0
You could also do this as a left join, which I find more intuitive:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM TEACHER T
LEFT JOIN STUDENT S ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
which gets the same result.

oracle update from select range

I use this sql
SELECT *
FROM (
SELECT a.*, rownum rnum
FROM (
SELCT s.id, s.user_id, u.user_id u_id
FROM sample s
INNER JOIN template t ON s.t_id = t.t_id
INNER JOIN user u ON t.u_id = u.u_id
ORDER BY u.u_id desc
) a WHERE rownum <= 100
) WHERE rnum >= 0
The sql query result
id user_id u_id
1 A001 B001
2 A002 B002
3 A003 B003
4 A004 B004
5 A005 B005
May I use one sql for php to update the user_id as u_id's value, not use code to run a loop, thanks.
This is I want the result:
id user_id u_id
1 B001 B001
2 B002 B002
3 B003 B003
4 B004 B004
5 B005 B005
Yes, very simply, without thinking about the complicated update statement, you can use merge ...
merge into sample TGT
using (
-- your original query starts here
SELECT *
FROM (
SELECT a.*, rownum rnum
FROM (
SELECT s.id, s.user_id, u.user_id u_id
FROM sample s
INNER JOIN template t ON s.t_id = t.t_id
INNER JOIN user u ON t.u_id = u.u_id
ORDER BY u.u_id desc
) a WHERE rownum <= 100
) WHERE rnum >= 0
-- your original query ends here
) SRC
on ( TGT.id = SRC.id )
when matched then
update set TGT.user_id = SRC.u_id
;
You want update table sample using table user as source?
merge into sample s
using (select t.t_id, u.user_id
from user u, template t
where u.u_id = t.u_id) u_source
on (u_source.t_id = s.t_id)
when matched then update
set s.user_ud = u_source.user_id

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Query to exclude row based on another row's filter

I'm using Oracle 10g.
Question: How can I write query to return just ID only if ALL the codes for that ID end in 6? I don't want ID=1 because not all its codes end in 6.
TABLE_A
ID Code
===============
1 100
1 106
2 206
3 316
3 326
4 444
Desired Result:
ID
==
2
3
You simply want each ID where the count of rows for that id is the same as the count of rows where the third digit is six.
SELECT ID
FROM TABLE_A
GROUP BY ID
HAVING COUNT(*) = COUNT(CASE WHEN SUBSTR(code,3,1) = '6' THEN 1 END)
Try this:
SELECT DISTINCT b.id
FROM (
SELECT id,
COUNT(1) cnt
FROM table_a
GROUP BY id
) a,
(
SELECT id,
COUNT(1) cnt
FROM table_a
WHERE CODE LIKE '%6'
GROUP BY id
)b
WHERE a.id = b.id
AND a.cnt = b.cnt
Alternative using ANALYTIC functions:
SELECT DISTINCT id
FROM
(
SELECT id,
COUNT(1) OVER(PARTITION BY id) cnt,
SUM(CASE WHEN code LIKE '%6' THEN 1 ELSE 0 END) OVER(PARTITION BY id) sm
FROM table_a
)
WHERE cnt = sm

Resources