Limit by percent instead of number of rows without subqueries - postgresql

I would like to select the top 1% of rows; however, I cannot use subqueries to do it. I.e., this won't work:
SELECT * FROM mytbl
WHERE var='value'
ORDER BY id,random()
LIMIT(SELECT (COUNT(*) * 0.01)::integer FROM mytbl)
How would I accomplish the same output without using a subquery with limit?

You can utilize PERCENT_RANK:
WITH cte(ID, var, pc) AS
(
SELECT ID, var, PERCENT_RANK() OVER (ORDER BY random()) AS pc
FROM mytbl
WHERE var = 'value'
)
SELECT *
FROM cte
WHERE pc <= 0.01
ORDER BY id;
SqlFiddleDemo

I solved it with Python using the psycopg2 package:
cur.execute("SELECT ROUND(COUNT(id)*0.01,0)
FROM mytbl")
nrows = str([int(d[0]) for d in cur.fetchall()][0])
cur.execute("SELECT *
FROM mytbl
WHERE var='value'
ORDER BY id, random() LIMIT (%s)",nrows)
Perhaps there is a more elegant solution using just SQL, or a more efficient one, but this does exactly what I'm looking for.

If I got it right, you need:
Random 1% sample of all rows,
If some id is within the sample, all rows with the same id must be there too.
The follow sql should do the trick:
with ids as (
select id,
total,
sum(cnt) over (order by max(rnd)) running_total
from (
select id,
count(*) over (partition by id) cnt,
count(*) over () total,
row_number() over(order by random()) rnd
from mytbl
) q
group by id,
cnt,
total
)
select mytbl.*
from mytbl,
ids
where mytbl.id = ids.id
and ids.running_total <= ids.total * 0.01
order by mytbl.id;

I don’t have your data, of course, but I have no trouble using a sub query in the LIMIT clause.
However, the sub query contains only the count(*) part and I then multiply the result by 0.01:
SELECT * FROM mytbl
WHERE var='value'
ORDER BY id,random()
LIMIT(SELECT count(*) FROM mytbl)*0.01;

Related

simpler query for counting total row in the column

not sure if my below query script right to execute as I am trying to find just one query in Oracle SQL query
select distinct (master_id) , last_name from (
select q2.* , max (count_a) over (partition by master_id)
count_b from ( select q1.* , count (*) over (partition by
master_id order by purchased_date desc ) count_a from profile q1)
q2) where count_b > 2
I am trying to minimise timer to execute get data by reducing sub query
for example above it has two subqueries
max (count_a) over (partition by master_id) count_b
count (*) over (partition by master_id order by purchased_date desc ) count_a
so I played around until this query
max (count (*)) over (partition by master_id) count
SQL query script;
select * from profile a
join( select * from (
select master_id, max (count(*)) over (partition by
master_id) count from profile) where count >2) b
ON a. master_id = b. master_id
Thank you in advance for your help

postgres how to properly use max function

Help please with correct usage of max, I have the following:
select busqueda.valorBusqueda, count(*) from busqueda where usu_id = 24 group by busqueda.valorBusqueda;
and it works, but I want only the max count of it, so far I tried:
select max (busqueda.valorBusqueda, count(*) from busqueda where usu_id = 24 group by busqueda.valorBusqueda);
but no success..
The easiest solution here is probably to use a LIMIT query:
select valorBusqueda, count(*) as cnt
from busqueda
where usu_id = 24
group by valorBusqueda
order by count(*) desc
limit 1;
Postgres does not support ties with LIMIT, but we can use the RANK analytic function here if you do want all ties for the highest count:
with cte as (
select valorBusqueda, count(*) as cnt, rank() over (order by count(*) desc) rnk
from busqueda
where usu_id = 24
group by valorBusqueda
)
select valorBusqueda, cnt
from cnt
where rnk = 1;
you can use subquery.
select max(t.ct) from (
select busqueda.valorBusqueda, count(*) ct from busqueda
where usu_id = 24 group by busqueda.valorBusqueda) t;

Lead() considering where clause, don't want it to

I have a simple log table named TaskLog. I want to see how long a certain step is taking by looking at the time difference between that step and the one after.
The query below has the effect of looking at the time between rows that read 'performing media relations rollup' - I don't want the LEAD() rows to follow the WHERE clause. (they should take the next row in cteTask regardless of the value of Notes.
;with cteTask as
(select * from Tasklog where prog = 'StatsMajor' and Date>'8/1/2014')
, cteLead as
(select *
, LEAD(ID) over (order by ID) NextID
, LEAD(date) over (order by ID) NextDt
from cteTask
where notes = 'performing media relations rollup'
)
select *, DATEDIFF(second, Date, NextDt) as Secs
from cteLead
Just filter after?
;with cteTask as
(select * from Tasklog where prog = 'StatsMajor' and Date>'8/1/2014')
, cteLead as
(select *
, LEAD(ID) over (order by ID) NextID
, LEAD(date) over (order by ID) NextDt
from cteTask
)
select *, DATEDIFF(second, Date, NextDt) as Secs
from cteLead
where notes = 'performing media relations rollup'

T-SQL if value exists use it other wise use the value before

I have the following table
-----Account#----Period-----Balance
12345---------200901-----$11554
12345---------200902-----$4353
12345 --------201004-----$34
12345 --------201005-----$44
12345---------201006-----$1454
45677---------200901-----$14454
45677---------200902-----$1478
45677 --------201004-----$116776
45677 --------201005-----$996
56789---------201006-----$1567
56789---------200901-----$7894
56789---------200902-----$123
56789 --------201003-----$543345
56789 --------201005-----$114
56789---------201006-----$54
I want to select the account# that have a period of 201005.
This is fairly easy using the code below. The problem is that if a user enters 201003-which doesnt exist- I want the query to select the previous value.*NOTE that there is an account# that has a 201003 period and I still want to select it too.*
I tried CASE, IF ELSE, IN but I was unsuccessfull.
PS:I cannot create temp tables due to system limitations of 5000 rows.
Thank you.
DECLARE #INPUTPERIOD INT
#INPUTPERIOD ='201005'
SELECT ACCOUNT#, PERIOD , BALANCE
FROM TABLE1
WHERE PERIOD =#INPUTPERIOD
SELECT t.ACCOUNT#, t.PERIOD, t.BALANCE
FROM (SELECT ACCOUNT#, MAX(PERIOD) AS MaxPeriod
FROM TABLE1
WHERE PERIOD <= #INPUTPERIOD
GROUP BY ACCOUNT#) q
INNER JOIN TABLE1 t
ON q.ACCOUNT# = t.ACCOUNT#
AND q.MaxPeriod = t.PERIOD
select top 1 account#, period, balance
from table1
where period >= #inputperiod
; WITH Base AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Period DESC) RN FROM #MyTable WHERE Period <= 201003
)
SELECT * FROM Base WHERE RN = 1
Using CTE and ROW_NUMBER() (we take all the rows with Period <= the selected date and we take the top one (the one with auto-generated ROW_NUMBER() = 1)
; WITH Base AS
(
SELECT *, 1 AS RN FROM #MyTable WHERE Period = 201003
)
, Alternative AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY Period DESC) RN FROM #MyTable WHERE NOT EXISTS(SELECT 1 FROM Base) AND Period < 201003
)
, Final AS
(
SELECT * FROM Base
UNION ALL
SELECT * FROM Alternative WHERE RN = 1
)
SELECT * FROM Final
This one is a lot more complex but does nearly the same thing. It is more "imperative like". It first tries to find a row with the exact Period, and if it doesn't exists does the same thing as before. At the end it unite the two result sets (one of the two is always empty). I would always use the first one, unless profiling showed me the SQL wasn't able to comprehend what I'm trying to do. Then I would try the second one.

Perl prepare DB2 statement not returning what I need

Since I am using DB2, in order to select a portion of a database in the middle (like a limit/offset pairing), I need to do a different kind of prepare statement. The example I was given was this:
SELECT *
FROM (SELECT col1, col2, col3, ROW_NUMBER() OVER () AS RN FROM table) AS cols
WHERE RN BETWEEN 1 AND 10000;
Which I adapted to this:
SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY 2,3,4,6,7 ASC) AS rownum FROM TRANSACTIONS) AS foo WHERE rownum >= 500 AND rownum <1000
And when I call the fetchall_arrayref(), I do come out with 500 results like I want to, but it is only returning an array with references to the row number, and not all of the data I want to pull. I know for a fact that that is what the code is SUPPOSED to do as its written, and I have tried a bunch of permutations to get my desired result with no luck.
All I want is to grab all of the columns like my previous prepare statement into an array of arrays:
SELECT * FROM TU_TRANSACTIONS ORDER BY 2, 3, 4, 6, 7
but just on a designated section. There is just a fundamental thing I am missing, and I just cant see it.
Any help is appreciated, even if its paired with some constructive criticism.
Your table expression:
(SELECT ROW_NUMBER() OVER (ORDER BY 2,3,4,6,7 ASC) AS rownum FROM TRANSACTIONS) as foo
Has only one column - rownum - so when you select "*" from "foo" you get only the one column.
Your table expression needs to include all of the columns you want, just like e example you posted.
I don't use DB2 so I could be off-base but it seems that:
SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY 2,3,4,6,7 ASC) AS rownum FROM TRANSACTIONS) AS foo WHERE rownum >= 500 AND rownum <1000
Would only return the row numbers because while the sub-query references the table the main query does not. All it seems it would see is the set of numbers (which would return a single column with the number filled in)
Perhaps this would work:
SELECT * FROM TRANSACTIONS, (SELECT ROW_NUMBER() OVER (ORDER BY 2,3,4,6,7 ASC) AS rownum FROM TRANSACTIONS) AS foo WHERE rownum >= 500 AND rownum <1000