How to sort one column by two ways - sql-server

I have a table called product and its data sample data structer as follows,
+-----------+-------------+-----------+
| ProductID | ProductName | SortValue |
+-----------+-------------+-----------+
| 1 | AA12 | 0 |
+-----------+-------------+-----------+
| 2 | AA10 | 0 |
+-----------+-------------+-----------+
| 3 | AA11 | 0 |
+-----------+-------------+-----------+
| 4 | AA13 | 0 |
+-----------+-------------+-----------+
| 5 | AA1 | 2 |
+-----------+-------------+-----------+
| 6 | AA2 | 1 |
+-----------+-------------+-----------+
| 7 | AA3 | 4 |
+-----------+-------------+-----------+
| 8 | AA4 | 3 |
+-----------+-------------+-----------+
| 9 | AA5 | 5 |
+-----------+-------------+-----------+
| 10 | AA6 | 6 |
+-----------+-------------+-----------+
I need to insert this table data into another temporary table with sorting using its SortValue. Here you can see multiple sortvalue as 0. Those 0 consists SortValue Should be inserted into end of the table by its ProductName. My expected output should be,
+-----------+-------------+-----------+
| ProductID | ProductName | SortValue |
+-----------+-------------+-----------+
| 6 | AA2 | 1 |
+-----------+-------------+-----------+
| 5 | AA1 | 2 |
+-----------+-------------+-----------+
| 8 | AA4 | 3 |
+-----------+-------------+-----------+
| 7 | AA3 | 4 |
+-----------+-------------+-----------+
| 9 | AA5 | 5 |
+-----------+-------------+-----------+
| 10 | AA6 | 6 |
+-----------+-------------+-----------+
| 2 | AA10 | 0 |
+-----------+-------------+-----------+
| 3 | AA11 | 0 |
+-----------+-------------+-----------+
| 1 | AA12 | 0 |
+-----------+-------------+-----------+
| 4 | AA13 | 0 |
+-----------+-------------+-----------+
How can I do this? I just tried something like this.
DECLARE #tmp TABLE (
[ProductID] INT,
[ProductName] VARCHAR(50),
[SortValue] INT )
INSERT INTO #tmp
p.ProductID,p.ProductName,p.SortValue
SELECT FROm Product p ORDER by p.SortValue

As a starter: database tables represent unordered set of rows. There is no inherent ordering of the rows: when you select from a table, you control the order in which rows are returned with the order by clause.
So I understand your question as how to select rows in the relevant order. For this, you can use a conditional sort:
select t.*
from mytbable
order by
case when sortValue = 0 then 1 else 0 end,
sortValue,
productName
The case expression in the first level of sorting puts rows where sortValue is not 0 first. Then, groups are sorted by sortValue and productName.

Related

SQL Server Lag by partitioned group

I have a table of data as follows:
+----+-------+----------+
| id | value | group_id |
+----+-------+----------+
| 1 | -200 | 0 |
| 2 | -620 | 0 |
| 3 | -310 | 0 |
| 4 | 400 | 1 |
| 5 | 300 | 1 |
| 6 | 100 | 1 |
| 7 | -200 | 2 |
| 8 | -400 | 2 |
| 9 | -500 | 2 |
+----+-------+----------+
What I would like to do is produce a 4th column that, for each record, shows the last value of the preceding group_id.
So the result I want is as follows:
+----+-------+----------+----------------+
| id | value | group_id | LastValByGroup |
+----+-------+----------+----------------+
| 1 | -200 | 0 | 0 |
| 2 | -620 | 0 | 0 |
| 3 | -310 | 0 | 0 |
| 4 | 400 | 1 | -310 |
| 5 | 300 | 1 | -310 |
| 6 | 100 | 1 | -310 |
| 7 | -200 | 2 | 100 |
| 8 | -400 | 2 | 100 |
| 9 | -500 | 2 | 100 |
+----+-------+----------+----------------+
What I have done so far is in 2 parts. First I use the LAST_VALUE function to get the last Value in each group. Then I have tried to use the LAG function to get the last value from the previous group. Unfortunately the second part of my code isn't working as desired.
Here is my code:
CREATE TABLE #temp
(
id int identity(1,1),
value int,
group_id int
)
INSERT #temp VALUES(-200,0)
INSERT #temp VALUES(-620,0)
INSERT #temp VALUES(-310,0)
INSERT #temp VALUES(400,1)
INSERT #temp VALUES(300,1)
INSERT #temp VALUES(100,1)
INSERT #temp VALUES(-200,3)
INSERT #temp VALUES(-400,3)
INSERT #temp VALUES(-500,3)
;WITH cte AS
(
SELECT
*,
LastValByGroup = LAST_VALUE(Value) OVER(Partition By group_id ORDER BY id
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
FROM
#temp
), lagged AS
(
SELECT
*,
LaggedLastValByGroup = LAG(LastValByGroup,1,0) OVER(Partition By group_id ORDER BY id)
FROM
cte
)
SELECT * FROM lagged ORDER BY id
DROP TABLE #temp
And this is the result I get:
+----+-------+----------+----------------+----------------------+
| id | value | group_id | LastValByGroup | LaggedLastValByGroup |
+----+-------+----------+----------------+----------------------+
| 1 | -200 | 0 | -310 | 0 |
| 2 | -620 | 0 | -310 | -310 |
| 3 | -310 | 0 | -310 | -310 |
| 4 | 400 | 1 | 100 | 0 |
| 5 | 300 | 1 | 100 | 100 |
| 6 | 100 | 1 | 100 | 100 |
| 7 | -200 | 3 | -500 | 0 |
| 8 | -400 | 3 | -500 | -500 |
| 9 | -500 | 3 | -500 | -500 |
+----+-------+----------+----------------+----------------------+
Any help is much appreciated.
Thanks
You can use first_value like following to get the desired result.
select distinct t2.*, ISNULL(FIRST_VALUE(t1.[value]) over(partition by t1.group_id order by t1.id desc), 0) LastValByGroup
from #data t1
right join #data t2 on t1.group_id + 1 = t2.group_id
Please find the db<>fiddle here.

SQL server multi-period comparison

I have the following table T1 (sample shown), which shows the category for each client (each with a unique ID) on a specific date and his category on the next date:
+------------+----------------+----------+---------------+
| DATE | ID | STAGE | STAGE_NEXT |
+------------+----------------+----------+---------------+
| 2014-07-01 | 10010101841033 | 1 | 1 |
| 2015-07-01 | 74610108542146 | 1 | 1 |
| 2014-10-01 | 47970108841775 | 3 | 3 |
| 2014-10-01 | 48870108841816 | 2 | 3 |
| 2014-10-01 | 32910097439541 | 1 | 1 |
| 2016-04-01 | 46930097440855 | 2 | 3 |
| 2016-04-01 | 47380097440931 | 2 | 3 |
| 2016-04-01 | 54560097441411 | 3 | 3 |
+------------+----------------+----------+---------------+
Table info:
- Rows: 513,000
- Date range: 2013-01-01 to 2019-10-01
- Stages: 1 - 3
I need to create a new column in T1, which will flag the date a client moved to Stage 1 if at any point he was in Stage 3. For example if we take 1 client from T1 by using this code:
SELECT [DATE], ID, STAGE, STAGE_NEXT
FROM T1
WHERE ID = '74610108542146'
ORDER BY [DATE]
We get the following result:
+------------+----------------+-------+------------+
| DATE | ID | STAGE | STAGE_NEXT |
+------------+----------------+-------+------------+
| 2015-07-01 | 74610108542146 | 1 | 1 |
| 2015-10-01 | 74610108542146 | 1 | 1 |
| 2016-01-01 | 74610108542146 | 1 | 2 |
| 2016-04-01 | 74610108542146 | 2 | 1 |
| 2016-07-01 | 74610108542146 | 1 | 1 |
| 2016-10-01 | 74610108542146 | 1 | 2 |
| 2017-01-01 | 74610108542146 | 2 | 3 |
| 2017-04-01 | 74610108542146 | 3 | 3 |
| 2017-07-01 | 74610108542146 | 3 | 2 |
| 2017-10-01 | 74610108542146 | 2 | 1 |
| 2018-01-01 | 74610108542146 | 1 | 1 |
| 2018-04-01 | 74610108542146 | 1 | NULL |
+------------+----------------+-------+------------+
After the new column with the flag is added to T1 we should be able to get the following result using this code on T1:
SELECT [DATE], ID, STAGE, STAGE_NEXT, FLAG
FROM T1
WHERE ID = '74610108542146'
ORDER BY [DATE]
+------------+----------------+-------+------------+------+
| DATE | ID | STAGE | STAGE_NEXT | FLAG |
+------------+----------------+-------+------------+------+
| 2015-07-01 | 74610108542146 | 1 | 1 | 0 |
| 2015-10-01 | 74610108542146 | 1 | 1 | 0 |
| 2016-01-01 | 74610108542146 | 1 | 2 | 0 |
| 2016-04-01 | 74610108542146 | 2 | 1 | 0 |
| 2016-07-01 | 74610108542146 | 1 | 1 | 0 |
| 2016-10-01 | 74610108542146 | 1 | 2 | 0 |
| 2017-01-01 | 74610108542146 | 2 | 3 | 0 |
| 2017-04-01 | 74610108542146 | 3 | 3 | 0 |
| 2017-07-01 | 74610108542146 | 3 | 2 | 0 |
| 2017-10-01 | 74610108542146 | 2 | 1 | 1 |
| 2018-01-01 | 74610108542146 | 1 | 1 | 0 |
| 2018-04-01 | 74610108542146 | 1 | NULL | 0 |
+------------+----------------+-------+------------+------+
If the client never moved to Stage 3 then the flag for the client is always 0
You could calculate and update the new FLAG column from a CTE.
The update statement uses the LAG function to use the previous STAGE in the calculation of FLAG.
;WITH CTE AS
(
SELECT ID, [DATE], FLAG,
CASE
WHEN STAGE = 2
AND STAGE_NEXT = 1
AND LAG(STAGE) OVER (PARTITION BY ID ORDER BY IIF(STAGE=2 AND STAGE_NEXT=2,0,1), [DATE]) = 3
THEN 1
ELSE 0
END AS CalcFlag
FROM T1
WHERE ID = '10010101841033' -- optional, to target only 1 ID
)
UPDATE CTE
SET FLAG = CalcFlag
WHERE (FLAG IS NULL OR FLAG != CalcFlag);
The IIF(STAGE=2 AND STAGE_NEXT=2,0,1) in the LAG is used to make the calculation also work when the stage 2 is repeated.
Test it on rextester here
Try this,
DECLARE #T1 table
(
[DATE] date,ID numeric(18,0),STAGE int,STAGE_NEXT int
)
INSERT INTO #T1 VALUES
('2013-01-01',10010101841033,1,1 ),
('2013-04-01',10010101841033,1,3 ),
('2013-07-01',10010101841033,3,3 ),
('2013-10-01',10010101841033,3,2 ),
('2014-01-01',10010101841033,2,1 ),
('2014-04-01',10010101841033,1,1 ),
('2014-07-01',10010101841033,1,1 ),
('2014-10-01',10010101841033,1,NULL),
('2014-07-01',47820108841771,1,2)
SELECT A.DATE,A.ID,A.STAGE,A.STAGE_NEXT,
CASE WHEN B.ID IS NOT NULL AND (STAGE_NEXT=1 AND STAGE>STAGE_NEXT) THEN 1 ELSE 0 END AS FLAG
FROM #T1 A
LEFT JOIN
(
SELECT DISTINCT ID AS ID
FROM #T1
WHERE STAGE_NEXT=3
)B
ON A.ID=B.ID

Creating a conditonal ROW_NUMBER() Partition clause based on previous row value

I have a table that looks like this:
+----------------+--------+
| EvidenceNumber | ID |
+----------------+--------+
| 001 | 8 |
| 001.A | 8 |
| 001.A.01 | 8 |
| 001.A.02 | 8 |
| 001.B | 8 |
| 001.C | 8 |
| 001.D | 8 |
| 001.E | 8 |
| 001.F | 8 |
| 001.G | 8 |
| 001.G.01 | 8 |
+----------------+--------+
If 001 were a bag, inside of it was 001.A, 001.B, and so on through to 001.G
In the output above, 001.A was another bag, and that bag contained 001.A.01 and 001.A.02. The same thing can be seen with 001.G.01.
Every entry in this table is either a bag or an item. I am only interested in counting the amount of items per ID.
Since 001.A.01 and 001.A.02 is the last we see of the "001.A's" we know A.01 and A.02 were items.
Since we see 001.B only once, that was an item as well.
001.G was a bag, but 001.G.01 was an item.
The above output is showing 8 items and 3 bags.
I feel like Row_number and the Partition clause is the perfect tool for the job, but I can't find a way to partition based on a clause that uses a previous row's value.
Maybe something like that isn't even necessary here, but I pictured it like:
{001} -- variable
{001}.A -- variable seen again, obviously 001 was a bag. Create new variable {001.A} and move on.
{001.A}.01 -- same thing.
{001.A.01} -- Unique variable. This is a final step. This is a bag and should be Row number 1.
Obviously, the below code is just making "ItemNum" 1 for each item since there are not duplicates.
SELECT
ROW_NUMBER() OVER(Partition BY EvidenceNumber ORDER BY EvidenceNumber) AS ItemNum,
EvidenceNumber,
ID
FROM EVIDENCE
WHERE ID = '18'
ORDER BY EvidenceNumber
+---------+----------------+--------+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+--------+
| 1 | 001 | 8 |
| 1 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 1 | 001.A.02 | 8 |
| 1 | 001.B | 8 |
| 1 | 001.C | 8 |
| 1 | 001.D | 8 |
| 1 | 001.E | 8 |
| 1 | 001.F | 8 |
| 1 | 001.G | 8 |
| 1 | 001.G.01 | 8 |
+---------+----------------+--------+
Ideally, it would partition on the items only, so in this case:
+---------+----------------+----+
| ItemNum | EvidenceNumber | ID |
+---------+----------------+----+
| 0 | 001 | 8 |
| 0 | 001.A | 8 |
| 1 | 001.A.01 | 8 |
| 2 | 001.A.02 | 8 |
| 3 | 001.B | 8 |
| 4 | 001.C | 8 |
| 5 | 001.D | 8 |
| 6 | 001.E | 8 |
| 7 | 001.F | 8 |
| 0 | 001.G | 8 |
| 8 | 001.G.01 | 8 |
+---------+----------------+----+
I don't think window functions alone are the best approach. Instead:
select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t ;
Then sum these up using another subquery:
select t.*,
sum(is_item) over (partition by caseid order by EvidenceNumber) as item_counter
from (select t.*,
(case when exists (select 1
from evidence t2
where t2.caseid = t.caseid and
t2.EvidenceNumber like t.EvidenceNumber + '.%'
)
then 0 else 1
end) as is_item
from evidence t
) t;
trick with Lead and Row_Number:
DECLARE #Table TABLE (
EvidenceNumber varchar(64),
Id int
)
INSERT INTO #Table VALUES
('001',8),
('001.A',8),
('001.A.01',8),
('001.A.02',8),
('001.B',8),
('001.C',8),
('001.D',8),
('001.E',8),
('001.F',8),
('001.G',8),
('001.G.01',8);
WITH CTE AS (
SELECT
[IsBag] = PATINDEX(EvidenceNumber+'%',
IsNull(LEAD(EvidenceNumber) OVER (ORDER BY EvidenceNumber),0)
),
[EvidenceNumber],
[Id]
FROM
#Table
)
SELECT
[NumItem] = IIF(IsBag = 0,ROW_NUMBER() OVER (PARTITION BY [ISBag] order by [IsBag]),0),
[EvidenceNumber],
[Id]
FROM
CTE
ORDER BY EvidenceNumber

Update first table based on values from second table

I have two tables:
- #CAMERC
- #CAMERC_LOG
I have to update column #CAMERC.MERC_LPR with values from column #CAMERC_LOG.MERC_LPR.
Records must match on MERC_KEY, but only one record must be taken from #CAMERC_LOG - with highest MERC_KEY_LOG, and #CAMERC_LOG.MERC_LPR must not be null or 0.
My problem is updating one table based on results from second table. I don't know how to properly make such an update?
Table #CAMERC:
+----------+----------+
| MERC_KEY | MERC_LPR |
+----------+----------+
| 1 | 0.0000 |
| 2 | NULL |
| 3 | 0.0000 |
| 4 | 0.0000 |
+----------+----------+
Table #CAMERC_LOG:
+----------+--------------+----------+
| MERC_KEY | MERC_KEY_LOG | MERC_LPR |
+----------+--------------+----------+
| 1 | 1 | 1.1000 |
| 1 | 2 | 2.3000 |
| 2 | 3 | 3.4000 |
| 2 | 4 | 4.4000 |
| 1 | 5 | 7.8000 |
| 1 | 6 | NULL |
| 2 | 7 | 0.0000 |
| 2 | 8 | 12.4000 |
| 3 | 1 | 12.1000 |
| 3 | 2 | 42.3000 |
| 3 | 3 | 43.4000 |
| 3 | 4 | 884.4000 |
| 4 | 5 | 57.8000 |
| 4 | 6 | NULL |
| 4 | 7 | 0.0000 |
| 4 | 8 | 412.4000 |
+----------+--------------+----------+
Code for table creation:
DECLARE #CAMERC TABLE
(
MERC_KEY INT,
MERC_LPR DECIMAL(10,4)
)
DECLARE #CAMERC_LOG TABLE
(
MERC_KEY INT,
MERC_KEY_LOG INT,
MERC_LPR DECIMAL(10,4)
)
INSERT INTO #CAMERC(MERC_LPR, MERC_KEY) VALUES(0, 1),(NULL,2),(0,3),(0,4)
INSERT INTO #CAMERC_LOG(MERC_LPR, MERC_KEY, MERC_KEY_LOG) VALUES(1.1, 1,1),(2.3,1,2),(3.4,2,3),(4.4,2,4),(7.8, 1,5),(NULL,1,6),(0,2,7),(12.4,2,8),
(12.1, 3,1),(42.3,3,2),(43.4,3,3),(884.4,3,4),(57.8, 4,5),(NULL,4,6),(0,4,7),(412.4,4,8)
Try this:
WITH DataSource AS
(
SELECT MERC_KEY
,ROW_NUMBER() OVER (PARTITION BY MERC_KEY ORDER BY MERC_KEY_LOG DESC) AS [RowID]
,MERC_LPR
FROM #CAMERC_LOG
WHERE MERC_LPR IS NOT NULL
AND MERC_LPR <> 0
)
UPDATE #CAMERC
SET MERC_LPR = B.[MERC_LPR]
FROM #CAMERC A
INNER JOIN DataSource B
ON A.[MERC_KEY] = B.[MERC_KEY]
AND B.[RowID] = 1
SELECT *
FROM #CAMERC
The idea is to eliminated the invalid records from the #CAMER_LOG and then using ROW_NUMBER to order the rows by MERC_KEY_LOG. After that, we are performing UPDATE by only where RowID = 1.

Adding a count column in SQL Server for groups of records

I am trying to update an existing table with an individual count of the record on each row in a count column.
The table has the following columns that need to be incremented:
MBR_NO, CLAIM_N0, Effective_Dt, incr_count
So a sample might look like this before the run:
MBR_NO | CLAIM_N0 | Effective_Dt | incr_count |
-------+----------+----------------+------------+
1 | 2 | 1/1/2015 | NULL |
1 | 4 | 5/5/2015 | NULL |
1 | 5 | 6/7/2016 | NULL |
1 | 7 | 8/7/2016 | NULL |
2 | 2 | 4/3/2015 | NULL |
2 | 5 | 5/21/2015 | NULL |
3 | 8 | 3/27/2015 | NULL |
I want to count by MBR_NO and update the Incr_count to look like this:
MBR_NO | CLAIM_N0 | Effective_Dt | incr_count |
-------+----------+----------------+------------+
1 | 2 | 1/1/2015 | 1 |
1 | 4 | 5/5/2015 | 2 |
1 | 5 | 6/7/2016 | 3 |
1 | 7 | 8/7/2016 | 4 |
2 | 2 | 4/3/2015 | 1 |
2 | 5 | 5/21/2015 | 2 |
3 | 8 | 3/27/2015 | 1 |
I need to change that filed for processing later on.
I know this is not that complex but It seemed that the other topics offered solutions that don't incrementally update. Any help would be appreciated.
You could just do this in a query with
ROW_NUMBER() OVER (PARTITION BY MBR_NO ORDER BY Effective_DT).
but does it matter if the number changes? i.e. in your example if you had
MBR_NO EffectiveDate RowNumber
------------------------------------
2 1/1/2017 1
2 5/1/2017 2
but if you inserted a row with an effective date of say 3/1/2017 it would change the row number for the 5/1/2017 row i.e.
MBR_NO EffectiveDate RowNumber
------------------------------------
2 1/1/2017 1
2 3/1/2017 2
2 5/1/2017 3
You can query as below:
Select MBR_NO, CLAIM_N0, Effective_Dt,
incr_count = count(MBR_NO) over(Partition by MBR_NO order by Effective_Dt)
from yourtable
Output as below:
+--------+----------+--------------+------------+
| MBR_NO | CLAIM_N0 | Effective_Dt | incr_count |
+--------+----------+--------------+------------+
| 1 | 2 | 2015-01-01 | 1 |
| 1 | 4 | 2015-05-05 | 2 |
| 1 | 5 | 2016-06-07 | 3 |
| 1 | 7 | 2016-08-07 | 4 |
| 2 | 2 | 2015-04-03 | 1 |
| 2 | 5 | 2015-05-21 | 2 |
| 3 | 8 | 2015-03-27 | 1 |
+--------+----------+--------------+------------+

Resources