SQL Using TOP n with UNION, but only want results of second query if first does not have enough records - sql

I am trying to write a sql statement that works something like a store. I have two queries and I want n records from the first query, but if there is less than n, I want the rest from the second.
I tried using TOP n and UNION
SELECT TOP 20 FROM (
(SELECT * FROM t1)
UNION
(SELECT * FROM t2))
but the results are from both tables regardless of how many are in t1. Basically, I want the first query to have precedence. If 5 records exist there and I want them and I want the rest from t2.

Add a column that identifies the query, so the first one have precedence.
SELECT TOP 20 *
FROM ((SELECT 1 as query, * FROM t1)
UNION
(SELECT 2 as query, * FROM t2))
ORDER BY query

you can use a CTE to place a sort order on the two tables and then use that in an order by clause
declare #foo1 table(
bar INT
)
insert into #foo1
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
--,(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)
declare #foo2 table(
bar INT
)
insert into #foo2
values (101),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(114),(115),(116),(117),(118),(119),(120)
;with base_data as (
select
0 as sort,
f1.bar
FROM #foo1 f1
UNION
SELECT
1 as sort,
f2.bar
FROM #foo2 f2
)
select top 20 bar
from base_data
order by sort, bar

Related

Msg 120, Level 15, State 1, Procedure Generate_Exame, Line 6,The select list for the INSERT statement contains fewer items than the insert list

I want insert in question table that has these columns
C#_T_F_Id, C#_T_F_Q, C#_T_F_Choices, C#_Mcq_Id, C#_MCQ_Q, C#_Choices
After execute Generate_Exame procedure what should I do :
create procedure Generate_Exame
#course_id int
as
if #course_id = 600
begin
insert into [dbo].[Question](C#_T_F_Id, C#_T_F_Q, C#_T_F_Choices,
C#_Mcq_Id, C#_MCQ_Q, C#_Choices)
select *
from
(select top(3)
T.C#_T_F_Id, T.C#_T_F_Q, T.C#_T_F_Choices
from
C#_T_F T
order by
newid()) as t1
union all
select *
from
(select top(7)
C.C#_Mcq_Id C#_Q_id, C.C#_MCQ_Q C#_question, C.C#_Choices Choices
from
C#_MCQ C
order by
newid()) as t2)
end
If I understand well you want to:
Insert data into a table from a combined result set.
Combine two result sets side by side. The first one provides columns 1, 2, and 3, while the second one provides column 4, 5, and 6.
On top of this both result sets (left and right) do not have the same lenght. One has 3 rows, while the other has 7 rows. I assume these numbers may vary.
There's no set order for the rows on the left, or the rows on the right. You are producing them by ordering using a random UUID, so that can change every time you run the query.
In order to do this you need to produce a row number on each side. Then a simple full join will combine both result sets.
For example:
insert into [dbo].[Question] (
C#_T_F_Id, C#_T_F_Q, C#_T_F_Choices,
C#_Mcq_Id, C#_MCQ_Q, C#_Choices
)
select -- Step #4: produce combined rows, ready for insert
a.T.C#_T_F_Id, a.T.C#_T_F_Q, a.T.C#_T_F_Choices,
b.C#_Q_id, b.C#_question, b.Choices
from ( -- Step #1: Produce the left result set with row number (rn)
select *, row_number() over(order by ord) as rn
from (
select top(3)
T.C#_T_F_Id, T.C#_T_F_Q, T.C#_T_F_Choices,
newid() as ord
from C#_T_F T
order by ord
) x
) a
full join ( -- Step #2: Produce the right result set with row number (rn)
select *, row_number() over(order by ord) as rn
from (
select top(7)
C.C#_Mcq_Id C#_Q_id, C.C#_MCQ_Q C#_question, C.C#_Choices Choices,
newid() as ord
from C#_MCQ C
order by ord
) y
) b on a.rn = b.rn -- Step #3: Full join both result sets by row number (rn)
You are having six columns in the INSERT clause. But, you have only 3 columns coming out of the UNION query.
-- You are inserting 6 columns
insert into [dbo].[Question](C#_T_F_Id, C#_T_F_Q, C#_T_F_Choices,
C#_Mcq_Id, C#_MCQ_Q, C#_Choices)
-- You are selecting only 3 columns.
select *
from
(select top(3)
T.C#_T_F_Id, T.C#_T_F_Q, T.C#_T_F_Choices
from
C#_T_F T
order by
newid()) as t1
union all
select *
from
(select top(7)
C.C#_Mcq_Id C#_Q_id, C.C#_MCQ_Q C#_question, C.C#_Choices Choices
from
C#_MCQ C
order by
newid()) as t2)
If you need to have 6 columns, you need to join the two SELECT statements in some way, based on JOIN condition.

Run a query with out a subquery to yield results

'MotorVehicles' Table
I ran a query to find 'AVG(Price) * 2' of attached table, then I ran another query where I substituted 'AVG(Price) * 2' with a hard number, I was able to get the two records in the result table, I have tried to use the aggregate functions in a 'Having' clause but my result table comes back empty. Need some help I would like to formulate a SELECT statement without a subquery to find all Motor vehicles whos price is more or equal to 'AVG(Price * 2)' in attached table.
thanks in advance
Many methods, this isn't nice, but it would work:
;with cte_a as
(
select avg(Price)*2 [Average]
from yourTable
-- or whatever your query to get average is as long as only 1 result
)
select *
from yourTable yt
inner join cte_a a on 1 = 1
where price >= a.Average
select * from MotorVehicles where price > (select avg(price) from t)*2;
I apologise if this is the subquery you want to avoid.
You can get something similar using a partitioned AVG().
DECLARE #T TABLE
(
X INT
)
INSERT #T SELECT 1
INSERT #T SELECT 10
INSERT #T SELECT 15
INSERT #T SELECT 20
SELECT X,XAVG=AVG(X) OVER(PARTITION BY 1 ) FROM #T
Resulting in:
X XAVG
1 11
10 11
15 11
20 11

Alternating Results of two SQL queries

This is probably not possible, but is there a way to output the results of two queries in alternating lines on a table?
For example if I have two tables that are trying to show one widget vs all of the widgets in its category, I would output each widget followed by the category averages, followed by widget 2 with its category averages in the next line. This would result in 4 lines. This is all assuming that widgets and their category averages are in two separate tables.
Sorry if this was confusing, I can clarify if I need to. I'm just trying to make it very simple for the final application to display in C#. It would probably be easier to do in the actual application but I'm not very familiar with C#...
First - as said in the comments - If you have a field to order by you can use one select with union and order by. and you are done.
Even if not (different ordering on each query) it's stil possible (assuming the two queries have the same exact schema):
SELECT *
FROM (SELECT ROW_NUMBER() OVER (ORDER BY ColA) OrderA,1 OrderB,*
FROM A
UNION ALL
SELECT ROW_NUMBER() OVER (ORDER BY ColB) + 1 OrderA,2 OrderB, *
FROM B) C
ORDER BY OrderA, OrderB
Disclaimer - I don't think it's a database operation.
This is possible using two table variables, two sort columns in each and a union all, but I would recommend that you join the two tables together and have one row for each widget (can have many columns one for category averages) and manage the display aspects of the design on the C# side
If you have two temporary tables with identity columns, you can select a UNION of both tables with a careful manipulation of the row number:
declare #linesA table
(
lineid int identity(1,1) primary key clustered,
line varchar(80)
)
declare #linesB table
(
lineid int identity(1,1) primary key clustered,
line varchar(80)
)
insert into #linesA (line)
select 'A - 1'
union select 'A - 2'
union select 'A - 3'
insert into #linesB (line)
select 'B - 1'
union select 'B - 2'
union select 'B - 3'
select
lineid * 2 as RowNum,
line from #linesA
union select
lineid * 2 + 1 as RowNum,
line from #linesB
order by RowNum
If you don't have an identity column, then create one with ROW_NUMBER() like this:
select
row_number() over (order by line) * 2 as RowNum,
line from #linesA
union select
(row_number() over (order by line) * 2) + 1 as RowNum,
line from #linesB
order by RowNum
as updated in the comments.
first CTE is to generate only ODD and Even Numbers
remember that by default max allowed recursion is 100.
so we are setting value of #I to the number of records in to first table.
also notice the "OPTION (MAXRECURSION 0);" which is required if number of records happen to be more that 100 and in that case recursion happen that many times.
DECLARE #widget TABLE
(
widgetID INT
,widgetName sysname
,WidgetType sysname
)
DECLARE #categoryAvg TABLE
(
WidgetType sysname
,categoryAvg sysname
)
INSERT INTO #widget( widgetID, widgetName,WidgetType )
SELECT 1,'widget1','Wtype1'
UNION ALL SELECT 2,'widget2','Wtype1'
UNION ALL SELECT 2,'widget3','Wtype2'
UNION ALL SELECT 2,'widget4','Wtype2'
INSERT INTO #categoryAvg( WidgetType, categoryAvg )
SELECT 'Wtype1',10 UNION ALL SELECT 'Wtype2',20
declare #i int=100--MAX 100 ByDefault
declare #StartOdd int=1 --To generate ODD numbers
declare #StartEven int=2 --To generate EVEN numbers
SELECT #i = COUNT(*) From #widget
;WITH CTE_OE(Rowid,OddNum,EvenNum)
as
(
select 1,#StartOdd,#StartEven
union all
select t1.rowid+1,t1.OddNum+2,t1.EvenNum+2
from CTE_OE t1
where t1.rowid<#i
),
CTE_1(WidgetType,OutputColumn,RowID)
AS
(
SELECT t1.WidgetType,t1.OutputColumn,t2.OddNum
FROM
(
SELECT WidgetType
,widgetName As OutputColumn
,ROW_NUMBER() OVER (ORDER BY widgetName,WidgetType) RowID
FROM #widget
)t1
JOIN CTE_OE t2
ON t1.RowID=t2.Rowid
),
CTE_2(OutputColumn,RowID)
AS
(
Select t1.OutputColumn
,t2.EvenNum
From
(
SELECT 'Type'+ ' = ' + q1.WidgetType + ', Avg = ' + q1.categoryAvg As OutputColumn
,ROW_NUMBER() OVER (ORDER BY q1.WidgetType) AS RowID
FROM #categoryAvg q1
JOIN CTE_1 q2
on q1.WidgetType=q2.WidgetType
)t1
JOIN CTE_OE t2
ON t1.RowID=t2.Rowid
)
Select OutputColumn
From
(
Select OutputColumn,RowID from CTE_1
union all
select OutputColumn,RowID from CTE_2
)qry
order by RowID
OPTION (MAXRECURSION 0);
I don't know C# personally, so I don't know how hard/easy this would be to do within the application, but just as a side thought, one way you might be able to approach it within the SQL query without having to make the two separate queries and alternate display and all... an option might be to just do a join and display results side by side. What I mean is you could do something along the lines of:
SELECT Widget.Name, Widget.Category, Widget.Speed, Category.Speed AS AvgSpeed
FROM Widget
INNER JOIN Category
ON Widget.Category=Category.Name;
Then you would end up having a table something along the lines of
Name Category Speed AvgSpeed
W1 Sample 1ms 2ms

SELECT TOP ... FROM UNION

What is the best way to SELECT TOP N records from UNION of 2 queries?
I can't do
SELECT TOP N ... FROM
(SELECT ... FROM Table1
UNION
SELECT ... FROM Table2)
because both queries return huge results I need every bit of optimization possible and would like to avoid returning everything. For the same reason I cannot insert results into #TEMP table first either.
I can't use SET ROWCOUNT N either because I may need to group results and this command will limit number of grouped rows, and not underlying row selections.
Any other ideas? Thanks!
Use the Top keyword for inner queries also:
SELECT TOP N ... FROM
(SELECT TOP N... FROM Table1
UNION
SELECT TOP N... FROM Table2) as result
You could try the following. It uses a CTE which would give you the results of both queries first and then you would select your TOP N from the CTE.
WITH table_cte (
(
[col1],
[col2],
...
)
AS
(
SELECT *
FROM table1
UNION ALL
SELECT *
FROM table2
)
SELECT TOP 1000 * FROM table_cte

Sql inner query issue

I have a table tbl_test:
create table tbl_test (
tabid int identity
)
with the values:
Insert into tbl_test values 1 union 2 union 3 .... union 1000
Query:
select MAX(b.tabid) from
(
select top 100 * from tbl_test
) as b
I expect this query to return 100 but instead it returns 1000.
select top 100 * from tbl_test
There is no explicit order on the inner statement, so there is no guarentee in which order the rows are read. If you order it by tabid ASC you should see the expected 100.
You're not including an order by clause in your subquery (which is allowed in conjunction with TOP), so there's no telling what records will come back. 1000 is obviously being included in the data returned from the subquery, which means it will be returned by MAX.