DELETE query with Common Table Expression ("WITH AS " clause) - sql

I am trying to delete 41,6% of my old rows from my oracle data table ( senstrig is a date format):
DELETE FROM
(WITH RS AS (SELECT * FROM OLD_WIFISIGN WHERE SENSID= 1 ORDER BY SENSTRIG ASC))
WHERE ROWNUM <= (SELECT COUNT (*)/ 2.4 FROM RS);
But it returns an error:
ORA-00928 missing SELECT
I have already tried several versions but with no luck.
Could you please help me how can I make this "delete from" runnable?

Test table, 1000 rows, all with sensid = 1:
create table old_wifisign (sensid, senstrig) as (
select 1, trunc(sysdate) - level
from dual connect by level <= 1000);
Delete:
delete
from old_wifisign
where rowid in (
select rowid
from (
select rowid, row_number() over (order by senstrig) / count(1) over () rto
from old_wifisign
where sensid = 1 )
where rto <= .416)
Result: 416 rows with oldest senstrig deleted. Note that only sensid 1 is taken into calculations here, as in your query.

To delete 41,6% of old rows check fist the boundary date to delete and than use it.
The analytic function PERCENT_RANK gives you the youngest senstrig you do not want to delete.
with perc_rank as (
select SENSID, SENSTRIG,
PERCENT_RANK() OVER (ORDER BY senstrig) AS pr
from old_wifisign)
select max(SENSTRIG) from perc_rank
where pr < .416
Than you simple takes this date and performs a DELETE
delete from old_wifisign
where SENSTRIG <
(with perc_rank as (
select SENSID, SENSTRIG,
PERCENT_RANK() OVER (ORDER BY senstrig) AS pr
from old_wifisign)
select max(SENSTRIG) from perc_rank
where pr < .416
);
Anyway you should take soem care considering ties - thing about a case when half of the rows in the table have identical senstrig

Put WITH before DELETE:
WITH RS AS (SELECT * FROM OLD_WIFISIGN WHERE SENSID= 1 ORDER BY SENSTRIG ASC)
DELETE FROM OLD_WIFISIGN
WHERE ROWNUM <= (SELECT COUNT (*)/ 2.4 FROM RS);
Still a bit weird, what is the purpose of the order by?

Related

How to skip/offset rows in Oracle database?

I am writing a very simple query for an Oracle DB (version 9).
Somehow I can get first 5 rows:
select * from cities where rownum <= 5
But skipping 5 rows returns an empty result:
select * from cities where rownum >= 5
Using:
Oracle SQL Developer
Oracle DB version 9
Why is the second query returning an empty result?
In Oracle Database 12c (release 1) and above, you can do this very simple, for skip 5 rows:
SELECT * FROM T OFFSET 5 ROWS
and for skip 5 rows and take 15 rows:
SELECT * FROM T OFFSET 5 ROWS FETCH NEXT 15 ROWS ONLY
You can use the following query to skip the first not n of rows.
select * from (
select rslts.*, rownum as rec_no from (
<<Query with proper order by (If you don't have proper order by you will see weird results)>>
) rslts
) where rec_no > <<startRowNum - n>>
The above query is similar to pagination query below.
select * from (
select rslts.*, rownum as rec_no from (
<<Query with proper order by (If you don't have proper order by you will see weird results)>>
) rslts where rownum <= <<endRowNum>>
) where rec_no > <<startRowNum>>
Your cities query:
select * from (
select rslts.*, rownum as rec_no from (
select * from cities order by 1
) rslts
) where rec_no > 5 <<startRowNum>>
Note: Assume first column in cities table is unique key
Oracle increments rownum each time it adds a row to the result set. So saying rownum < 5 is fine; as it adds each of the first 5 rows it increments rownum, but then once ruwnum = 5 the WHERE clause stops matching, no more rows are added to the result, and though you don't notice this rownum stops incrementing.
But if you say WHERE rownum > 5 then right off the bat, the WHERE clause doesn't match; and since, say, the first row isn't added to the result set, rownum isn't incremented... so rownum can never reach a value greater than 5 and the WHERE clause can never match.
To get the result you want, you can use row_number() over() in a subquery, like
select *
from (select row_number() over() rn, -- other values
from table
where -- ...)
where rn > 5
Update - As noted by others, this kind of query only makes sense if you can
control the order of the row numbering, so you should really use row_number() over(order bysomething) where something is a useful ordering key in deciding which records are "the first 5 records".
rownum is being increased only when a row is being output, so this type of condition won't work.
In any case, you are not ordering your rows, so what's the point?
Used row_number() over (order by id):
select * from
(select row_number() over (order by id) rn, c.* from countries c)
where rn > 5
Used ROWNUM:
select * from
(select rownum rn, c.* from countries c)
where rn > 5
Important note:
Using alias as countries c instead of countries is required! Without, it gives an error "missing expression"
Even better would be:
select * from mytab sample(5) fetch next 1 rows only;
Sample clause indicates the probability of each row getting picked up in the sampling process. FETCH NEXT clause indicates the number of rows you want to select.
With this code, you can query your table with skip and take.
select * from (
select a.*, rownum rnum from (
select * from cities
) a
) WHERE rnum >= :skip + 1 AND rnum <= :skip + :take
This code works with Oracle 11g. With Oracle 12, there is already a better way to perform this queries with offset and fetch

How to find if there are more records in a table using oracle sql

I am using a sql to select data from a table for a particular range of records.
I am using rownum to implement greater and less than logic to arrive at the required set of data.
With the use of below sql I can fetch the records from 21 to 40 from my table. In total for this condition table contains 100 rows.
Through this sql I want to fetch an indicator(value) which tells that there are more records for this condition.
I could not find any solution in google.
Sql -
select * from ( select rownum rnum, a.* from(SELECT TO_CHAR(D.DATE,'YYYYMMDD'),D.TYPE,
TO_CHAR(D.VDATE,'YYYYMMDD'),D.AMT,D.PARTICULAR,D.NUM,D.ID,
D.CODE,D.INFO FROM MySCHEMA
.MYTABLE D WHERE D.DATE >= TO_CHAR(TO_DATE('20160701','YYYYMMDD'),'DD-MON-RRRR')
AND D.DATE <= TO_CHAR(TO_DATE('20161105','YYYYMMDD'),'DD-MON-RRRR') AND D.XDATE >= TO_CHAR(TO_DATE('20160701','YYYYMMDD'),'DD-MON-RRRR')
AND D.XDATE <= TO_CHAR(TO_DATE('20161105','YYYYMMDD'),'DD-MON-RRRR')
AND D.FLG='Y' AND D.TYPE IN('D','C')
AND
D.ACI = 'CO6'
ORDER BY D.DATE DESC
)
a where rownum <= 40 ) where rnum >= 21;
You can add a count(*) over () total_rows to your inner select. That will tell you how many rows the inner query would return without the rownum predicates. That is going to mean, however, that every time you ask for a page of results, Oracle has to execute the inner query completely and then discard all the rows you aren't fetching. That's going to be more expensive than what you are currently doing
select *
from ( select rownum rnum, a.*
from(SELECT count(1) over () total_rows,
<<the rest of your query>>

How to use a fast way for oracle sql to find the last log column in Jobs run log

The run log have too much data that i can't search it in fast ways.Now i only want to get the last log data in this log table in oracle jobs. Pls give me help.Best!
There are various options to return a sinlge row but these will do it with only a single table scan:
SELECT *
FROM ( SELECT *
FROM log_table
ORDER BY entry_date DESC
)
WHERE ROWNUM = 1;
or
SELECT *
FROM ( SELECT l.*,
ROW_NUMBER() OVER ( ORDER BY entry_date DESC ) rn
FROM log_table l
)
WHERE rn = 1;
or (for Oracle 12)
SELECT *
FROM log_table
ORDER BY entry_date DESC
FETCH FIRST 1 ROW ONLY;
Better to use max(date) like this
select * from log where date=(select max(date) from log)
It'll use indexes (if they exists)
If you have no date field you can use, and this can be even better:
select * from log where rowid=(select max(rowid) from log)

delete records from the table sorted by two columns

I have a table with these columns:
tel_number,date,time
There might be several records for a single tel_number. For each tel_number, I want to delete all except the record which has the most recent date from the table and in cases where there are multiple records for the most recent date, the one with the most recent time get to be selected and all the rest should be removed from the table.
for example from records like these:
1 2223333,14/01/28,08:30
2 2223333,14/01/27,08:30
3 2223333,14/01/28,16:30
4 2225555,14/01/27,10:34
5 2225555,13/12/29,10:34
all record except these two should be deleted:
3 2223333,14/01/28,16:30
4 2225555,14/01/27,10:34
edit:
I have tried this so far, but it doesn't delete records which has the same date but different times:
delete from table where (tel_number,date) not in
(select tel_number,max(date) from table group by tel_number);
According to Deleting Duplicate Rows In Oracle:
1. Using MIN(rowid) : The most common method of removing duplicate rows.
DELETE FROM Table1
WHERE ROWID NOT IN (SELECT MIN (ROWID)
FROM Table1
GROUP BY tel_number, Date, Time);
2. Using MIN(rowid) & Join: More or less the same as first one
DELETE FROM Table1 t
WHERE t.ROWID NOT IN (SELECT MIN (b.ROWID)
FROM Table1 b
WHERE b.tel_number = t.tel_number
AND b.Date = t.Date
AND b.Time = t.Time);
3. Using Analytic functions
DELETE FROM Table1
WHERE ROWID IN (
SELECT rid
FROM (SELECT ROWID rid,
ROW_NUMBER () OVER (PARTITION BY tel_number,Date, Time ORDER BY ROWID) rn
FROM Table1 )
WHERE rn <> 1);
I solved the problem with this query:
delete from table a where (a.tel_number,a.DATE,a.TIME) not in
(
select tel_number,date,max(time)
from table group by tel_number,date
having date in
(
select max(date) from table group by tel_number
)
);

How to implement LIMIT with SQL Server? [duplicate]

This question already has answers here:
Implement paging (skip / take) functionality with this query
(6 answers)
Closed 1 year ago.
I have this query with MySQL:
select * from table1 LIMIT 10,20
How can I do this with SQL Server?
Starting SQL SERVER 2005, you can do this...
USE AdventureWorks;
GO
WITH OrderedOrders AS
(
SELECT SalesOrderID, OrderDate,
ROW_NUMBER() OVER (ORDER BY OrderDate) AS 'RowNumber'
FROM Sales.SalesOrderHeader
)
SELECT *
FROM OrderedOrders
WHERE RowNumber BETWEEN 10 AND 20;
or something like this for 2000 and below versions...
SELECT TOP 10 * FROM (SELECT TOP 20 FROM Table ORDER BY Id) ORDER BY Id DESC
Starting with SQL SERVER 2012, you can use the OFFSET FETCH Clause:
USE AdventureWorks;
GO
SELECT SalesOrderID, OrderDate
FROM Sales.SalesOrderHeader
ORDER BY SalesOrderID
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;
GO
http://msdn.microsoft.com/en-us/library/ms188385(v=sql.110).aspx
This may not work correctly when the order by is not unique.
If the query is modified to ORDER BY OrderDate, the result set returned is not as expected.
This is how I limit the results in MS SQL Server 2012:
SELECT *
FROM table1
ORDER BY columnName
OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY
NOTE: OFFSET can only be used with or in tandem to ORDER BY.
To explain the code line OFFSET xx ROWS FETCH NEXT yy ROW ONLY
The xx is the record/row number you want to start pulling from in the table, i.e: If there are 40 records in table 1, the code above will start pulling from row 10.
The yy is the number of records/rows you want to pull from the table.
To build on the previous example: If table 1 has 40 records and you began pulling from row 10 and grab the NEXT set of 10 (yy).
That would mean, the code above will pull the records from table 1 starting at row 10 and ending at 20. Thus pulling rows 10 - 20.
Check out the link for more info on OFFSET
This is almost a duplicate of a question I asked in October:
Emulate MySQL LIMIT clause in Microsoft SQL Server 2000
If you're using Microsoft SQL Server 2000, there is no good solution. Most people have to resort to capturing the result of the query in a temporary table with a IDENTITY primary key. Then query against the primary key column using a BETWEEN condition.
If you're using Microsoft SQL Server 2005 or later, you have a ROW_NUMBER() function, so you can get the same result but avoid the temporary table.
SELECT t1.*
FROM (
SELECT ROW_NUMBER OVER(ORDER BY id) AS row, t1.*
FROM ( ...original SQL query... ) t1
) t2
WHERE t2.row BETWEEN #offset+1 AND #offset+#count;
You can also write this as a common table expression as shown in #Leon Tayson's answer.
SELECT *
FROM (
SELECT TOP 20
t.*, ROW_NUMBER() OVER (ORDER BY field1) AS rn
FROM table1 t
ORDER BY
field1
) t
WHERE rn > 10
Syntactically MySQL LIMIT query is something like this:
SELECT * FROM table LIMIT OFFSET, ROW_COUNT
This can be translated into Microsoft SQL Server like
SELECT * FROM
(
SELECT TOP #{OFFSET+ROW_COUNT} *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum
FROM table
) a
WHERE rnum > OFFSET
Now your query select * from table1 LIMIT 10,20 will be like this:
SELECT * FROM
(
SELECT TOP 30 *, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS rnum
FROM table1
) a
WHERE rnum > 10
SELECT TOP 10 * FROM table;
Is the same as
SELECT * FROM table LIMIT 0,10;
Here's an article about implementing Limit in MsSQL Its a nice read, specially the comments.
This is one of the reasons I try to avoid using MS Server... but anyway. Sometimes you just don't have an option (yei! and I have to use an outdated version!!).
My suggestion is to create a virtual table:
From:
SELECT * FROM table
To:
CREATE VIEW v_table AS
SELECT ROW_NUMBER() OVER (ORDER BY table_key) AS row,* FROM table
Then just query:
SELECT * FROM v_table WHERE row BETWEEN 10 AND 20
If fields are added, or removed, "row" is updated automatically.
The main problem with this option is that ORDER BY is fixed. So if you want a different order, you would have to create another view.
UPDATE
There is another problem with this approach: if you try to filter your data, it won't work as expected. For example, if you do:
SELECT * FROM v_table WHERE field = 'test' AND row BETWEEN 10 AND 20
WHERE becomes limited to those data which are in the rows between 10 and 20 (instead of searching the whole dataset and limiting the output).
In SQL there's no LIMIT keyword exists. If you only need a limited number of rows you should use a TOP keyword which is similar to a LIMIT.
Must try. In below query, you can see group by, order by, Skip rows, and limit rows.
select emp_no , sum(salary_amount) from emp_salary
Group by emp_no
ORDER BY emp_no
OFFSET 5 ROWS -- Skip first 5
FETCH NEXT 10 ROWS ONLY; -- limit to retrieve next 10 row after skiping rows
Easy way
MYSQL:
SELECT 'filds' FROM 'table' WHERE 'where' LIMIT 'offset','per_page'
MSSQL:
SELECT 'filds' FROM 'table' WHERE 'where' ORDER BY 'any' OFFSET 'offset'
ROWS FETCH NEXT 'per_page' ROWS ONLY
ORDER BY is mandatory
This is a multi step approach that will work in SQL2000.
-- Create a temp table to hold the data
CREATE TABLE #foo(rowID int identity(1, 1), myOtherColumns)
INSERT INTO #foo (myColumns) SELECT myData order By MyCriteria
Select * FROM #foo where rowID > 10
SELECT
*
FROM
(
SELECT
top 20 -- ($a) number of records to show
*
FROM
(
SELECT
top 29 -- ($b) last record position
*
FROM
table -- replace this for table name (i.e. "Customer")
ORDER BY
2 ASC
) AS tbl1
ORDER BY
2 DESC
) AS tbl2
ORDER BY
2 ASC;
-- Examples:
-- Show 5 records from position 5:
-- $a = 5;
-- $b = (5 + 5) - 1
-- $b = 9;
-- Show 10 records from position 4:
-- $a = 10;
-- $b = (10 + 4) - 1
-- $b = 13;
-- To calculate $b:
-- $b = ($a + position) - 1
-- For the present exercise we need to:
-- Show 20 records from position 10:
-- $a = 20;
-- $b = (20 + 10) - 1
-- $b = 29;
If your ID is unique identifier type or your id in table is not sorted you must do like this below.
select * from
(select ROW_NUMBER() OVER (ORDER BY (select 0)) AS RowNumber,* from table1) a
where a.RowNumber between 2 and 5
The code will be
select * from limit 2,5
better use this in MSSQLExpress 2017.
SELECT * FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) as [Count], * FROM table1
) as a
WHERE [Count] BETWEEN 10 and 20;
--Giving a Column [Count] and assigning every row a unique counting without ordering something then re select again where you can provide your limits.. :)
One of the possible way to get result as below , hope this will help.
declare #start int
declare #end int
SET #start = '5000'; -- 0 , 5000 ,
SET #end = '10000'; -- 5001, 10001
SELECT * FROM (
SELECT TABLE_NAME,TABLE_TYPE, ROW_NUMBER() OVER (ORDER BY TABLE_NAME) as row FROM information_schema.tables
) a WHERE a.row > #start and a.row <= #end
If i remember correctly (it's been a while since i dabbed with SQL Server) you may be able to use something like this: (2005 and up)
SELECT
*
,ROW_NUMBER() OVER(ORDER BY SomeFields) AS [RowNum]
FROM SomeTable
WHERE RowNum BETWEEN 10 AND 20