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

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

Related

sql selection of one value from several identical

I have the result of executing a query. it collects data from several tables. he is such a:
|Name|date |number|Id
|alex|01-01-2021 |1111 | 1
|mike|01-01-2021 |2222 | 2
|alex|02-01-2021 |1111 | 3
|alex|03-01-2021 |1111 | 4
|john|04-01-2021 |3333 | 5
i need to get the following result:
|Name|date |number| Id
|mike|01-01-2021|2222 | 2
|alex|any value |1111 | Any value
|john|04-01-2021|3333 | 5
I need to select one of the repeated values and show it.I have a large query with many columns. here I gave only a short version to explain the essence of the problem
select Name,max(date) as date,number
from atable
group by Name, number
You may use this CTE and manage which date (first or last) you will get
WITH data AS (
SELECT
Name,
date,
number,
row_number() OVER (PARTITION BY Name ORDER BY date) AS row_num
FROM test01
)
SELECT
Name,
date,
number
FROM data
WHERE row_num = 1

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

TSQL Conditional Where or Group By?

I have a table like the following:
id | type | duedate
-------------------------
1 | original | 01/01/2017
1 | revised | 02/01/2017
2 | original | 03/01/2017
3 | original | 10/01/2017
3 | revised | 09/01/2017
Where there may be either one or two rows for each id. If there are two rows with same id, there would be one with type='original' and one with type='revised'. If there is one row for the id, type will always be 'original'.
What I want as a result are all the rows where type='revised', but if there is only one row for a particular id (thus type='original') then I want to include that row too. So desired output for the above would be:
id | type | duedate
1 | revised | 02/01/2017
2 | original | 03/01/2017
3 | revised | 09/01/2017
I do not know how to construct a WHERE clause that conditionally checks whether there are 1 or 2 rows for a given id, nor am I sure how to use GROUP BY because the revised date could be greater than or less than than the original date so use of aggregate functions MAX or MIN don't work. I thought about using CASE somehow, but also do not know how to construct a conditional that chooses between two different rows of data (if there are two rows) and display one of them rather than the other.
Any suggested approaches would be appreciated.
Thanks!
you can use row number for this.
WITH T AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC) AS RN
FROM YourTable
)
SELECT *
FROM T
WHERE RN = 1
Is something like this sufficient?
SELECT *
FROM mytable m1
WHERE type='revised'
or 1=(SELECT COUNT(*) FROM mytable m2 WHERE m2.id=m1.id)
You could use a subquery to take the MAX([type]). In this case it works for [type] since alphabetically we want revised first, then original and "r" comes after "o" in the alphabet. We can then INNER JOIN back on the same table with the matching conditions.
SELECT T2.*
FROM (
SELECT id, MAX([type]) AS [MAXtype]
FROM myTABLE
GROUP BY id
) AS dT INNER JOIN myTable T2 ON dT.id = T2.id AND dT.[MAXtype] = T2.[type]
ORDER BY T2.[id]
Gives output:
id type duedate
1 revised 2017-02-01
2 original 2017-03-01
3 revised 2017-09-01
Here is the sqlfiddle: http://sqlfiddle.com/#!6/14121f/6/0

How to do pagination based on a calculated column

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

Perform a double sort on two columns in SQL Server with a primary sort not in the evaluation of the first two

I have a table using SQL Server 2008 it has a table with two sortable columns on it one is manually set and the other is calculated by a system procedure (this procedure sorts everything as a whole and assigns a sort starting at 10 until the highest row number times 10)
ID Manual System
------------------------
1 null 300
2 2 380
3 null 500
4 null 200
And I am trying to get it to sort the ids to be 4,2,1,3
I would like the output to take the Manual Sort over the System when it has been applied. to further complicate things if another row is added and it has a manual sort that also needs to be considered.
ID Manual System
-----------------------
1 null 300
2 2 380
3 null 500
4 null 200
5 5 100
so the new sort would be 4,2,1,3,5
ID Manual System
-----------------------
4 null 200
2 2 380
1 null 300
3 null 200
5 5 100
Any ideas? and I have tried Rank, Dense_Rank, Row_Number etc.
The solutions that have been given seem correct for my example. I forgot to mention there is a third column personID that is also a factor here.
ID Manual System PersonID
-------------------------------------
4 null 200 22
2 2 380 22
1 null 300 22
3 null 200 22
5 5 100 22
8 1 210 25
6 1 480 25
7 null 600 25
9 4 800 25
10 null 990 25
So I first have to order them by person then, order them by Manual then by sort. which still seems to give me an issue.
Here is my solution: http://sqlfiddle.com/#!3/a32a0/1/0
SELECT *
FROM
(
SELECT
ID
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY System)-.1 AS rn
, Manual
, System
, PersonID
FROM YourTable
) t0
ORDER BY PersonID
, COALESCE(Manual, RN)
Here is the explanation:
We are taking the row number as the base row number. But since we first order by a higher-order index of PersonID, I PARTITION BY... before I ORDER BY... this resets the index for each grouping of MANUAL
In the case of a tie between the natural ordering of the ROW_NUMBER and the MANUAL sorting, I subtract .1 (arbitrary amount between (0,1)). This gives preference to the MANUAL value in case of a tie
When it comes to ordering the final result, I ORDER BY the PARTITION BY value first, ensuring the proper grouping first, then I order by the first non-null value of MANUAL and RN
Give it a try. +points to the starting points of the previous two answers. I used one of them as a starting point and re-wrote from there.
EDIT: Removed the subtraction of .1 and added a new ranking function which "tricks" the optimizer into preferring manual over rank. I have no idea if this holds up in all cases or if the optimizer will fail to give the results in this order under other circumstances, but I wanted to include the findings just in case they're helpful.
My updated query is as follows:
SELECT *
FROM
(
SELECT
ID
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY System) AS rn
, ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Manual) AS rn_throwaway
, Manual
, System
, PersonID
FROM YourTable
) t0
ORDER BY PersonID
, COALESCE(Manual, RN)
And examples of it in use are at http://sqlfiddle.com/#!3/1831d/55/0 and http://sqlfiddle.com/#!3/a32a0/9/0
If I'm understanding your requirements, you want to sort by the System column, unless the Manual column is supplied, and in which case, use that as the sort position instead? If so, then this should work for you using CASE and ROW_NUMBER:
SELECT Id, Manual, System
FROM (
SELECT Id,
Manual,
System,
ROW_NUMBER() OVER (ORDER BY Manual, System) rn
FROM YourTable) t
ORDER BY CASE WHEN Manual IS NULL THEN RN ELSE Manual END, COALESCE(Manual,RN+1)
SQL Fiddle Demo
I think this is what you need. It is bit difficult to explain.
Basically inserting not null manual values as row index (or row number) to the record list ordered by system.
FIDDLE DEMO
;with cte as (
select id, manual,system,
convert(decimal(10,1),row_number() over(order by system)) rn
from t
where manual is null
union all
select id, manual,system, convert(decimal(10,1),manual-0.5) rn
from t
where manual is not null
)
select id,manual,system
from cte
order by rn
| ID | MANUAL | SYSTEM |
------------------------
| 4 | (null) | 200 |
| 2 | 2 | 380 |
| 1 | (null) | 300 |
| 3 | (null) | 500 |
| 5 | 5 | 100 |

Resources