How to do pagination based on a calculated column - sql-server

I have a problem ascribable to this simplified version:
I have a table in Sql Server 2008 like this (it's a denormalized table specific for my search):
ItemId | CategoryId | Descr | ExtendedDescription
0001 | 1 | Mouse X | Blue mouse
0002 | 1 | Blue Pen | Beautiful ....
0003 | 2 | Blue Pencil | Pencil with ...
0004 | 2 | Eraser | Eraser with ....
I need to search a word (like "Blue") in this table, assign a rank to the result based on where the word appears and group the result by CategoryId summing the rank.
I am able to do that; the problem arises when I try to paginating the result.
This is the stored procedure that I tried (now the word to search is fixed, but I know how to make it a parameter; I know also how to filter ID to have pagination):
CREATE PROCEDURE [dbo].[spSearch]
AS
BEGIN
SET NOCOUNT ON;
SELECT
[CategoryId],
SUM(
CASE WHEN (PATINDEX('%Blue%', Descr) > 0) THEN 100 ELSE 0 END +
CASE WHEN (PATINDEX('%Blue%', ExtendedDescription) > 0) THEN 10 ELSE 0 END
) AS Ranking,
ROW_NUMBER() OVER(ORDER BY CategoryId DESC) ID
FROM [dbo].[Data]
where (Descr like '%Blue%' or
ExtendedDescription like '%Blue%')
GROUP BY CategoryId
ORDER BY Ranking DESC
END
With this sp I get the following result:
CategoryId | Ranking | ID
0001 | 110 | 2
0002 | 100 | 1
The problem is: to paginating the result, I need that ID (ROW_NUMBER) is generated ordering descending the Ranking, while in this way it's generated in the order of CategoryId.
If I try to change the sp in this way:
ROW_NUMBER() OVER(ORDER BY Ranking DESC) ID
I can't save the sp because Ranking is not a column.
Do you have some hint?

I think by using Common Table expression (CTE) we can solve this
Try this
;WITH cte AS
(
SELECT
[CategoryId],
SUM(
CASE WHEN (PATINDEX('%Blue%', Descr) > 0) THEN 100 ELSE 0 END +
CASE WHEN (PATINDEX('%Blue%', ExtendedDescription) > 0) THEN 10 ELSE 0 END
) AS Ranking,
ROW_NUMBER() OVER(ORDER BY CategoryId DESC) ID
FROM [dbo].[Data]
where (Descr like '%Blue%' or
ExtendedDescription like '%Blue%')
GROUP BY CategoryId
)
SELECT ROW_NUMBER () OVER(ORDER BY Ranking DESC) AS r_no,
Ranking,
ID
FROM cte

Related

SQL Server - assign value to a field based on a running total

For a customer, I'm sending through an XML file to another system, the sales orders and I sum the quantities for each item across all sales orders lines (e.g.: if I have "ItemA" in 10 sales orders with different quantities in each one, I sum the quantity and send the total).
In return, I get a response whether the requested quantities can be delivered to the customers or not. If not, I still get the total quantity that can be delivered. However, could be situations when I request 100 pieces of "ItemA" and I cannot deliver all 100, but 98. In cases like this, I need to distribute (to UPDATE a custom field) those 98 pieces FIFO, according to the requested quantity in each sales order and based on the registration date of each sales order.
I tried to use a WHILE LOOP but I couldn't achieve the desired result. Here's my piece of code:
DECLARE #PickedQty int
DECLARE #PickedERPQty int
DECLARE #OrderedERPQty int=2
SET #PickedQty =
WHILE (#PickedQty>0)
BEGIN
SET #PickedERPQty=(SELECT CASE WHEN #PickedQty>#OrderedERPQty THEN #OrderedERPQty ELSE #PickedQty END)
SET #PickedQty=#PickedQty-#PickedERPQty
PRINT #PickedQty
IF #PickedQty>=0
BEGIN
UPDATE OrderLines
SET UDFValue2=#PickedERPQty
WHERE fDocID='82DADC71-6706-44C7-9B78-7FCB55D94A69'
END
IF #PickedQty <= 0
BREAK;
END
GO
Example of response
I requested 35 pieces but only 30 pieces are available to be delivered. I need to distribute those 30 pieces for each sales order, based on requested quantity and also FIFO, based on the date of the order. So, in this example, I will update the RealQty column with the requested quantity (because I have stock) and in the last one, I assign the remaining 5 pieces.
ord_Code CustOrderCode Date ItemCode ReqQty AvailQty RealQty
----------------------------------------------------------------------------
141389 CV/2539 2018-11-25 PX085 10 30 10
141389 CV/2550 2018-11-26 PX085 5 30 5
141389 CV/2563 2018-11-27 PX085 10 30 10
141389 CV/2564 2018-11-28 PX085 10 30 5
Could anyone give me a hint? Thanks
This might be more verbose than it needs to be, but I'll leave it to you to skinny it down if that's possible.
Set up the data:
DECLARE #OrderLines TABLE(
ord_Code INTEGER NOT NULL
,CustOrderCode VARCHAR(7) NOT NULL
,[Date] DATE NOT NULL
,ItemCode VARCHAR(5) NOT NULL
,ReqQty INTEGER NOT NULL
,AvailQty INTEGER NOT NULL
,RealQty INTEGER NOT NULL
);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2539','2018-11-25','PX085',10,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2550','2018-11-26','PX085', 5,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2563','2018-11-27','PX085',10,0,0);
INSERT INTO #OrderLines(ord_Code,CustOrderCode,[Date],ItemCode,ReqQty,AvailQty,RealQty) VALUES (141389,'CV/2564','2018-11-28','PX085',10,0,0);
DECLARE #AvailQty INTEGER = 30;
For running totals, for SQL Server 20012 and up anyway, SUM() OVER is the preferred technique so I started off with some variants on that. This query brought in some useful numbers:
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
ol.ReqQty,
#AvailQty AS AvailQty,
SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS TotalOrderedQty,
#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS RemainingQty
FROM
#OrderLines AS ol;
Then I used the RemainingQty to do a little math. The CASE expression is hairy, but the first step checks to see if the RemainingQty after processing this row will be positive, and if it is, we fulfill the order. If not, we fulfill what we can. The nested CASE is there to stop negative numbers from coming into the result set.
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
ol.ReqQty,
#AvailQty AS AvailQty,
SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS TotalOrderedQty,
#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]) AS RemainingQty,
CASE
WHEN (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty
ELSE
CASE
WHEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]))
ELSE 0
END
END AS RealQty
FROM
#OrderLines AS ol
Windowing functions (like SUM() OVER) can only be in SELECT and ORDER BY clauses, so I had to do a derived table with a JOIN. A CTE would work here, too, if you prefer. But I used that derived table to UPDATE the base table.
UPDATE Lines
SET
Lines.AvailQty = d.AvailQty
,Lines.RealQty = d.RealQty
FROM
#OrderLines AS Lines
JOIN
(
SELECT
ol.ord_Code,
ol.CustOrderCode,
ol.Date,
ol.ItemCode,
#AvailQty AS AvailQty,
CASE
WHEN (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty
ELSE
CASE
WHEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date])) > 0
THEN ol.ReqQty + (#AvailQty-SUM(ReqQty) OVER (PARTITION BY ord_Code ORDER BY [Date]))
ELSE 0
END
END AS RealQty
FROM
#OrderLines AS ol
) AS d
ON d.CustOrderCode = Lines.CustOrderCode
AND d.ord_Code = Lines.ord_Code
AND d.ItemCode = Lines.ItemCode
AND d.Date = Lines.Date;
SELECT * FROM #OrderLines;
Results:
+----------+---------------+---------------------+----------+--------+----------+---------+
| ord_Code | CustOrderCode | Date | ItemCode | ReqQty | AvailQty | RealQty |
+----------+---------------+---------------------+----------+--------+----------+---------+
| 141389 | CV/2539 | 25.11.2018 00:00:00 | PX085 | 10 | 30 | 10 |
| 141389 | CV/2550 | 26.11.2018 00:00:00 | PX085 | 5 | 30 | 5 |
| 141389 | CV/2563 | 27.11.2018 00:00:00 | PX085 | 10 | 30 | 10 |
| 141389 | CV/2564 | 28.11.2018 00:00:00 | PX085 | 10 | 30 | 5 |
+----------+---------------+---------------------+----------+--------+----------+---------+
Play with different available qty values here: https://rextester.com/MMFAR17436

SQL: doing several joins to evaluate different columns

I'm quite new to SQL but use it a lot now in my work now (Microsoft SQL Server).
So the issue is this: I collect data that is atypical for a certain column.
Let's say I got different Burgers and they should have a standardized calories value. So I did this with a query
------------------------------------------
| Burger | calories | numBurgers | Rank |
------------------------------------------
| Chicken| 600 | 20 | 1 |
| Chicken| 400 | 3 | 2 |
| Beef | 700 | 35 | 1 |
| Beef | 850 | 4 | 2 |
-------------------------------------------
To get a list of all the "wrong" burgers I use a temporary table and filter out GroupRank = 1
USE database;
GO
WITH GapRanking AS
(
SELECT TOP 100 PERCENT Burger, calories, COUNT(calories),
ROW_NUMBER() OVER(PARTITION BY Burger ORDER BY COUNT(calories) DESC) AS Rank
)
SELECT * FROM GapRanking
WHERE Rank <> 1
...
I get all combinations of Burgers and calories that are not "standard"
Then I do an Inner Join with the original table and all columns on the one above.
SELECT * FROM BaseTable as base
INNER JOIN
(SELECT * FROM GapRanking
WHERE Rank <> 1) AS err
ON (base.Burgers = err.Burgers
AND base.calories = err.calories)
This way I get a table with complete information about the "not-standard" burgers. So far so good.
Now I want to add other rows where there is a deviation in another criteria, price for example, not just calories and add it to the list if its not already there.
So I thought of UNION or JOIN.
So what is the best approach. UNION the above query with the same query just different column (price instead of calories)?
Or do a JOIN with the same query just different column (price instead of calories)?
The code gets quite "ugly" and I'm not sure if I do the right approach here.
Also because of me using the temporary table using WITH a UNION does not seem possible so easily.
I'm really glad for any ideas here. Cheers
use sub-query and join below is just sudo-code not actual you can follow like this way
select t1.*, t2.required_colum
(SELECT TOP 100 PERCENT Burger, calories, COUNT(calories),
ROW_NUMBER() OVER(PARTITION BY Burger ORDER BY COUNT(calories) DESC) AS Rank
) as t1
join
(SELECT TOP 100 PERCENT Burger, calories, COUNT(calories),
ROW_NUMBER() OVER(PARTITION BY Burger ORDER BY COUNT(calories) DESC) AS Rank
) as t2
on t1.colname = t2.colname
where t1.Rank != 1 and t2.Rank != 1

Field equal 1 display

I am using SQL Server 2008 and I would like to only get the activityCode for the orderno when it equals 1 if there are duplicate orderno with the activityCode equals 0.
Also, if the record for orderno activityCode equals 0 then display those records also. But I would only like to display the orderno when the activityCode equals 0 if the same orderno activityCode does not equal 1 or the activityCode only equals 0. I hope this is clear and makes sense but let me know if I need to provide more details. Thanks
--create table
create table po_v
(
orderno int,
amount number,
activityCode number
)
--insert values
insert into po_v values
(170268, 2774.31, 0),
(17001988, 288.82, 0),
(17001988, 433.23, 1),
(170271, 3786, 1),
(170271, 8476, 0),
(170055, 34567, 0)
--Results
170268 | 2774.31 | 0
17001988 | 433.23 | 1
170271 | 3786 | 1
170055 | 34567 | 0
*****Updated*****
I have inserted two new records and the results have been updated. The data in the actual table has other numbers besides 0 and 1. The select statement displays the correct orderno's but I would like the other records for the orderno to display also. The partition only populates one record per orderno. If possible I would like to see the records with the same activityCode.
--insert values
insert into po_v values
(170271, 3799, 1),
(172525, 44445, 2)
--select statement
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
--select statement results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
--expected results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
170271 | 3799 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
Not totally clear what you are trying to do here but this returns the output you are expecting.
select orderno
, amount
, activityCode
from
(
select *
, RowNum = ROW_NUMBER() over(partition by orderno order by activityCode desc)
from po_v
) x
where x.RowNum = 1
---EDIT---
With the new details this is a very different question. As I understand it now you want all row for that share the max activity code for each orderno. You can do this pretty easily with a cte.
with MyGroups as
(
select orderno
, Activitycode = max(activitycode)
from po_v
group by orderno
)
select *
from po_v p
join MyGroups g on g.orderno = p.orderno
and g.Activitycode = p.Activitycode
Try this
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
Result
Orderno Amount Activitycode
------------------------------------
170055 34567.00 0
170268 2774.31 0
170271 3786.00 1
17001988 433.23 1

Use ORDER BY in SQL Server starting from row 2 onwards only

I have a table that always returns multiple of rows, in which I use those rows as value in a combo box in VB.NET. In those rows, the 1st row is the data that is set to be as default value. Now, the rest of the rows must be ordered alphabetically to make it easier to read. I'm trying to use ROW_NUMBER() right now, is there a concise way for me to do this.
Using this structure as basis.
tbl_Sample
col_ID - int
col_description - varchar(30)
with these datas present
col_ID | col_description
--------------------------
1 | Default_Value
2 | a_value2
3 | a_value2
4 | a_value5
5 | a_value1
6 | a_value3
i want to have a query that returns something like this
col_ID | col_description
--------------------------
1 | Default_Value
5 | a_value1
2 | a_value2
3 | a_value2
6 | a_value3
4 | a_value5
as for the query, as I said I'm testing up ROW_NUMBER() along with OVER and ORDER BY, since ordering it by col_description will not work since the arrangement of the descriptions in alphabetical order will alter the Default_Value's row number which must remain in row 1.
row_number() over(order by case when col_description = 'Default_Value' then 0 else 1 end ASC, col_description ASC)
Actually, I see no reason to use row_number() here. you can use the case in the order by clause directly:
SELECT col_ID, col_description
FROM tbl_Sample
ORDER BY CASE WHEN col_description = 'Default_Value' THEN 0 ELSE 1 END, col_description
If you want to GROUP BY column, using PARTITION BY, like this:
SELECT *, ROW_NUMBER()
OVER(PARTITION BY col_description ORDER BY CASE WHEN col_description = 'Default_Value' THEN 0 ELSE 1 END ASC, col_description ASC) AS RN
FROM tbl_Sample

TSQL, Remove rows based upon Row-Index

My Table is like this.....
**AttName** **Title** **Count_Index**
Red Boys 1
Red Girls 2
Green Boys 1
Blue Boys 1
I only Want to return...
Red Boys 1
Red Girls 2
Thats because I have Red with two entries, I want to skip/remove all the ROW(s) if their Count is ONLY 1. In other words I am only interested in rows if their count goes above "1".
Try
SELECT *
FROM table1
WHERE AttName IN (SELECT AttName FROM table1 GROUP BY AttName HAVING COUNT(*) > 1)
SQLFiddle
Output
| ATTNAME | TITLE | COUNT_INDEX |
---------------------------------
| Red | Boys | 1 |
| Red | Girls | 2 |
Ok, this is tested. I like using windowing functions when looking for things like duplicates. Particularly because the avoids doing a subselect in a where clause, and from the same table twice. Instead all the needed columns are already pulled in the subselect. Although windowing function can be expensive sometimes.
Select *, ROW_NUMBER() over (Partition by AttrName Order By AttrName) --probably better to order by whatever the primary key is for consistent results, esepcially if you plan to use this in a delete statement
From (
SELECT AttName, title, COUNT(AttrName) over (partition by AttrName) as cnt
FROM yourtable
) as counted
Where counted.cnt > 1

Resources