SQL SERVER Update and Insert to different table that has duplicate value - sql

I have two tables, Table A and B. I want to update the RAM_PARENT_CATEGORY_CODE column of Table A with the value from Table B. However, for category_alert_ctr 55 and 82. There are 2 possible RAM_PARENT_CATEGORY_CODE from Table B.
I want to insert a new row in Table A to accommodate both of those RAM_PARENT_CATEGORY_CODE (CRM1b and crm1c). Then update the value with the correct value from Table B.
So the expected result will be :
55 | crm1b1 | CRM1 | CRM1b
83 | crm1b1 | CRM1 | crm1c
82 | Area1 | BuildApp | OFC01
84 | Area1 | BuildApp | OFC02
Do you have any idea to do this? Thanks.

Use the MERGE statement to insert such rows from Table B that are not in Table A, and update those that are in Table A.To check, click here.
with
a as (
select
*,
row_number() over(
partition by category_alert_ctr order by (select 0)
) rn
from #A
),
b as (
select
*,
row_number() over(
partition by RAM_CATEGORY_CODE, RAM_GROUP
order by RAM_PARENT_CATEGORY_CODE
) rn,
row_number() over(
order by RAM_CATEGORY_CODE, RAM_PARENT_CATEGORY_CODE
) cac_offset
from #B
),
m as (
select
a.category_alert_ctr,
iif(b.rn = 1 and a.category_alert_ctr is not null,
a.category_alert_ctr,
max(a.category_alert_ctr) over() + b.cac_offset) new_cac,
b.RAM_CATEGORY_CODE, b.RAM_GROUP, b.RAM_PARENT_CATEGORY_CODE, b.rn
from b
left join a
on b.RAM_CATEGORY_CODE = a.ram_category_code and
b.RAM_GROUP = a.RAM_GROUP and b.rn = a.rn
)
merge a
using m
on a.RAM_CATEGORY_CODE = m.RAM_CATEGORY_CODE and
a.RAM_GROUP = m.RAM_GROUP and a.rn = m.rn
when matched then
update set
a.category_alert_ctr = m.new_cac,
a.RAM_PARENT_CATEGORY_CODE = m.RAM_PARENT_CATEGORY_CODE
when not matched then
insert(category_alert_ctr, ram_category_code,
RAM_GROUP, RAM_PARENT_CATEGORY_CODE)
values(m.new_cac, m.RAM_CATEGORY_CODE,
m.RAM_GROUP, m.RAM_PARENT_CATEGORY_CODE)
;

to handle multiple matches, use row_number() to generate a unique numbering for matching
UPDATE A
SET RAM_PARENT_CATEGORY_CODE = B.RAM_PARENT_CATEGORY_CODE
FROM (
SELECT *, RN = ROW_NUMBER() OVER (PARTITION BY RAM_CATEGORY_CODE, RAM_GROUP
ORDER BY RAM_CATEGORY_CODE)
FROM TABLEA
) A
INNER JOIN (
SELECT *, RN = ROW_NUMBER() OVER (PARTITION BY RAM_CATEGORY_CODE, RAM_GROUP
ORDER BY RAM_CATEGORY_CODE)
FROM TABLEB
) B
ON A.RAM_CATEGORY_CODE = B.RAM_CATEGORY_CODE
AND A.RAM_GROUP = B.RAM_GROUP
AND A.RN = B.RN

If necessary to update the category_alert_ctr column.To check, click here.
declare #A table(
category_alert_ctr int,
ram_category_code varchar(30),
RAM_GROUP varchar(30),
RAM_PARENT_CATEGORY_CODE varchar(30)
);
insert #A values
(25, 'HLTHLIC_FF', 'HEALTHLIC', null),
(28, 'M16', 'QBL', null),
(55, 'crm1b1', 'CRM1', null),
(55, 'crm1b1', 'CRM1', null),
(82, 'Area1', 'BuildApp', null),
(82, 'Area1', 'BuildApp', null);
declare #B table(
RAM_CATEGORY_CODE varchar(30),
RAM_GROUP varchar(30),
RAM_PARENT_CATEGORY_CODE varchar(30)
);
insert #B values
('HLTHLIC_FF', 'HEALTHLIC', 'HLTHLC_RST'),
('M16', 'QBL', 'EPA'),
('crm1b1', 'CRM1', 'CRM1b'),
('crm1b1', 'CRM1', 'crm1c'),
('Area1', 'BuildApp', 'OFC01'),
('Area1', 'BuildApp', 'OFC02');
with
a as (
select
*,
iif(
row_number() over(
partition by category_alert_ctr order by (select 0)
) = 1,
category_alert_ctr,
max(category_alert_ctr) over() +
row_number() over(order by category_alert_ctr) -
dense_rank() over(order by category_alert_ctr)
) new_category_alert_ctr,
row_number() over(
partition by category_alert_ctr order by (select 0)
) rn
from #A
),
b as (
select
*,
row_number() over(
partition by RAM_CATEGORY_CODE, RAM_GROUP
order by RAM_PARENT_CATEGORY_CODE
) rn
from #B
)
update a
set a.category_alert_ctr = a2.new_category_alert_ctr,
a.RAM_PARENT_CATEGORY_CODE = b.RAM_PARENT_CATEGORY_CODE
from a
join a a2
on a.category_alert_ctr = a2.category_alert_ctr and a.rn = a2.rn
join b
on a.RAM_CATEGORY_CODE = a.RAM_CATEGORY_CODE and
a.RAM_GROUP = b.RAM_GROUP and a.rn = b.rn
;
select * from #A;

Related

Group by with gap in date sequence ("gaps and islands")

I am trying to solve a "gaps and islands" by date issue I'm facing (kudos to Gordon Linoff helping me identify this issue). I want to group the below table by person, office and job while respecting order by person,from_date. consider the table below:
declare #temp table(person varchar(25),office varchar(25),job varchar(25),from_date date,to_date date)
insert into #temp values ('jon','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('jon','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('jon','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('jon','ny','programmer','1/8/2020','1/9/2020');
insert into #temp values ('lou','ny','programmer','1/1/2020','1/3/2020');
insert into #temp values ('lou','ny','programmer','1/4/2020','1/5/2020');
insert into #temp values ('lou','dc','programmer','1/6/2020','1/7/2020');
insert into #temp values ('lou','ny','programmer','1/8/2020','1/9/2020');
the intended output is
This is a type of gaps-and-islands problem. If there are no gaps in the dates, the simplest solution is the difference of row numbers:
select person, office, job, min(from_date), max(to_date)
from (select t.*,
row_number() over (partition by person, office, job order by from_date) as seqnum,
row_number() over (partition by person, office order by from_date) as seqnum_2
from t
) t
group by person, office, job, (seqnum - seqnum_2)
This is a general solution:
WITH preceders_and_followers AS (
SELECT
b.person,
b.office,
b.job,
b.from_date,
b.to_date,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.from_date - 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_preceder,
CASE
WHEN EXISTS (
SELECT
c.*
FROM
ora$ptt_tmp c
WHERE
b.person = c.person
AND b.office = c.office
AND b.job = c.job
AND ( b.to_date + 1 BETWEEN c.from_date AND c.to_date )
) THEN
1
END AS has_follower
FROM
ora$ptt_tmp b
ORDER BY
1,
2,
3
)
SELECT DISTINCT
pf1.person,
pf1.office,
pf1.job,
pf1.from_date,
(
SELECT
MIN(pf2.to_date)
FROM
preceders_and_followers pf2
WHERE
pf1.person = pf2.person
AND pf1.office = pf2.office
AND pf1.job = pf2.job
AND pf2.to_date >= pf1.from_date
AND has_follower IS NULL
) to_date
FROM
preceders_and_followers pf1
WHERE
pf1.has_preceder IS NULL
ORDER BY
1,
4,
2,
3;

Basic code optimization, exclusion of double grouping

I want to show only one row per column (PN) from result of calculations base on 2 indicators (WK) and (Prio), and I think that doing this in way below using double grouping is... stupid, but I see no other solution. Is there other way to reach same result as query below?
CREATE TABLE #table
(
[PN] varchar(3) null
,[WK] int null
,[Prio] int null
);
INSERT INTO #table
(
[PN]
,[WK]
,[Prio]
)
VALUES
('AAA',37,1)
,('AAA',37,2)
,('AAA',38,3)
,('BBB',39,1)
,('BBB',39,2)
,('BBB',37,3)
,('BBB',38,4)
,('CCC',null,1)
,('CCC',null,2)
,('CCC',37,3)
,('CCC',38,4);
SELECT GTG.[PN]
,GTG.[WK]
,MIN([Prio]) [Prio]
FROM
(
SELECT [PN]
,MIN([WK]) [WK]
FROM #table
GROUP BY [PN]
) GTG
LEFT JOIN #table TMP
ON GTG.[PN] = TMP.[PN]
and GTG.[WK] = TMP.[WK]
GROUP BY GTG.[PN],GTG.[WK];
DROP TABLE #table;
Try using a Common Table Expression (CTE) with ROW_NUMBER():
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE MyTable
(
[PN] varchar(3) null
,[WK] int null
,[Prio] int null
);
INSERT INTO MyTable
(
[PN]
,[WK]
,[Prio]
)
VALUES
('AAA',37,1)
,('AAA',37,2)
,('AAA',38,3)
,('BBB',39,1)
,('BBB',39,2)
,('BBB',37,3)
,('BBB',38,4)
,('CCC',null,1)
,('CCC',null,2)
,('CCC',37,3)
,('CCC',38,4);
Query 1:
;WITH CTE
AS
( SELECT [PN],
[WK],
[Prio],
ROW_Number() OVER (Partition BY [PN] ORDER BY COALESCE([WK], 999), [Prio]) AS RN
FROM MyTable T1
)
SELECT [PN], [WK], [Prio]
FROM CTE
WHERE RN = 1
Results:
| PN | WK | Prio |
|-----|----|------|
| AAA | 37 | 1 |
| BBB | 37 | 3 |
| CCC | 37 | 3 |
do you find below
SELECT GTG.[PN]
,min(GTG.[WK])
,MIN([Prio]) [Prio]
FROM
#table GTG
LEFT JOIN #table TMP
ON GTG.[PN] = TMP.[PN]
and GTG.[WK] = TMP.[WK]
GROUP BY GTG.[PN]
You seem to want window functions. This should do what you want:
select t.*
from (select t.*, row_number() over (partition by pn order by coalesce(wk, 999), prio) as seqnum
from #table t
) t
where seqnum = 1;
Here is a db<>fiddle.
Note: wk possibly represents a week value, so I replaced it with 999 when it is NULL for the sorting to be correct. You could filter out NULL values or use a CASE expression if COALESCE() does not quite meet your needs.
I think, we can use Row_Number window function to sort result first and then apply where clause. Please try this-
;with cte as (
select
pn,
wk,
prio,
row_number() over (
partition by pn
order by (case when wk is not null then 0 else 1 end), wk, prio
) as rankid
from #table
)
select pn,wk,prio
from cte
where rankid =1;

Insert last not null value in temp table by date

I have this table for testing:
CREATE TABLE #ExchRates
(
[TimeId] int,
[CurrencyId] INT,
[ExchRate] DECIMAL(30,6)
)
INSERT INTO #ExchRates ([TimeId], [CurrencyId], [ExchRate])
VALUES
(
2017030500,
3,
6.142911
),
(
2017030600,
3,
6.152911
),
(
2017030700,
3,
NULL
),
(
2017030800,
3,
5.5
)
;
I want to insert values from this table in other table for one particular day(TimeId BETWEEN GETUTCDATE()-1 AND GETUTCDATE). Problem is when ExchRate is not set (NULL in table #ExchRate). In that case I want to use last known ExchRate for that currency. How can I solve this problem?
Try this-
SELECT * FROM(
SELECT *, ROW_NUMBER() OVER (ORDER BY TimeID DESC) RN
FROM #ExchRates
WHERE ExchRate IS NOT NULL
) A WHERE RN = 1
If you have more than one currency in the table, you can do this following -
SELECT * FROM(
SELECT *, ROW_NUMBER() OVER (PARTITION BY CurrencyId ORDER BY TimeID DESC) RN
FROM #ExchRates
WHERE ExchRate IS NOT NULL
) A WHERE RN = 1
for the case of null you can use row_number() for getting the last value
select * from (select *,row_number() over(partition by CurrencyId order by TimeId desc) rn
from #ExchRates
) a where a.rn=1
Here's your query.
insert into Table2 ([TimeId], [CurrencyId], [ExchRate])
select ([TimeId], [CurrencyId], [ExchRate]),
isnull([ExchRate], (select top 1 [ExchRate] from #ExchRates order by [TimeId] desc)) from #ExchRates
Use ROW_NUMBER() to get the last record you want :
WITH CTE AS (
SELECT *,ROW_NUMBER() OVER (PARTITION BY CurrencyId ORDER BY TimeId DESC) rn
FROM #ExchRates )
SELECT
*
FROM CTE
WHERE rn = 1;

SQL Row_Number() increase only when the value = 1 for a specific column

I am having some trouble generating row_number() in my SQL query as my expectation. I have this following output of my query-
Now, I want to add row number for all rows where row number will only increase when the value in C1 is = 1. Required output as below-
Any help will be appreciated. TIA
Table Variable:
DECLARE #Table AS TABLE (C1 INT)
INSERT INTO #Table VALUES (1),(4),(1),(1),(4),(1),(3),(4)
SQL 2008 Version
;WITH cteSimulateAnOriginalIdentityKey AS (
SELECT
C1
,OriginalOrder = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Table
)
, cteC1RowNumber AS (
SELECT
*
,C1RowNumber = ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY OriginalOrder)
FROM
cteSimulateAnOriginalIdentityKey
)
SELECT
C1
,RN = ISNULL((SELECT MAX(C1RowNumber) FROM cteC1RowNumber r2 WHERE r2.C1 = 1 AND r2.OriginalOrder <= r1.OriginalOrder),1)
FROM
cteC1RowNumber r1
ORDER BY
OriginalOrder
SQL 2012+ version
;WITH cteSimulateAnOriginalIdentityKey AS (
SELECT
C1
,OriginalOrder = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
#Table
)
, cteC1RowNumber AS (
SELECT
*
,C1RowNumber = ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY OriginalOrder)
FROM
cteSimulateAnOriginalIdentityKey
)
SELECT
C1
,RN = ISNULL(MAX(CASE WHEN C1 = 1 THEN C1RowNumber END) OVER (ORDER BY OriginalOrder),1)
FROM
cteC1RowNumber
ORDER BY
OriginalOrder
RESULT:
C1 RN
1 1
4 1
1 2
1 3
4 3
1 4
3 4
4 4
If you in fact have another column by which to maintain the desired original order you don't need the first cte which is simply simulating that column
Try this:
SELECT C1,
ROW_NUMBER() OVER (PARTITION BY C1 ORDER BY (SELECT 100)) RN
FROM TableNAme
I presume you have another column(s) in your query by which you determine the order of rows; without such a criteria, your whole question is pointless.
The query below will work on SQL Server 2012 or later versions:
declare #Table table (
Id int identity(1,1) not null,
C1 int
);
insert into #Table(C1) values (1),(4),(1),(1),(4),(1),(3),(4);
select t.C1,
sum(case t.C1 when 1 then 1 else 0 end) over(order by t.Id) as [RN]
from #Table t;

How to fetch records from two tables without common column using CTE

Users table details
userid values (abc,xyz,abc,sdf)
master table details
(mid,priority)values(101,1),(102,2),(101,1),(103,1)
i need to count of mid based on userid (userid is names of users) group by priority(priority is int ) grouping like case priority =1 then 'Open', priority =2 then 'closed' etc using CTE(common table expression)
Select * from users
userid
abc
xyz
abc
sdf
Select * from master
mid Priority
101 1
102 2
101 1
103 1
(Priority 1= Open 2=Closed)
OUTPUT expected:
Userid count(mid) Priority
abc 2 Open
xyz 1 Closed
sdf 1 Open
Try this:
use db_test;
go
drop table dbo.users;
create table dbo.users
(
userid varchar(max) not null
)
;
insert into dbo.users
values
('abc'),
('xyz'),
('sdf')
create table dbo.master
(
mid int not null,
Priority int not null
)
;
insert into dbo.master
values
(101, 1),
(102, 2),
(101, 1),
(103, 1)
;
with cte1 as (
select userid, row_number() over(order by userid asc) as rn
from dbo.users
), cte2 as (
select mid, priority, dense_rank() over(order by mid asc) as rn
from dbo.master
)
select a.userid, count(*) as [count(mid)], b.priority
from cte1 a join cte2 b on a.rn = b.rn
group by a.userid, b.priority