Strange behavior OFFSET ... ROWS FETCH NEXT ... ROWS ONLY after ORDER BY - sql

I see very strange behavior of construction like
SELECT ... ORDER BY ... OFFSET ... ROWS FETCH NEXT ... ROWS ONLY
I made simple example to display it (SQL Server version: 2012 - 11.0.5058.0 (X64) ):
CREATE TABLE #temp( c1 int NOT NULL, c2 varchar(MAX) NULL)
INSERT INTO #temp (c1,c2) VALUES (1,'test1')
INSERT INTO #temp (c1,c2) VALUES (2,NULL)
INSERT INTO #temp (c1,c2) VALUES (3,'test3')
INSERT INTO #temp (c1,c2) VALUES (4,NULL)
INSERT INTO #temp (c1,c2) VALUES (5,NULL)
First query:
select * from #temp
ORDER BY c2 DESC
Result is ok:
3 test3
1 test1
2 NULL
4 NULL
5 NULL
Second query:
select * from #temp
ORDER BY c2 DESC
OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY
Result has another sorting:
3 test3
1 test1
4 NULL
And last query:
select * from #temp
ORDER BY c2 DESC
OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY
Result is very strange and invalid in my opinion (I need get two records that not contains in previous result, but instead I get record with id=4 second time):
4 NULL
2 NULL
Can anyone explain why SQL server works so strange?

Use unique key column with this type of case:
select * from #temp
ORDER BY c2, c1 DESC
OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY
Never faced ordering problem.

Related

SQL clone/replicate records within same table with a condition

I have a table and i would like to replicate/clone records within the same table. However i would like to do that with a condition. And the condition is i have a column called recordcount with numeric values. For example Row 1 can take on a value of recordcount say 7, then i would like my row 1 to be replicated 7 times. Row 2 could take on a value say 9 then i would like row 2 to be replicated 9 times.
Any help is appreciated. Thank you
What you can do (and I'm pretty sure it's not a best practice),
Is to hold a table with just numbers, which has rowcount that correspond to the numeric value.
Join that with your table, and project your table only.
Example:
create table nums(x int);
insert into nums select 1;
insert into nums select 2;
insert into nums select 2;
insert into nums select 3;
insert into nums select 3;
insert into nums select 3;
create table t (txt varchar(10) , recordcount int);
insert into t select 'A',1;
insert into t select 'B',2;
insert into t select 'C',3;
select t.*
from t
inner join nums
on t.recordcount = nums.x
order by 1
;
Will project:
"A",1
"B",2
"B",2
"C",3
"C",3
"C",3

SQL Server find unique combinations

I have a table
rate_id service_id
1 1
1 2
2 1
2 3
3 1
3 2
4 1
4 2
4 3
I need to find and insert in a table the unique combinations of sevice_ids by rate_id...but when the combination is repeated in another rate_id I do not want it to be inserted
In the above example there are 3 combinations
1,2 1,3 1,2,3
How can I query the first table to get the unique combinations?
Thanx!
Try doing something like this:
DECLARE #TempTable TABLE ([rate_id] INT, [service_id] INT)
INSERT INTO #TempTable
VALUES (1,1),(1,2),(2,1),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3)
SELECT DISTINCT
--[rate_id], --include if required
(
SELECT
CAST(t2.[service_id] AS VARCHAR) + ' '
FROM
#TempTable t2
WHERE
t1.[rate_id] = t2.[rate_id]
ORDER BY
t2.[rate_id]
FOR XML PATH ('')
) AS 'Combinations'
FROM
#TempTable t1
I put the values in a table variable just for ease of testing the SELECT query.

Sum the number of occurrence by id

Is it possible to COUNT the number of times a value occurs in a table, however, use the count of 1 if the value appears more than once for each id.
Take the below table as an example. We want to see if either {5,6} occurred for p_id. If more than 1 occurrence of {5,6} is found, treat it as 1. For eg. p_id 1, the total count is 1.
p_id status
1 5
1 6
1 2
2 5
2 5
3 4
3 2
4 6
4 2
4 5
..transforms to..
p_id count
1 1
2 1
3 0
4 1
COUNT(CASE status IN (5,6) THEN 1 END) does an overall count.
Use the CASE...WHEN... as follows:
SELECT a.id, ISNULL(b.cnt, 0)
FROM
(
SELECT DISTINCT id FROM tab
) a
LEFT JOIN
(
SELECT id, CASE COUNT(*) WHEN 1 THEN 0 ELSE 1 END 'cnt'
FROM tab WHERE val in (5, 6) GROUP BY id
) b
ON a.id = b.id
SQLFiddle
This solution provides a quick setup and a simple two-step explanation of how I do this, using your example. The second query provides the desired result:
CREATE TABLE #temp (p_id INT, [status] INT);
INSERT #temp VALUES (1,5);
INSERT #temp VALUES (1,6);
INSERT #temp VALUES (1,2);
INSERT #temp VALUES (2,5);
INSERT #temp VALUES (2,5);
INSERT #temp VALUES (3,4);
INSERT #temp VALUES (3,2);
INSERT #temp VALUES (4,6);
INSERT #temp VALUES (4,2);
INSERT #temp VALUES (4,5);
-- Simple two-step tutorial
-- First, group by p_id so that all p_id's will be shown
-- run this to see...
SELECT A.p_id
FROM #temp A
GROUP BY A.p_id;
-- Now expand your query
-- Next, for each p_id row found, perform sub-query to see if 1 or more exist with status=5 or 6
SELECT A.p_id
,CASE WHEN EXISTS(SELECT 1 FROM #temp B WHERE B.p_id=A.p_id AND [status] IN (5,6)) THEN 1 ELSE 0 END AS [Count]
FROM #temp A
GROUP BY A.p_id;
Use the SIGN() function. It is exactly what you are looking for.
SELECT
[p_id],
SIGN(COUNT(CASE WHEN [status] IN (5,6) THEN 1 END)) AS [count]
FROM #temp
GROUP BY p_id
You can translate 5,6 = 1 and rest to 0 then do max()
with cte as (
select p_id, case when status in (5,6) then 1 else 0 end status
from FROM #tem)
select p_id, max(status) status
from cte
group by p_id

Dynamic SQL Procedure that can insert into a table using a while loop to control the number of row entries

I have a small SQL based challenge that i'm trying to solve to better my knowledge of Dynamic SQL.
My requirements are as follows.
I created a table that looks as follows:
CREATE TABLE Prison_Doors
(
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
DoorOpen BIT,
DoorClosed BIT,
Trips INT
)
GO
I need to Create a Dynamic SQL Proc to insert 50 Door numbers and assign them as closed.
Expected result of proc:
|DoorNum|DoorOpen|DoorClosed|Trips|
|-------|--------|----------|-----|
| 1 | 0 | 1 |null |
|-------|--------|----------|-----|
|---------All the way to 50-------|
|-------|--------|----------|-----|
| 50 | 0 | 1 |null |
This is what I have written but it is not inserting:
BEGIN
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
VALUES('+CAST(#Index AS VARCHAR)+',0,1),'
SET #Index=#Index+1
END
SET #SQL = SUBSTRING(#SQL, 1, LEN(#SQL)-1)
EXEC(#SQL)
END
I would like to know what I am doing wrong.
after all of this is done I then need to run another loop to start at door one and change every second door to open and change trips to one and then increment to every 3 doors to open and trips becomes 2 and this incrementation continues until all doors are open which will then select the number of trips that it took.
I hope somebody can assist me with this as I am new to Dynamic SQL and I just need some guidance and not the complete solution.
Help is much appreciated :)
The error you got is because you are trying to insert into an identity column DoorNumber:
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
Remove that columns from the column list, instead of:
INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
remove that column DoorNum:
INSERT INTO Prison_Doors(DoorOpen,DoorClosed)
...
However, there is no need for dynamic SQL to do that, you can do this using an anchor table like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums
WHERE n <= 50;
Live Demo.
Update:
What my code does line by line?
Generating Sequence of numbers:
The first problem was generating a sequence of 50 numbers from 1 to 50, I used an anchor table with only four rows from 1 to 4 like this:
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n);
This syntax using the VALUES is new to SQL-Server-2008, it is called Row Value Constructor. After the VALUES, you assign an alias of the table and the target columns in parentheses like temp(n).
For old versions you have to use something like :
SELECT n
FROM
(
SELECT 1 AS n
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
) AS temp;
This will give you only 4 rows, but we need to generate 50. Thats why I CROSS JOIN this table three time with itself using:
FROM temp t1, temp t2, temp t3
It is the same as
FROM temp t1
CROSS JOIN temp t2
CROSS JOIN temp t3
This will multiply these rows 64 times, 4 rows3 = 64 rows:
1 1 1
1 2 1
1 3 1
1 4 1
2 1 1
2 2 1
2 3 1
2 4 1
....
3 1 4
3 2 4
3 3 4
3 4 4
4 1 4
4 2 4
4 3 4
4 4 4
The Use Of ROW_NUMBER() Function:
Then using the ROW_NUMBER() will give us a ranking number from 1 to 64 like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3;
Note that: ROW_NUMBER requires an ORDER BY clause, in or case it doesn't matter the order, so I used (SELECT 1).
There is also another way for generating a sequence numbers with out the use of ROW_NUMBER, it also depends on the CROSS JOIN, with an anchor table like:
WITH temp
AS
(
SELECT n
FROM (
VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)
) temp(n)
), nums
AS
(
SELECT t1.n * 100 + t2.n * 10 + t3.n + 1 AS n
FROM temp t1, temp t2, temp t3
)
SELECT n
FROM nums
ORDER BY n;
Another Way of Generating Sequence Of Numbers
Common Table Expressions:
The CTE is called common table expression, and it was introdeced in SQL Server 2005. It is one of the table expressions types that SQL Server supports. The other three are:
Derived tables,
Views, and
Inline table-valued functions.
It has a lot of important advantages. One of them is let you create a virtual tables that you can reuse them later, like what I did:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
...
Here I defined two CTE's temp then another one nums that ruse that temp so this OL, you can create multiple CTE's, just put a semicolon, then a new one with the AS clause and two ().
Insert into one table from another table using INSERT INTO ... SELECT ...
Now, we have a virtual table nums having rows from 1 to 64, we need to insert the rows from 1 to 50.
For this, you can use the INSERT INTO ... SELECT ....
Note that the columns in the INSERT clause are optional, but If you do so, you have to put a value for each row, if not you will got an error, for example if you have four columns and you put only three values in the VALUES clause or in the SELECT clause, then you will got an error. This is not valid for the idenetityt columns which are defined with:
Identity(1,1)
^ ^
| |
| ------------------The seed
The start
In this case you simply ignore that column in the columns list in the INSERT clause and it will have the identity value. There is an option that let you insert a value manually like in the #Raj's answer.
Note that: In my answer, I am not inserting the sequence numbers in to that column instead, inserting the values 50 times. But the actual numbers are generating automatically because of the Identity column:
...
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums;
WHERE n <= 50;
Firstly, you have declared DoorNum as Identity and then you are trying to explicitly insert values into that column. SQL Server does not allow this, unless you choose to
SET IDENTITY_INSERT ON
Try this query. It should insert the 50 rows you want
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorOpen,DoorClosed)VALUES(0,1)'
EXEC(#SQL)
SET #Index=#Index+1
END
Raj

How to insert multiple rows from one column?

I want to insert multiple rows from one column by splitting column value. But I have to do that without cursors because of performance issues.
Every value is splitted to 6 chars length values. Then these values also splitted to 3, 1 and 2 chars length values to insert different columns in table B.
I think giving a sample will clarify my question:
Table A
ID Value
1 ABCDEFGHJKLM
2 NOPRST
3 NULL VALUE
I want to insert these values into table B like this format
Table B
ID Value1 Value2 Value3
1 ABC D EF
1 GHJ K LM
2 NOP R ST
Supposing 600(100 rows) as maximum length of value:
insert into tableB
select id, substr(value,n*6+1,3), substr(value,n*6+4,1), substr(value,n*6+5,2)
from tableA
join (select level-1 as n from dual connect by level <= 100)
on length(value) > n*6;
see Sqlfiddle.
select ID,
SUBSTR(value,number*6+1,3),
SUBSTR(value,number*6+4,1),
SUBSTR(value,number*6+5,2)
from yourtable,
(select 0 as number union select 1 union select 2 union select 3 union select 4
union select 5 union select 6) as numbers
/* etc up to the max length of your string /6 */
where LEN(value)>number*6
try this:
Please convert it to ORACLE SQL..
Even though, its using a while loop, its doing bulk inserts..and the loop is executed as per the length of maximun length of value in the table
declare #max_len int=0;
declare #counter int=0;
declare #col_index int=1;
select #max_len=MAX(len(Value)) from TableA
while (#max_len/6 > #counter)
begin
set #counter=#counter+1
Insert into TableB
select ID,substring(Value,#col_index,3),
substring(Value,#col_index+3,1),
substring(Value,#col_index+4,2)
from TableA where substring(Value,#col_index,3) is not null
set #col_index=#col_index+6
end