Selecting a set of rows more than once - sql-server

Is there a simple, concise way to select the same set of rows repeated based on a count held in a variable, without using a loop?
For instance, suppose SELECT a, b, c FROM foo WHERE whatsit = something returns
a b c
--- --- ----
1 2 3
...and I have #count with 3 in it. Is there a reasonable way without a loop to get:
a b c
--- --- ----
1 2 3
1 2 3
1 2 3
? Order doesn't matter, and I don't need to know which group any given row belongs to. I actually only need this for one row (as above), and a solution that only works for one row would do the trick, but I assume if we can do it for one, we can do it for any number.

Try with a Recursive CTE
WITH cte
AS (SELECT 1 AS id,a,b,c
FROM tablename
UNION ALL
SELECT id + 1,a,b,c
FROM cte
WHERE id < 3) --#count
SELECT a,b,c
FROM cte

Another way to do using cross join
SELECT a, b, c
FROM Table1
CROSS JOIN (SELECT number
FROM master.dbo.spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 3) T

I don't know of a way you could do this without a loop or dynamic SQL. I think a union is all that I can come up with
select q1.a,q1.b,q1.c
from (
SELECT a, b, c FROM foo
union all
SELECT a, b, c FROM foo
union all
SELECT a, b, c FROM foo ) q1
order by q1.a

Related

T-SQL Recursive with two initial statements

I don't have idea how to write this recursive in SQL. How handle with CTE when I have two initial assumptions?
Below easy example:
a1 = 2
a2 = 3
an = a(n-1)*a(n-2)
I tried write something like below but unfortunately I don't know how handle with this:
with recur(n,results) as
(
select 1,2
union all
select 2,3
union all
select
/*how to write this pattern?*/
where n<
)
select * from recur
Do you have any idea?
It seems you want to generate the Fibonacci numbers using a recursive CTE.
Try something like this:
WITH CTE AS (
SELECT 1 AS N, 2 AS A, 3 AS B
UNION ALL
SELECT N+1 AS N, B AS A, A+B AS B
FROM CTE
WHERE N<10
)
SELECT A FROM CTE

Can "constant" lookups be done efficiently within a single query?

Pop quiz, SQL Server hotshots:
How many times will the following student subquery be executed? (assuming there are at least ten rows in something):
SELECT TOP 10 a, b
, (SELECT type_id
FROM type
WHERE type_code = 'student') student
FROM something
If you said 1, then like me, you assume SQL Server would recognize the value of student as an invariant scalar.
Unfortunately, the answer is 10:
I know, I'll use a CTE!
WITH codes (student) AS (
SELECT (SELECT type_id
FROM type
WHERE type_code = 'student')
)
SELECT TOP 10 a, b
, student
FROM something
CROSS JOIN codes
The result is exactly the same.
Of course, I can get the desired efficiency by first capturing the scalar to a variable:
DECLARE #Student tinyint
SELECT #Student = type_id
FROM type
WHERE type_code = 'student'
SELECT TOP 10 a, b
, #Student student
FROM something
This only does one seek, and adds nothing to the main query plan:
But besides being more verbose, if you're defining an inline table-valued function, it means you also have to write out an otherwise implicit return schema, which is a pain (and adds a vector for errors).
Is there any way to write a single query that only runs the subquery once?
For this query:
SELECT TOP 10 a, b,
(SELECT type_id FROM type WHERE type_code = 'student'
) as student
FROM something;
You want an index on type(type_code, type_id).
You might find this more efficient if you move the subquery to the FROM clause:
SELECT TOP 10 a, b,
t.type_id
FROM something s CROSS JOIN
(SELECT type_id FROM type WHERE type_code = 'student'
) t
Or even:
SELECT TOP 10 s.a, s.b, t.type_id
FROM something s JOIN
type t
ON t.type_code = 'student';

SQL Server calculations based on aggregates inside recursive CTEs

Could you please help me with this dillema?
I tried to simplify the example as much as I could, but basically, what I want is to somehow use an aggregate of the previous results of a recursive query inside the next level of the recursive query (hope that makes sense).
I tried using window functions (max() over()), however those seem to focus on only the current row for some reason (that seems better explained here: recursive cte with ranking functions ).
I also tried referencing the 'r' CTE more than once, however that seems illegal.
Do you have any other ideas of how I can do this?
I need to do this in SQL and not T-SQL. The reason is that I actually have a working version in T-SQL using loops, but the performance of that is pretty poor for what I'm trying to do. I'm hoping a pure SQL solution will work much faster.
I'm using SQL Server 2012.
Thanks!
--this works, however it's not recursive and I don't know in advance how many "levels" there will be:
;with t as (
select 1 a, 1 b union all
select 2 a, 1 b union all
select 3 a, 1 b
), r as (
select a, b, 1 lvl
from t
)
select *
from r
union all --we took the "union all" outside the CTE, which means it's not recursive anymore
select a + max(a) over(partition by b) a, --this now works as expected and returns "a + 3" on all cases
b, lvl-1
from r
where lvl > 0
--this doesn't work:
;with t as (
select 1 a, 1 b union all
select 2 a, 1 b union all
select 3 a, 1 b
), r as (
select a, b, 1 lvl
from t
union all
select a + max(a) over(partition by b) a, --this returns the "max" over only the current row instead of doing the partition from what I expect to be the "previous step"
b, lvl-1
from r
where lvl > 0
)
select *
from r
--this also fails:
;with t as (
select 1 a, 1 b union all
select 2 a, 1 b union all
select 3 a, 1 b
), r as (
select a, b, 1 lvl
from t
union all
select a + (select max(a) from r r2 where r2.b = r.b) a, --this returns the "max" over only the current row instead of doing the partition from what I expect to be the "previous step"
b, lvl-1
from r
where lvl > 0
)
select *
from r

How to query rows with nearest column values?

How should I construct T-SQL statement in order to achieve following:
from this
table1:
display_term|occurence
A|1
A|4
B|3
B|9
retrieve this
table2:
display_term|occurence
A|4
B|3
The "nearest distance" between A and B is 1 and it can be seen in resulting table.
i.e. I want to query the nearest (column "occurrence") distinct(column "display_term") records.
Thanks in advance
For just two terms, you can do something like:
declare #T table (display_term char(1) not null,occurence int not null)
insert into #T (display_term,occurence) values
('A',1),
('A',4),
('B',3),
('B',9)
select top 1
*
from
#T t1
cross join
#T t2
where
t1.display_term = 'A' and
t2.display_term = 'B'
order by ABS(t1.occurence - t2.occurence)
Which produces:
display_term occurence display_term occurence
------------ ----------- ------------ -----------
A 4 B 3
(You can search for UNPIVOT based solutions if you need exactly the result set you asked for)
It's not clear from your question whether this needs to extend to more terms - there's no obvious way to re-interpret the requirement for more terms, so I've left that undone for now.
UNPIVOT based solution if exact result set required. Setup #T as above:
select display_term,occurence from (
select top 1
t1.occurence as A,
t2.occurence as B
from
#T t1
cross join
#T t2
where
t1.display_term = 'A' and
t2.display_term = 'B'
order by ABS(t1.occurence - t2.occurence)
) t unpivot (occurence for display_term in (A,B)) as u
Result:
display_term occurence
------------------------------------ -----------
A 4
B 3
You said you want the nearest. It's not clear exactly what that means. Your example above shows the maximum of each display term. If you want the maximum value for each display term, you want to use aggregation. This is achieved by using the Group By and the Max method.
SELECT display_term, Max(occurence) as MaxOccurrence
FROM TABLE_NAME
GROUP BY display_term
If a single row result will work for you, this will do it:
SELECT a.display_term AS adt,
a.occurence As aoc,
b.display_term AS bdt,
b.occurence AS boc,
ABS(a.occurence - b.occurence) AS distance
FROM my_table a, my_table b
WHERE a.display_term = 'A'
AND b.display_term = 'B'
AND ABS(a.occurence - b.occurence) = (
SELECT MIN(ABS(a.occurence - b.occurence))
FROM my_table a, my_table b
WHERE a.display_term = 'A'
AND b.display_term = 'B'
)

How to insert artificial data into a select statement

Suppose we got a original query as follows:
SELECT A, B, C FROM tblA
Now, I need to additional artificial rows like
SELECT 'Kyala', B, C FROM tblA when, for example, C = 100 to be inserted into the resultset.
As an example, if the tblA hold one row:
A B C
John 1 100
my goal is to return two rows like below with a single SQL query.
A B C
John 1 100
Kyala 1 100
How could I achieve it using a single SQL instead of relying on table variable or temp table?
Just refined the query to resolve error on Union:
SELECT A, B, C from tblA
UNION
SELECT 'Kyala' as A, B, C FROM tblA WHERE C = 100
And if you don't want the others where c=100 and still getting the A in the result (from the first Select in the union), you can do it like:
SELECT A, B, C from tblA WHERE C <> 100
UNION
SELECT 'Kyala', B, C FROM tblA WHERE C = 100
or
SELECT CASE(C)
when 100 then 'Kyala'
else A
END as A, B, C from tblA
You can use a CASE:
SELECT B, C,
CASE
WHEN C = 100 THEN 'Kyala'
ELSE A
END
FROM tblA
You could achieve this with the UNION operator.
SELECT A, B, C from tblA
UNION
SELECT 'Kyala', B, C FROM tblA WHERE C = 100
In response to the question in the comments about improving performance so that the table is only queried once - you could add a covering index over columns C and B so that the second part of the query uses that index rather than querying the table:
CREATE NONCLUSTERED INDEX [IX_tblA_CD] ON [dbo].[tblA]
(
[C] ASC
)
INCLUDE ( [B]) ON [PRIMARY]
GO
However, depending on the use case (this sounds like some kind of ad-hoc process for testing?), you might prefer to take the hit of two table scans rather than adding a new index which might not be appropriate for use in production.
You can use UNIION statement:
SELECT A, B, C FROM tblA
UNION
SELECT 'Kyala', B, C FROM tblA WHERE C = 100
I need to additional artificial rows like SELECT 'Kyala', B, C FROM
tblA when, for example, C = 100 to be
inserted into the resultset.
Now, read up on....
* IF in SQL Server
*SWITCH etc.
Basically, you can define an additional column as was shown
(SELECT 'test', A, B, C FROM...)
But instead of 'test' you can put in an if or switch and work with the other fields to determine the exact stuff to output.
SELECT IF (xxxx) AS FirstColumn, A, B,
C FROM ...

Resources