How can I collectively select 100 rows from 3 different tables? - sql

I have 3 tables containing similar rows of data.
I need to select 100 rows from all of the three tables with the following conditions:
No more than 25 rows can be selected from Table A --> (name it count_a)
No more than 40 rows can be selected from Table B --> (count_b)
Any number of rows can be selected from Table C (count_c) but the number should be
count_c = 100 - (count_a + count_b)
Here is what I tried:
SELECT * FROM
(
SELECT * FROM TABLE_A WHERE ROWNUM <= 25
UNION ALL
SELECT * FROM TABLE_B WHERE ROWNUM <= 40
UNION ALL
SELECT * FROM TABLE_C
) WHERE ROWNUM <=100
But the query is too slow and does not always give me 100 rows.

Try to add WHERE ROWNUM <= 100 to the last select:
SELECT * FROM
(
SELECT TABLE_A.*, 1 as OrdRow FROM TABLE_A WHERE ROWNUM <= 25
UNION ALL
SELECT TABLE_B.*, 2 as OrdRow FROM TABLE_B WHERE ROWNUM <= 40
UNION ALL
SELECT TABLE_C.*, 3 as OrdRow FROM TABLE_C WHERE ROWNUM <= 100
) WHERE ROWNUM <=100
ORDER BY OrdRow;
Also you can try:
SELECT * FROM TABLE_A WHERE ROWNUM <= 25
UNION ALL
SELECT * FROM TABLE_B WHERE ROWNUM <= 40
UNION ALL
SELECT * FROM TABLE_C WHERE ROWNUM <=
100
-
(select count(*) TABLE_A WHERE ROWNUM <= 25)
-
(select count(*) TABLE_B WHERE ROWNUM <= 40)

Try like this,
SELECT * FROM
(
SELECT * FROM table_a where rownum <=25
UNION ALL
SELECT * FROM table_b WHERE ROWNUM <= 40
UNION ALL
SELECT * FROM table_c WHERE ROWNUM <= 100 - ((SELECT count(*) FROM table_a WHERE ROWNUM <= 25) + (SELECT count(*) FROM table_b WHERE ROWNUM <= 40))
);

Technically, you'd have to do something like this in order to guarantee that you'll always get rows from TABLE_A and TABLE_B if they exist:
SELECT * FROM (
SELECT * FROM (
SELECT 'A' t, TABLE_A.* FROM TABLE_A WHERE ROWNUM <= 25
UNION ALL
SELECT 'B' t, TABLE_B.* FROM TABLE_B WHERE ROWNUM <= 40
UNION ALL
SELECT 'C' t, TABLE_C.* FROM TABLE_C
) ORDER BY t
) WHERE ROWNUM <= 100;
This is because the optimizer is allowed to run the subqueries in any order it likes - e.g. in parallel.
With regard to performance, I suspect that the sort op will not add too much time to the execution time because it's only sorting a maximum of 100 rows anyway.

Yes ... Execution speed is a nightmare to every developer or programmer ...
In that case you can try like ... i think it will speed up your query more
DECLARE #Table_A_Row_Count INT, #Table_B_Row_Count INT,#RemainCount INT=0
--Getting count of primary tables
SELECT #Table_A_Row_Count= COUNT(1) FROM TABLE_A
SELECT #Table_B_Row_Count= COUNT(1) FROM TABLE_B
--Calculating remaining colections
IF #Table_A_Row_Count+#Table_B_Row_Count<100
BEGIN
SET #RemainCount=100-(#Table_A_Row_Count+#Table_B_Row_Count)
END
ELSE
BEGIN
--You might do somthing like this if First
--and second table covering 100 rows
SET #Table_B_Row_Count=100-#Table_A_Row_Count
END
--Finaly getting 100 rows
SELECT top #Table_A_Row_Count * FROM TABLE_A
UNION ALL
SELECT top #Table_B_Row_Count * FROM TABLE_B
UNION ALL
SELECT Top #RemainCount * FROM TABLE_C

Related

Merge three tables in Select query by rule 3, 2, 1 records from each table

Merge three tables in a Select query by rule 3, 2, 1 records from each table as follows:
TableA: ID, FieldA, FieldB, FieldC,....
TableB: ID, FieldA, FieldB, FieldC,....
TableC: ID, FieldA, FieldB, FieldC,....
ID : auto number in each table
FieldA will be unique in all three tables.
I am looking for a Select query to merge three tables as follows:
TOP three records from TableA sorted by ID
TOP two records from TableB sorted by ID
TOP 1 record from TableC sorted by ID
Repeat this until select all records from all three tables.
If some table has fewer records or does not meet the criteria, ignore that and continue with others.
My attempt:
I did it totally through programming way, like cursors and If conditions inside a SQL Server stored procedure.
It makes delay.
This requires a formula that takes row numbers from each table and transforms it into a series of integers that skips the desired values.
In the query below, I am adding some CTE for the sake of shortening the formula. The real magic is in the UNION. Also, I am adding an additional field for your control. Feel free to get rid of it.
WITH A_Aux as (
SELECT 'A' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableA.*
FROM TableA
), B_Aux AS (
SELECT 'B' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableB.*
FROM TableB
), C_Aux AS (
SELECT 'C' As FromTable, ROW_NUMBER() OVER (Order BY ID) AS RowNum, TableC.*
FROM TableC
)
SELECT *
FROM (
SELECT RowNum+3*FLOOR((RowNum-1)/3) As ColumnForOrder, A_Aux.* FROM A_Aux
UNION ALL
SELECT 3+RowNum+4*FLOOR((RowNum-1)/2), B_Aux.* FROM B_Aux
UNION ALL
SELECT 6*RowNum, C_Aux.* FROM C_Aux
) T
ORDER BY ColumnForOrder
PS: note the pattern Offset + RowNum + (6-N) * Floor((RowNum-1)/N) to group N records together (it of course simplifies a lot for TableC).
PPS: I don't have a SQL server at hand to test it. Let me know if there is a syntax error.
You may try this..
GO
select * into #temp1 from (select * from table1) as t1
select * into #temp2 from (select * from table2) as t2
select * into #temp3 from (select * from table3) as t3
select * into #final from (select col1, col2, col3 from #temp1 where 1=0) as tb
declare #i int
set #i=1
while( (select COUNT(*) from #temp1)>#i)
Begin
;with ct1 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp1
),ct2 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp2
),ct3 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp3
),cfinal as (
select top 3 * from #temp1
union all
select top 2 * from #temp2
union all
select top 1 * from #temp3
)
insert into #final ( col1 , col2, col3 )
select col1, col2, col3 from cfinal
delete from #temp1 where id in (select top 3 ID from #temp1)
delete from #temp2 where id in (select top 2 ID from #temp2)
delete from #temp3 where id in (select top 1 ID from #temp3)
set #i = #i+1
End
Select * from #final
Drop table #temp1
Drop table #temp2
Drop table #temp3
GO
First create temp table for all 3 tables with each insert delete the inserted record and this will result you the desired result, if nothing is missing from my side.
Please see to this if this works.
There is not a lot of information to go with here, but I assume you can use UNION to combine multiple statements.
SELECT * TableA ORDER BY ID DESC OFFSET 3 ROWS
UNION
SELECT * TableB ORDER BY ID DESC OFFSET 2 ROWS
UNION
SELECT * TableC ORDER BY ID DESC OFFSET 1 ROWS
Execute and see if this works.
/AF
From my understanding, I create three temp tables as ta, tb, tc.
select * into #ta from (
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
) a
select * into #tb from (
select 'B' b
union all
select 'B'
union all
select 'B'
union all
select 'B'
union all
select 'B'
) b
select * into #tc from (
select 'C' c
union all
select 'C'
union all
select 'C'
union all
select 'C'
union all
select 'C'
) c
If tables match you tables, then the output looks like A,A,A,B,B,C,A,A,A,B,B,C,A,B,C,C,C
T-SQL
declare #TAC int = (select count (*) from #ta) -- Table A Count = 7
declare #TBC int = (select count (*) from #tb) -- Table B Count = 5
declare #TAR int = #TAC % 3 -- Table A Reminder = 1
declare #TBR int = #TBC % 2 -- Table B Reminder = 1
declare #TAQ int = (#TAC - #TAR) / 3 -- Table A Quotient = (7 - 1) / 3 = 2, is will passed on NTILE
-- So we gonna split as two group (111), (222)
declare #TBQ int = (#TBC - #TBR) / 2 -- Table B Quotient = (5 - 1) / 2 = 2, is will passed on NTILE
-- So we gonna split as two group (11), (22)
select * from (
select *, NTILE (#TAQ) over ( order by a) FirstOrder, 1 SecondOrder from (
select top (#TAC - #TAR) * from #ta order by a
) ta -- 6 rows are obtained out of 7.
union all
select *, #TAQ + 1, 1 from (
select top (#TAR) * from #ta order by a desc
) ta -- Remaining one row is obtained. Order by desc is must
-- Here FirstOrder is next value of previous value.
union all
select *, NTILE (#TBQ) over ( order by b), 2 from (
select top (#TBC - #TBR) * from #tb order by b
) tb
union all
select *, #TBQ + 1, 2 from (
select top (#TBR) * from #tb order by b desc
) tb
union all
select *, ROW_NUMBER () over (order by c), 3 from #tc
) abc order by FirstOrder, SecondOrder
Let me explain the T-SQL:
Before that, FYR: NTILE and Row Number
Get the count.
Find the Quotient which will pass to NTILE function.
Order by the NTILE value and static.
Note:
I am using SQL Server 2017.
If T-SQL works fine, then you need to change the column in order by <yourcolumn>.

Selecting tables with one record in Oracle

select table_name,
to_number(extractvalue(xmltype(dbms_xmlgen.getxml('select
count(*) c from '||owner||'.'||table_name)),'/ROWSET/ROW/C')) as count
from all_tables
I would like to get those with one record in the table.
The query crashes only when the filter predicate is applied. It's probably a bug in some query rewrite optimization. If you wrap the query in a block with the materialize hint, it seems to bypass this behavior.
with workaround as(
select /*+ materialize */
owner
,table_name
,to_number(extractvalue(xmltype(dbms_xmlgen.getxml('select count(*) c from ' || owner || '.' || table_name || ' where rownum <= 2')),'/ROWSET/ROW/C')) as row_count
from all_tables
where owner = '<your-schema>'
)
select owner, table_name, row_count
from workaround
where row_count = 1;
I also found some potential to improve the performance of this query. If you only want tables with exactly one record, there is really no need to count every single record in the table. If you add the predicate rownum <= 2 Oracle will stop scanning as soon as it has found two records. So the count will be either:
0, meaning empty table
1, meaning exactly one record
2, meaning more than 1 record
Edit to show how the optimization work:
-- Creating tables
create table t0(c number);
create table t1(c number);
create table t2(c number);
create table t3(c number);
insert into t1 values(1);
insert into t2 values(1);
insert into t2 values(2);
insert into t3 values(1);
insert into t3 values(2);
insert into t3 values(3);
commit;
SQL:
/*
|| Without rownum you can filter on any rowcount you want
*/
select *
from (select 'T0' as t, count(*) as row_count from t0 union all
select 'T1' as t, count(*) as row_count from t1 union all
select 'T2' as t, count(*) as row_count from t2 union all
select 'T3' as t, count(*) as row_count from t3
)
where row_count = 1 -- Return tables having exactly 1 record.
;
/*
|| With rownum <= 1 Oracle will stop counting after it found one row.
|| So the rowcount will be either 0 or 1.
|| row_count = 0 means that the table is empty
|| row_count = 1 means that the table is NOT empty.
||
|| The Rownum predicate prevents us from knowing if there are 2,3,4 or 5 million records.
*/
select *
from (select 'T0' as t, count(*) as row_count from t0 where rownum <= 1 union all
select 'T1' as t, count(*) as row_count from t1 where rownum <= 1 union all
select 'T2' as t, count(*) as row_count from t2 where rownum <= 1 union all
select 'T3' as t, count(*) as row_count from t3 where rownum <= 1
)
where row_count = 1 -- Return tables having at least one record
;
/*
|| With rownum <= 2 Oracle will stop counting after it found two rows.
|| So the rowcount will be either 0, 1 or 2.
|| row_count = 0 means that the table is empty
|| row_count = 1 means that the table has exactly 1 record
|| row_count = 2 means that the table has more than 1 record
||
|| The Rownum predicate prevents us from knowing if there are exactly two records, or 3,4,5 etcetera
*/
select *
from (select 'T0' as t, count(*) as row_count from t0 where rownum <= 2 union all
select 'T1' as t, count(*) as row_count from t1 where rownum <= 2 union all
select 'T2' as t, count(*) as row_count from t2 where rownum <= 2 union all
select 'T3' as t, count(*) as row_count from t3 where rownum <= 2
)
where row_count = 1 -- Return tables having exactly one record
;
If your query is giving the data you want, but you just want to see the results with count equal to 1, you can wrap the query like this:
select * from (
select table_name,
to_number(extractvalue(xmltype(dbms_xmlgen.getxml('select
count(*) c from '||owner||'.'||table_name)),'/ROWSET/ROW/C')) as count
from all_tables
) where count = 1
SELECT * FROM
(
SELECT table_name,
TO_NUMBER(EXTRACTVALUE(XMLTYPE(dbms_xmlgen.getxml('select count(*) c from '||OWNER||'.'||table_name)),'/ROWSET/ROW/C')) AS cnt
FROM all_tables
)
WHERE cnt = 1;
or
SELECT * FROM
(
SELECT table_name,
TO_NUMBER(EXTRACTVALUE(XMLTYPE(dbms_xmlgen.getxml('select count(*) c from '||OWNER||'.'||table_name)),'/ROWSET/ROW/C')) AS cnt
FROM all_tables
) slct
GROUP BY slct.table_name
HAVING slct.cnt = 1;

Oracle: Select at least one record foreach cluster

Look at the following query:
SELECT *
FROM ENI_FLUSSI_HUB c1
WHERE flh_tipo_processo_cod IN ('VA', 'NUOVA_ATT_ENI')
AND rownum < 10
It simply extracts just some VA. I need the extract some VA and some NUOVA_ATT_ENI.
What is the most elegant way to do it?
You can do it like this:
SELECT *
FROM ENI_FLUSSI_HUB c1
WHERE flh_tipo_processo_cod = 'VA'
AND rownum < 5
UNION
SELECT *
FROM ENI_FLUSSI_HUB c1
WHERE flh_tipo_processo_cod = 'NUOVA_ATT_ENI'
AND rownum < 5
Is there aren't any duplicate values, you can use UNION ALL to perform faster:
SELECT *
FROM ENI_FLUSSI_HUB c1
WHERE flh_tipo_processo_cod = 'VA'
AND rownum < 5
UNION ALL
SELECT *
FROM ENI_FLUSSI_HUB c1
WHERE flh_tipo_processo_cod = 'NUOVA_ATT_ENI'
AND rownum < 5
As #DavidAldridge stated, you can always use a view to make this selection.
Here's a nicely overengineered solution:
with
va as (
select rowid ri,
t.*
from eni_flussi_hub t
where flh_tipo_processo_cod = 'VA'
and rownum <= 1),
nuova_att_eni as (
select rowid ri,
t
from eni_flussi_hub t
where flh_tipo_processo_cod = 'NUOVA_ATT_ENI'
and rownum <=1),
the_rest as (
select *
from eni_flussi_hub c1
where flh_tipo_processo_cod in ('VA','NUOVA_ATT_ENI')
and rowid not in (
select ri
from va
union all
select ri
from nuova_att_eni)
and rownum <=9)
select *
from (
select * from va
union all
select * from nuova_att_eni
union all
select * from the_rest
)
where rownum <= 10
/
I think that what it does is return at least one row for each of the two values of flh_tipe_processo_cod, and "lets nature take its course" with the rest.
You'd have to edit the *'s in the main query to avoid trying to include the rowid from the first two subquery factoring clauses.
Here's another, which I think attempts to bring back five of each but will "top up" the required total if less than five are available for either of the two subquery factoring clauses:
with
va as (
select rownum rn,
t.*
from eni_flussi_hub t
where flh_tipo_processo_cod = 'VA'
and rownum <= 10),
nuova_att_eni as (
select rownum rn,
t
from eni_flussi_hub t
where flh_tipo_processo_cod = 'NUOVA_ATT_ENI'
and rownum <=10)
select *
from (
select *
from (select * from va
union all
select * from nuova_att_eni)
order by rn asc
)
where rownum <= 10
/
Enjoy!

How to get rows from a table if total rows are more than 10 in Oracle?

I want to write a Oracle based query where I can choose if I want to see the results. Let's say:
SELECT *
FROM table
//when there are more than 10 rows
How can I do this?
select * from table
where 10 < (select count(*) from table)
Best speed:
select * from table
where 10=(select count(*) from
table
where rownum <11)
:)
UPDATE: Because there are suspicions that I claim something that is not true, here some tests:
In SQL Developer(keep in mind that select * from table will offer only first 50 rows, but count(*) read all requested rows)
The table has no indexes.
select
count(*) from
table
22074412 rows
3.16 seconds
select * from table where 10 =
(select
count(*) from
table
where rownum <11
)
0.051 seconds
select * from table where 10 <
(select
count(*) from
table
)
3.39 seconds
select count(*) from table where 10 <
(select
count(*) from
table
)
7.69 seconds
select count(*) from table where 10 =
(select
count(*) from
table
where rownum <11
)
3.42 seconds
Cause: Subquery with rownum is faster (it reads not the entire table)
DECLARE #Var int;
SET #Var = SELECT COUNT(*) FROM [somewhere]
IF #Var > 10
BEGIN
SELECT * FROM [somewhere]
END
You mean something like that?
Or just how to use the where clause?
SELECT *
FROM [somewhere]
WHERE (SELECT COUNT(*) FROM [somewhere]) > 10
This should do it for you:
SELECT * FROM [table] WHERE (SELECT COUNT(1) FROM [table]) > 10
select * from YourTable where (select count(*) from YourTable ) > 10
if you would like to avoid double scans and you have valid statistics you can
select * from table a, all_tables b
where b.num_rows > 10
and b.table_name = 'table';

Sql Select top 2 , bottom 2 and 6 random records

How to select top 2 , bottom 2 and 6 random (not in Top 2 and Bottom 2) records of the table using one SQL select query?
In MS SQL 2005/2008:
with cte
as
(
select
row_number() over (order by name) RowNumber,
row_number() over (order by newid()) RandomOrder,
count(*) over() Total,
*
from sys.tables
)
select *
from cte
where RowNumber <= 2 or Total - RowNumber + 1 <= 2
union all
select *
from
(
select top 6 *
from cte
where RowNumber > 2 and Total - RowNumber > 2
order by RandomOrder
) tt
Replace sys.tables with your table name and alter order by name to specify order condition for top 2 and bottom 2.
Perhaps not a single select statment, but it can be executed in one call:
/* Top 2 - change order by to get the 'proper' top 2 */
SELECT * from table ORDER BY id DESC LIMIT 2
UNION ALL
/* Random 6.. You may want to add a WHERE and random data to get the random 6 */
/* Old Statement before edit - SELECT * from table LIMIT 6 */
SELECT * from table t
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS top ON top.id = t.id
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS bottom ON bottom.id = t.id
WHERE ISNULL(top.id ) AND ISNULL(bottom.id)
ORDER BY RANDOM()
LIMIT 6
UNION ALL
/* Bottom 2 - change order by to get the 'proper' bottom 2 */
SELECT * from table ORDER BY id ASC LIMIT 2
Something along those lines. Basically the UNION All is the trick.
Assuming the "order" is by the id column:
select * from (select id, id from my_table order by id limit 2) t1
union
select * from (select id, id from my_table where id not in (
select * from (select id from my_table order by id asc limit 2) t22
union
select * from (select id from my_table order by id desc limit 2 ) t23)
order by rand()
limit 6) t2
union
select * from (select id, id from my_table order by id desc limit 2) t3
EDIT: Fixed syntax and tested query - it works