How to use case on group by result in Oracle-sql - sql

I have a table containing the below column
person_id Person_Name Telephone_No City Email_Id Insert_TS
and there are certain other column like Alternate_name etc.
In this table, whenever there is any update in any of the particular a row is inserted . For example
P01 Radhe 0112311231 Bia b#b.com 09-NOV-2012 15:24:38
P01 Radhe Null Bia a#b.com 30-APR-2014 21:26:51
P02 Shayam 456897845 Albi s#b.com 30-APR-2014 14:36:03
P03 Radha Null xyz s1#b.com 31-APR-2014 14:36:03
Means, few record contains null in telephone field but all other fields have data
I want to display person_id Person_Name Telephone_No City Email_Id columns such that if there is any row containing telephone_no value not null corresponding to one person_id then the latest record of that should be displayed
else
latest record of the person should be displayed
to summarize display unique person_id with latest telephone_no for those person which does not have telephone_no latest record should be displayed.
I tried this way :
Get the unique person_id with latest telephone_no -- A
Get all unique person_id with latest record --- B
display the data as (B-A) + A .
But that is taking too much time and that does not seems to be efficient .
Please suggest a query to fetch the records faster
Other possible way could be using the case statement on group by result like grouping the values by Person_id if there is any telephone_no in each group display the one with latest value otherwise display the latest entry from group

Would it not be sufficient to always show the last row for each person? Assuming person_id, Insert_TS is unique:
select person_id, max(Insert_TS)
from T
group by person_id
then join that with the table to get all info.
For recent versions of oracle you can use row_number() as in
select ...
from (
select ..., row_number() over (partition by person_id
order by Insert_TS desc) as rn
) as x
where rn = 1;
I'm not sure I fully understand but here's a try. I get errors from SQLFiddle for Oracle, and I don't have access to Oracle my self so the following is tested on DB2 V10.5. I simplified a bit by removing a couple of columns. Furthermore the timestamps are different than yours:
with tmp as (select PERSON_ID, PERSON_NAME, TELEPHONE_NO, INSERT_TS
,row_number() over (partition by person_id
order by insert_ts desc) as rn
from t)
select * from tmp x
where rn = (
select coalesce(min(rn),1)
from tmp y
where x.person_id = y.person_id
and y.TELEPHONE_NO is not null
);
PERSON_ID PERSON_NAME TELEPHONE_NO INSERT_TS RN
--------- ----------- --------------- -------------------------- --------------------
P01 Radhe 011231123109 2012-05-22-14.27.35.037579 2
P02 Shayam 456897845 2014-05-22-14.27.35.037579 1
P03 Radha - 2014-06-22-14.27.35.037579 1
3 record(s) selected.

Related

Delete rows where date was least updated

How can I delete rows where dateupdated was least updated ?
My table is
Name Dateupdated ID status
john 1/02/17 JHN1 A
john 1/03/17 JHN2 A
sally 1/02/17 SLLY1 A
sally 1/03/17 SLLY2 A
Mike 1/03/17 MK1 A
Mike 1/04/17 MK2 A
I want to be left with the following after the data removal:
Name Date ID status
john 1/03/17 JHN2 A
sally 1/03/17 SLLY2 A
Mike 1/04/17 MK2 A
If you really want to "delete rows where dateupdated was least updated" then a simple single-row subquery should do the trick.
DELETE MyTable
WHERE Date = (SELECT MIN(Date) From MyTable)
If on the other hand you just want to delete the row with the earliest Date per person (as identified by their ID) you could use:
DELETE MyTable
FROM MyTable a
JOIN (SELECT ID, MIN(Date) MinDate FROM MyTable GROUP BY ID) b
ON a.ID = b.ID AND a.Date = b.MinDate
The idea here is you create an aggregate query that returns rows containing the columns that would match the rows you want deleted, then join to it. Because it's an inner join, rows that do not match the criteria will be excluded.
If people are uniquely identified by something else (e.g. Name then you can just substitute that for the ID in my example above.
I am thinking though that you don't want either of these. I think you want to delete everything except for each person's latest row. If that is the case, try this:
DELETE MyTable
WHERE EXISTS (SELECT 0 FROM MyTable b WHERE b.ID = MyTable.ID AND b.Date > MyTable.Date)
The idea here is you check for existence of another data row with the same ID and a later date. If there is a later record, delete this one.
The nice thing about the last example is you can run it over and over and every person will still be left with exactly one row. The other two queries, if run over and over, will nibble away at the table until it is empty.
P.S. As these are significantly different solutions, I suggest you spend some effort learning how to articulate unambiguous requirements. This is an extremely important skill for any developer.
This deletes rows where the name is a duplicate, and deletes all but the latest row for each name. This is different from your stated question.
Using a common table expression (cte) and row_number():
;with cte as (
select *
, rn = row_number() over (
partition by Name
order by Dateupdated desc
)
from t
)
/* ------------------------------------------------
-- Remove duplicates by deleting rows
-- where the row number (rn) is greater than 1
-- leaving the first row for each partition
------------------------------------------------ */
delete
from cte
where cte.rn > 1
select * from t
rextester: http://rextester.com/HZBQ50469
returns:
+-------+-------------+-------+--------+
| Name | Dateupdated | ID | status |
+-------+-------------+-------+--------+
| john | 2017-01-03 | JHN2 | A |
| sally | 2017-01-03 | SLLY2 | A |
| Mike | 2017-01-04 | MK2 | A |
+-------+-------------+-------+--------+
Without using the cte it can be written as:
delete d
from (
select *
, rn = row_number() over (
partition by Name
order by Dateupdated desc
)
from t
) as d
where d.rn > 1
This should do the trick:
delete
from MyTable a
where not exists (
select top 1 1
from MyTable b
where b.name = a.name
and b.DateUpdated < a.DateUpdated
)
i.e. remove any entries from the table for which there is no record on the same name with a date earlier than the record to be deleted's.
Your Name column has Mike and Mik2 which is different for each other.
So, if you did not make a mistake, standard column to group by must be ID column without last digit.
I think following is more accurate if you did not mistaken.
delete a
from MyTable a
inner join
(select substring(ID, 1, len(ID) - 1) as ID, min(Dateupdated) as MinDate
from MyTable
group by substring(ID, 1, len(ID) - 1)
) b
on substring(a.ID, 1, len(a.ID) - 1) = b.ID and a.Dateupdated = b.MinDate
You can test it at SQLFiddle: http://sqlfiddle.com/#!6/9c440/1

update in oracle sql : multiple rows in 1 table

I am new to SQL and I am no good with more advanced queries and functions.
So, I have this 1 table with sales:
id date seller_name buyer_name
---- ------------ ------------- ------------
1 2015-02-02 null Adrian
1 2013-05-02 null John B
1 2007-11-15 null Chris F
2 2014-07-12 null Jane A
2 2011-06-05 null Ted D
2 2010-08-22 null Maryanne A
3 2015-12-02 null Don P
3 2012-11-07 null Chris T
3 2011-10-02 null James O
I would like to update the seller_name for each id, by putting the buyer_name from previous sale as seller_name to newer sale date. For example, for on id 1 John B would then be seller in 2015-02-02 and buyer in 2013-05-02. Does that make sense?
P.S. This is the perfect case, the table is big and the ids are not ordered so neat.
merge into your_table a
using ( select rowid rid,
lead(buyer_name, 1) over (partition by id order by date desc) seller
from your_table
) b
on (a.rowid = b.rid )
when matched then update set a.seller_name= b.seller;
Explanation : Merge into statement performs different operations based on matched or not matched criterias. Here you have to merge into your table, in the using having the new values that you want to take and also the rowid which will be your matching key. The lead function gets the result from the next n rows depending on what number you specify after the comma. After specifying how many rows to jump you also specify on what part to work, which in your case is partitioned by id and ordered by date so you can get the seller, who was the previous buyer. Hope this clears it up a bit.
Either of the below query can be used to perform the desire action
merge into sandeep24nov16_2 table1
using(select rowid r, lag(buyer_name) over (partition by id order by "DATE" asc) update_value from sandeep24nov16_2 ) table2
on (table1.rowid=table2.r)
when matched then update set table1.seller_name=table2.update_value;
or
merge into sandeep24nov16_2 table1
using(select rowid r, lead(buyer_name) over (partition by id order by "DATE" desc) update_value from sandeep24nov16_2 ) table2
on (table1.rowid=table2.r)
when matched then update set table1.seller_name=table2.update_value;
select a.*,
lag(buyer_name, 1) over(partition by id order by sale_date) seller_name
from <your_table> a;

sql select tuples and group by id

I have the current database schema
EMPLOYEES
ID | NAME | JOB
JOBS
ID | JOBNAME | PRICE
I want to query so that it goes through each employee, and gets all their jobs, but I want each employee ID to be grouped so that it returns the employee ID followed by all the jobs they have. e.g if employee with ID 1 had jobs with ID, JOBNAME (1, Roofing), (1,Brick laying)
I want it to return something like
1 Roofing Bricklaying
I was trying
SELECT ID,JOBNAME FROM JOBS WHERE ID IN (SELECT ID FROM EMPLOYEES) GROUP BY ID;
but get the error
not a GROUP BY expression
Hope this is clear enough, if not please say and I'll try to explain better
EDIT:
WITH ALL_JOBS AS
(
SELECT ID,LISTAGG(JOBNAME || ' ') WITHIN GROUP (ORDER BY ID) JOBNAMES FROM JOBS GROUP BY ID
)
SELECT ID,JOBNAMES FROM ALL_JOBS A,EMPLOYEES B
WHERE A.ID = B.ID
GROUP BY ID,JOBNAMES;
In the with clause, I am grouping by on ID and concatenating the columns corresponding to an ID(also concatenating with ' ' to distinguish the columns).
For example, if we have
ID NAME
1 Roofing
1 Brick laying
2 Michael
2 Schumacher
we will get the result set as
ID NAME
1 Roofing Brick laying
2 Michael Schumacher
Then, I am join this result set with the EMPLOYEES table on ID.
You need to put JobName to group by expression too.
SELECT ID,JOBNAME FROM JOBS WHERE ID IN (SELECT ID FROM EMPLOYEES) GROUP BY ID,JOBNAME;

SQL - fetch latest row with data in specific column or just latest row if no data in that column

I have a table that records a history of address updates, called transaction. The schema is something like row_id (int, PK), user_id (int), address1 (varchar), transdate (timestamp).
I want to query the table and have a single row returned for a user showing what is the latest row (i.e. greatest timestamp), but if there is data in the address1 column I want the latest row with data. If there is no data in this column then just the latest row.
Example:
row_id user_id address1 transdate
1 70005 56 The Street 2010-08-25 09:15
2 70005 NULL 2010-08-25 10:04
3 70005 12 Some Road 2010-08-25 11:17
4 70005 NULL 2010-08-25 12:18
With a query like
SELECT user_id, address1
FROM transaction t
WHERE user_id = 70005
AND row_id =
(SELECT MAX(row_id)
FROM transaction ti
WHERE ti.user_id = t.user_id)
the returned result would be
user_id address1
70005 NULL
but what I want is
user_id address1
70005 12 Some Road
because this is the latest row for that user that has some data.
Hope this makes sense. Does anyone have any suggestions?
I am using MySQL 5.1.49 (community). Thanks.
SELECT user_id, address1
FROM transaction
WHERE user_id = 70005
ORDER BY ISNULL(address1) ASC, row_id DESC
LIMIT 1
This should prioritize rows with data, while still working when address1 is NULL. I would also use transdate instead of row_id.
UPDATED
SELECT user_id, address1
FROM transaction t
WHERE user_id = 70005
AND row_id =
IFNULL(
(SELECT MAX(row_id)
FROM transaction ti
WHERE ti.user_id = t.user_id AND address1 IS NOT NULL),
(SELECT MAX(row_id)
FROM transaction ti
WHERE ti.user_id = t.user_id )
);
Take a look at the accepted answer on this question (not my answer, his was better)
By doing a union of the results with address and then the results where there is no address, you can prioritize and still use a simple subquery join.
Note that you're grabbing the max row_id, not that max date, so you're not going to get the results you expect, even if you do this.
SELECT user_id, address1
FROM transaction t
WHERE user_id = 70005
ORDER BY case when address1 is not null then 0 else 1 end, transdate desc
limit 1

SQL select value if no corresponding value exists in another table

I have a database which tries to acheive point-in-time information by having a master table and a history table which records when fields in the other table will/did change. e.g.
Table: Employee
Id | Name | Department
-----------------------------
0 | Alice | 1
1 | Bob | 1
Table: History
ChangeDate | Field | RowId | NewValue
---------------------------------------------
05/05/2009 | Department | 0 | 2
That records that employee 0 (Alice) will move to department 2 on 05/05/2009.
I want to write a query to determine the employee's department on a particular date. So it needs to:
Find the first history record for that field and employee before given date
If none exists then default to the value currently in the master employee table.
How can I do this? My intuition is to select the first row of a result set which has all suitable history records reverse ordered by date and with the value in the master table last (so it's only the first result if there are no suitable history records), but I don't have the required SQL-fu to achieve this.
Note: I am conscious that this may not be the best way to implement this system - I am not able to change this in the short term - though if you can suggest a better way to implement this I'd be glad to hear it.
SELECT COALESCE (
(
SELECT newValue
FROM history
WHERE field = 'Department'
AND rowID = ID
AND changeDate =
(
SELECT MAX(changedate)
FROM history
WHERE field = 'Department'
AND rowID = ID
AND changeDate <= '01/01/2009'
)
), department)
FROM employee
WHERE id = #id
In both Oracle and MS SQL, you can also use this:
SELECT COALESCE(newValue, department)
FROM (
SELECT e.*, h.*,
ROW_NUMBER() OVER (PARTITION BY e.id ORDER BY changeDate) AS rn
FROM employee e
LEFT OUTER JOIN
history h
ON field = 'Department'
AND rowID = ID
AND changeDate <= '01/01/2009'
WHERE e.id = #id
)
WHERE rn = 1
Note, though, that ROWID is reserved word in Oracle, so you'll need to rename this column when porting.
This should work:
select iif(history.newvalue is null, employee.department, history.newvalue)
as Department
from employee left outer join history on history.RowId = employee.Id
and history.changedate < '2008-05-20' // (i.e. given date)
and history.changedate = (select max(changedate) from history h1
where h1.RowId = history.RowId and h1.changedate <= history.changedate)