SQL query - strange behaviour - sql

DECLARE #OrdersTemp TABLE
(
OrderId UNIQUEIDENTIFIER
)
INSERT INTO #OrdersTemp
SELECT ord.Id
FROM Orders
--all rows count
SELECT
#RowsCount = COUNT(DISTINCT ord.Id)
FROM Orders
--#RowsCount = 5. It's right!
--second table with paging
DECLARE #OrdersTempWithPaging TABLE
(
OrderId UNIQUEIDENTIFIER
)
INSERT INTO #OrdersTempWithPaging
SELECT OrderId
FROM (SELECT DISTINCT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId) AS RowNum
FROM #OrdersTemp) AS alias
WHERE
RowNum BETWEEN (#PageIndex - 1) * #PageSize + 1
AND #PageIndex * #PageSize
SELECT * FROM #OrdersTempWithPaging
--10 or more rows. It's wrong.
Why does #OrdersTempWithPaging return wrong amount of rows? How do I avoid it?
UPDATE:
The statement below returns 25 = 5*5 rows (instead of 5)
INSERT INTO #OrdersTempWithPaging
SELECT OrderId
FROM (
SELECT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId ) AS RowNum
FROM #OrdersTemp ) AS alias
--WHERE RowNum BETWEEN ( #PageIndex - 1 ) * #PageSize + 1
-- AND #PageIndex * #PageSize
SELECT * FROM #OrdersTempWithPaging

It's because your ordering inside the select,
SELECT DISTINCT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId ) AS RowNumber
You have to coose an ordering over a column where you don't have to use DISTINCT in the selection.
SELECT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId ) AS RowNumber
Try it, without DISTINCT

Try this (reversing the DISTINCT use):
INSERT INTO #OrdersTempWithPaging
SELECT DISTINCT OrderId
FROM (SELECT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId) AS RowNum
FROM #OrdersTemp) AS alias
WHERE
RowNum BETWEEN (#PageIndex - 1) * #PageSize + 1
AND #PageIndex * #PageSize
If you need only distinct order-ids, you could have:
INSERT INTO #OrdersTemp
SELECT DISTINCT ord.Id
FROM Orders
and then:
INSERT INTO #OrdersTempWithPaging
SELECT OrderId
FROM (SELECT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId) AS RowNum
FROM #OrdersTemp) AS alias
WHERE
RowNum BETWEEN (#PageIndex - 1) * #PageSize + 1
AND #PageIndex * #PageSize

Instead of
SELECT DISTINCT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId) AS RowNum
FROM #OrdersTemp
use
SELECT OrderId,
ROW_NUMBER() OVER (ORDER BY OrderId) AS RowNum
FROM #OrdersTemp
GROUP BY OrderId
This is an interesting case of another difference between SELECT DISTINCT and SELECT GROUP BY, which manifests itself when the select list includes a ranking function.
In the first query the output includes duplicate OrderId values from #OrdersTemp because the ranking function is evaluated before DISTINCT is applied . In contrast, the second query first groups the rows by OrderId (i.e. effectively selects distinct OrderId values first) and then applies ranking.

Related

How to get minimum 3 records per a group?

I have 3 columns in SalesCart table as follows,
I need to get minimum 3 records per Item as follows,
How to do that?
I guess we can use simply Row_Number() -
declare #testtable TABLE
(
ItemCode NVARCHAR(30),
Customer VARCHAR(10),
Amount INT
)
INSERT INTO #testtable
VALUES
('A-001','A', 25000)
,('A-001','B', 15000)
,('A-001','C', 12000)
,('A-001','D', 12500)
,('A-001','E', 20000)
,('A-002','C', 3000)
,('A-002','X', 2250)
,('A-002','Y', 3750)
,('A-002','D', 3100)
select *
from #testtable
select *
from
(
select *, ROW_number() over (PARTITION BY ItemCode ORDER BY ItemCode ) as Number
from #testtable
) t
where t.Number < 4
You can also try this and you can increase or decrease number based on your requirement dynamically.
DECLARE #top INT;
SET #top = 3;
;WITH grp AS
(
SELECT ItemCode, Customer, Amount,
rn = ROW_NUMBER() OVER
(PARTITION BY ItemCode ORDER BY ItemCode DESC)
FROM itemTable
)
SELECT ItemCode, Customer, Amount
FROM grp
WHERE rn <= #top
ORDER BY ItemCode DESC;

get only row that meet condition if such row exist and if not get the row that meet another condition

this sounds like a simple question but I just cant find the right way.
given the simplified table
with t as (
select ordernumber, orderdate, case when ordertype in (5,21) then 1 else 0 end is_restore , ordertype, row_number() over(order by orderdate) rn from
(
select to_date('29.08.08','DD.MM.YY') orderdate,'313' ordernumber, 1 as ordertype from dual union all
select to_date('13.03.15','DD.MM.YY') orderdate, '90/4/2' ordernumber, 5 as ordertype from dual
)
)
select * from t -- where clause should be here
for every row is_restore guaranteed to be 1 or 0.
if table has a row where is_restore=1 then select ordernumber,orderdate of that row and nothing else.
If a table does not have a row where is_restore=1 then select ordernumber,orderdate of the row where rn=1(row where rn=1 is guaranteed to exist in a table)
Given the requirements above what do I need to put in where clause to get the following?
You could use ROW_NUMBER:
CREATE TABLE t
AS
select ordernumber, orderdate,
case when ordertype in (5,21) then 1 else 0 end is_restore, ordertype,
row_number() over(order by orderdate) rn
from (
select to_date('29.08.08','DD.MM.YY') orderdate,'313' ordernumber,
1 as ordertype
from dual union all
select to_date('13.03.15','DD.MM.YY') orderdate, '90/4/2' ordernumber,
5 as ordertype
from dual);
-------------------
with cte as (
select t.*,
ROW_NUMBER() OVER(/*PARTITION BY ...*/ ORDER BY is_restore DESC, rn) AS rnk
from t
)
SELECT *
FROM cte
WHERE rnk = 1;
db<>fiddle demo
Here is sql, that doesn't use window functions, maybe it will be useful for those, whose databases don't support OVER ( ... ) or when there are indexed fields, on which query is based.
SELECT
*
FROM t
WHERE t.is_restore = 1
OR (
NOT EXISTS (SELECT 1 FROM t WHERE t.is_restore = 1)
AND t.rn = 1
)

How do I create a temp table using this snippet?

how do i produce this SQL snippet as a temp table so I can join some other stuff into it?
with MyCTE AS
(
select *, RANK() OVER (PARTITION BY workplace ORDER BY Total DESC) AS Rank
from [dbo].[OriginDestination]
)
select * from MyCTE where Rank <= 5
Like this:
with MyCTE AS
(
select *, RANK() OVER (PARTITION BY workplace ORDER BY Total DESC) AS Rank
from [dbo].[OriginDestination]
)
select *
into #yourTempTable
from MyCTE
where Rank <= 5

SQL - Limit results

I was wondering if you could do something like this is Microsoft SQL Server 2k8 R2
Say I have a query which returns 100 rows of data.
Is their a way I can pass in some variables for example #lower_limit and #upper_limit.
Then I want the query to record the rows between the lower and upper limit
For example:
#lower_limit = 5
#upper_limt 10
Would return me rows 5 - 10 from the 100 records.
You can assign a ROW_NUMBER() over your result set and then use the BETWEEN statement to limit the rows.
A contrived example:
WITH data AS
(
SELECT
ID
,YourColumn
,ROW_NUMBER() OVER (ORDER BY ID) AS RowNum
FROM
YourTable
)
SELECT
*
FROM
data
WHERE
RowNum BETWEEN 5 AND 10
EDIT: For standard paging, here's exactly the technique I use in all the applications I develop:
DECLARE #PageNumber int = /* The page number you want */
DECLARE #PageSize int = /* The number of records per page */
WITH paged AS
(
SELECT
ROW_NUMBER() OVER(ORDER BY [OrderByColumns]) AS RowNum
,*
FROM
[YourSource]
)
SELECT
[Column1]
,[Column2]
,...
FROM
paged
WHERE
RowNum BETWEEN (#PageNumber - 1) * #PageSize + 1 AND #PageNumber * #PageSize
ORDER BY
[OrderByColumns] -- Same as used in ROW_NUMBER()
select *
from
(
select *, row_number() over(order by someColToOrderBy) RowNum
from yourTable
) a
where RowNum between #lower_limit and #uppder_limit
Something like this should work:
SELECT ID, Foo, Bar
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY ID ASC) AS Row, ID, Foo, Bar
FROM SomeTable
)
tmp
WHERE Row >= #RowRangeStart AND Row <= #RowRangeEnd

Delete one row from same rows

I have a table T with (first, second) columns. I have two rows with first=1 and second=2. I would like to delete just one of the rows. How do I do that?
;WITH CTE AS
(
SELECT TOP 1 *
FROM YourTable
WHERE first=1 and second=2
)
DELETE FROM CTE;
Or if SQL Server 2000
DELETE T
FROM (
SELECT TOP 1 *
FROM YourTable
WHERE [first]=1 and [second]=2
) T;
Then add a primary key.
You can use ROW_NUMBER().
DECLARE #T as Table(First int , Second int )
INsert Into #T
Values (1,2),
(1,2)
SELECT * FROM #T
;WITH CTE as
(SELECT ROW_NUMBER() over (order by first,second) rn , * from #T)
DELETE FROM CTE where rn = 1
select * from #T
If you change rn to include Partition by
ROW_NUMBER() over (PARTITION BY first, second order by first,second)
and change the where to be WHERE RN <> 1
you could use this as a general solution to remove any dupes on First, Second