Get value of two columns when pivoting - sql-server

I have a table that has entries like
Acct Nurse EntryDateTime DBCode Answer FormSeq
123 Sally 9/8/2020 09:22 Code1 Ans1 0001
123 Jim 9/8/2020 10:25 Code1 Ans2 0001
123 Sally 9/8/2020 09:15 Code2 C2Ans1 0001
I have a query that is pivoting this to get the answer from the last entry based on DBCode and EntryDateTime that works great. What I need to do is get the NURSE as well as the answer.
So my row would be
Acct Code1 Code1Nurse Code2 Code2Nurse
123 Ans2 Jim C2Ans1 Sally
Is there a way to do this? I would need the nurse for each unique DBCode
Here is my pivot code:
SELECT * FROM (
SELECT
[AcctNumber],
[Answer],
[DBCode],
[EntryDate],
[FormCode],[FormSeq]
FROM V_FAC_MULTIAPP_FORM_WITH_HOURLY
) MultiApp
PIVOT (
MAX( [Answer])
FOR [DBCode]
IN (
[AST],
[SDRM],
[SDRF],[SDAAS],[SDDCT],[SDDAS],[SDABY],[SDDAT],[SDTRCC],[SDADMTW],[Prptic],[Pdcrt]
)
) AS PivotTable WHERE EntryDate >='8/15/2020' and FormCode='LL003' ORDER BY EntryDate

You could use conditional aggregation.
Data
drop table if exists #tTEST;
go
select * INTO #tTEST from (values
(123, 'Sally', '9/8/2020 09:22', 'Code1', 'Ans1', '0001'),
(123, 'Jim', '9/8/2020 10:25', 'Code1', 'Ans2', '0001'),
(123, 'Sally', '9/8/2020 09:15', 'Code2', 'C2Ans1', '0001')) V(Acct, Nurse, EntryDateTime, DBCode, Answer, FormSeq);
Query
;with rn_cte as (
select *, row_number() over (partition by DBCode order by EntryDateTime desc) rn
from #tTEST)
select Acct,
max(case when DBCode='Code1' and rn=1 then Answer else null end) Code1,
max(case when DBCode='Code1' and rn=1 then Nurse else null end) Code1Nurse,
max(case when DBCode='Code2' and rn=1 then Answer else null end) Code2,
max(case when DBCode='Code2' and rn=1 then Nurse else null end) Code2Nurse
from rn_cte
group by Acct;
Output
Acct Code1 Code1Nurse Code2 Code2Nurse
123 Ans2 Jim C2Ans1 Sally

Here is something to play with. It would take some tinkering, but you could make the query dynamic to build out the columns to select based upon the DBCodes you have in your table...
IF OBJECT_ID('tempdb..#V_FAC_MULTIAPP_FORM_WITH_HOURLY') IS NOT NULL
DROP TABLE #V_FAC_MULTIAPP_FORM_WITH_HOURLY;
CREATE TABLE #V_FAC_MULTIAPP_FORM_WITH_HOURLY
(
Acct INT,
Nurse VARCHAR(20),
EntryDateTime DATETIME,
DBCode VARCHAR(10),
Answer VARCHAR(10),
FormSeq VARCHAR(10)
)
INSERT #V_FAC_MULTIAPP_FORM_WITH_HOURLY
VALUES
(123,'Sally','9/8/2020 09:22','Code1','Ans1','0001'),
(123,'Jim','9/8/2020 10:25','Code1','Ans2','0001'),
(123,'Sally','9/8/2020 09:15','Code2','C2Ans1','0001');
WITH Top_Row_Per_DBCode_By_EntryDate AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY DBCode ORDER BY EntryDateTime DESC) AS top_row,
Acct,
DBCode,
Nurse+':'+Answer AS Answer
FROM #V_FAC_MULTIAPP_FORM_WITH_HOURLY
), Filtered_CTE AS
(
SELECT *
FROM Top_Row_Per_DBCode_By_EntryDate
WHERE top_row = 1
)
SELECT Acct,
Substring([Code1],CHARINDEX(':',[Code1])+1,LEN([Code1])-CHARINDEX(':',[Code1])) AS Code1,
Substring([Code1],0,CHARINDEX(':',[Code1])) AS Code1Nurse,
Substring([Code2],CHARINDEX(':',[Code2])+1,LEN([Code2])-CHARINDEX(':',[Code2])) AS Code2,
Substring([Code2],0,CHARINDEX(':',[Code2])) AS Code2Nurse
FROM Filtered_CTE
PIVOT
(
MAX( [Answer]) FOR [DBCode]
IN ([Code1],[Code2])
) AS PivotTable

Related

Update gaps in sequential table

I have a table that contains employee bank data
Employee |Bank |Date |Delta
---------------------------------------------------
Smith |Vacation |2023-01-01 |15.0
Smith |Vacation |2023-01-02 |Null
Smith |Vacation |2023-01-03 |Null
Smith |Vacation |2023-01-04 |7.5
I would like to write a statement so that I can update 2023-01-02 and 2023-01-03 with the Delta value from January 1. Essentially, I want to use the value from the most recent row that isn't > than the date on the row.
Once complete, I want the table to look like this:
Employee |Bank |Date |Delta
---------------------------------------------------
Smith |Vacation |2023-01-01 |15.0
Smith |Vacation |2023-01-02 |15.0
Smith |Vacation |2023-01-03 |15.0
Smith |Vacation |2023-01-04 |7.5
The source table has a unique index consisting of Employee, Bank and Date descending. There could be up to 2 billion rows in the table.
I currently update the table with the following, but I am wondering if there is a more efficient way to do so?
WITH cte_date
AS (SELECT dd.date_key,
db.balance_key,
feb.employee_key
FROM shared.dim_date dd
CROSS JOIN
(
SELECT DISTINCT
employee_key
FROM wfms.fact_employee_balance
) feb
CROSS JOIN wfms.dim_balance db
WHERE dd.date BETWEEN DATEFROMPARTS(DATEPART(YY, GETDATE()) - 2, 12, 31) AND GETDATE())
SELECT dd.*,
t.delta
INTO wfms.test2
FROM cte_date dd
LEFT JOIN wfms.test1 t ON dd.balance_key = t.balance_key
AND dd.employee_key = t.employee_key
AND t.date_key = (SELECT TOP 1 tt1.date_key
FROM wfms.test1 tt1
WHERE tt1.balance_key = t.balance_key
AND tt1.employee_key = t.employee_key
AND tt1.date_key < dd.date_key);
Just for fun, I wanted to test an idea.
For the moment, lets assume the gaps are not too wide ... In this example 7 days.
On a relative to batch, the lag() over() approach was 22% while the Cross Apply was 78%.
Again, Just for fun
Select Employee
,Bank
,Date
,Delta = coalesce(A.Delta
,lag(Delta,1) over (partition by Employee,Bank order by date)
,lag(Delta,2) over (partition by Employee,Bank order by date)
,lag(Delta,3) over (partition by Employee,Bank order by date)
,lag(Delta,4) over (partition by Employee,Bank order by date)
,lag(Delta,5) over (partition by Employee,Bank order by date)
,lag(Delta,6) over (partition by Employee,Bank order by date)
,lag(Delta,7) over (partition by Employee,Bank order by date)
)
From YourTable A
Versus
Select Employee
,Bank
,Date
,Delta = coalesce(A.Delta,B.Delta)
From YourTable A
Cross Apply ( Select top 1 Delta
From YourTable
Where Employee=A.Employee
and A.Bank = Bank
and Delta is not null
and A.Date>=Date
Order By Date desc
) B
Update
Same results with 20 days
Here is another way. Using sum() with window function to find the group "Grp" of rows (1 row with not null with subsequent rows of null). Finally max(Delta) of the Grp to return the not null value.
select Employee, Bank, [Date], max (max(Delta))
over (partition by Employee, Bank, Grp)
from
(
select *, Grp = sum (case when Delta is not null then 1 else 0 end)
over (partition by Employee,Bank
order by [Date])
from YourTable
) t
group by Employee, Bank, [Date], Grp

Repeated data on inserted rows

--demo setup
drop table if exists dbo.product
go
create table dbo.Product
(
ProductId int,
ProductTitle varchar(55),
ProductCategory varchar(255),
Loaddate datetime
)
insert into dbo.Product
values (1, 'Table', 'ABCD', '3/4/2018'),
(1, 'Table', 'ABCD', '3/5/2018'),
(1, 'Table', 'ABCD', '3/6/2018'),
(1, 'Table', 'XYZ', '3/7/2018'),
(1, 'Table', 'XYZ', '3/8/2018'),
(1, 'Table', 'XYZ', '3/9/2018'),
(1, 'Table', 'GHI', '3/10/2018'),
(1, 'Table', 'GHI', '3/11/2018'),
(1, 'Table', 'XYZ', '3/12/2018'),
(1, 'Table', 'XYZ', '3/13/2018')
SELECT
product.productid,
product.producttitle,
product.productcategory,
MIN(product.loaddate) AS BeginDate,
-- ,max(product.LoadDate) as BeginDate1
CASE
WHEN MAX(product.loaddate) = MAX(oa.enddate1)
THEN '12/31/9999'
ELSE MAX(product.loaddate)
END AS EndDate
FROM
dbo.product product
CROSS APPLY
(SELECT MAX(subproduct.loaddate) EndDate1
FROM dbo.product subproduct
WHERE subproduct.productid = product.productid) oa
GROUP BY
productid, producttitle, productcategory
Output:
productid
producttitle
productcategory
BeginDate
EndDate
1
Table
ABCD
2018-03-04 00:00:00.000
2018-03-06 00:00:00.000
1
Table
XYZ
2018-03-07 00:00:00.000
9999-12-31 00:00:00.000
1
Table
GHI
2018-03-10 00:00:00.000
2018-03-11 00:00:00.000
Desired output:
productid
producttitle
productcategory
BeginDate
EndDate
1
Table
ABCD
2018-03-04 00:00:00.000
2018-03-06 00:00:00.000
1
Table
XYZ
2018-03-07 00:00:00.000
2018-03-09 00:00:00.000
1
Table
GHI
2018-03-10 00:00:00.000
2018-03-11 00:00:00.000
1
Table
XYZ
2018-03-12 00:00:00.000
9999-12-31 00:00:00.000
The last two inserted rows repeat the data from Loaddate '3/7/2018'-'3/9/2018', this doesn't happen if any of the new inserted rows doesn't repeat data. The only thing that changes is the LoadDate, giving me incorrect output. how can i get something like that desired output?
Well, first of all, you need to find a sequence number over all your records. If you already have a primary key, that's good. In example you gave us, there's no such column, so let's generate it.
Then, we make pairs with start and end dates for each product's category change. Another thing is to group all these product's category changes.
Finally, we make just a simple group by:
;
with cte as ( select *,
row_number() over(partition by ProductId order by Loaddate) as rn
from product
), cte2 as ( select t1.ProductId,
t1.ProductTitle,
t1.ProductCategory,
t1.Loaddate as BeginDate,
case
when t1.ProductCategory <> t2.ProductCategory
then t1.Loaddate
else coalesce(t2.Loaddate, null)
end as EndDate,
row_number() over(order by t1.ProductId, t1.Loaddate) as rn_overall,
row_number() over(partition by t1.ProductId, t1.ProductCategory order by t1.Loaddate) as rn_category
from cte as t1
left join cte as t2
on t2.ProductId = t1.ProductId
and t2.rn = t1.rn + 1
), cte3 as ( select *,
min(rn_overall) over (partition by ProductId, ProductCategory, rn_overall - rn_category) as product_group
from cte2
)
select ProductId, ProductTitle, ProductCategory,
min(BeginDate) as BeginDate,
case
when max(case when EndDate is null then 1 else 0 end) = 0
then max(EndDate)
else null
end as EndDate
from cte3
group by ProductId, ProductTitle, ProductCategory, product_group
order by ProductId, BeginDate

How do I add original amount of oldest transaction in an aggregate function?

I'm trying to find out how to include the original amount of the first transaction (oldest by Posted Date) to an aggregate query.
The following finds reversed transactions ..
SELECT DISTINCT
[Account], [Voucher],
[DocumentDate],
SUM([Amount])
FROM
MyTable
WHERE
[Account] = 'abc'
GROUP BY
[Account], [Voucher], [DocumentDate]
HAVING
SUM([Amount]) = 0
How would I include the amount in the results for the transaction with the oldest posted date for each record?
For example, using the following:
Account Voucher DocumentDate PostedDate Amount
---------------------------------------------------------
abc 1 01/01/2018 08/01/2018 100.00
abc 1 01/01/2018 15/01/2018 -100.00
The expected result would be:
Account Voucher DocumentDate OriginalAmount Sum(Amount) Records
-------------------------------------------------------------------------
abc 1 01/01/2018 100.00 0.00 2
One way to do it is using a cte with first_value, sum...over and count...over.
First, create and populate sample table (Please save us this step in your future questions)
DECLARE #T AS TABLE
(
Account char(3),
Voucher int,
DocumentDate date,
PostedDate date,
Amount numeric(5,2)
)
INSERT INTO #T VALUES
('abc', 1, '2018-01-01', '2018-01-08', 100),
('abc', 1, '2018-01-01', '2018-01-15', -100)
The cte:
;WITH CTE
AS
(
SELECT [Account],
[Voucher],
[DocumentDate],
FIRST_VALUE(Amount) OVER(PARTITION BY [Account], [Voucher], [DocumentDate] ORDER BY PostedDate) AS OriginalAmount,
SUM([Amount]) OVER(PARTITION BY [Account], [Voucher], [DocumentDate]) AS [Sum(Amount)],
COUNT(*) OVER(PARTITION BY [Account], [Voucher], [DocumentDate]) Records
FROM
#T
WHERE
[Account] = 'abc'
)
The query:
SELECT DISTINCT *
FROM CTE
WHERE [Sum(Amount)] = 0
Results:
Account Voucher DocumentDate OriginalAmount Sum(Amount) Records
abc 1 01.01.2018 00:00:00 100,00 0,00 2
See a live demo on rextester.
It seems straightforward ... am I missing something?
WITH CTE AS
(
SELECT
[Account],
[Voucher],
[DocumentDate],
ROW_NUMBER() OVER (PARTITION BY [Account],[Voucher] ORDER BY [DocumentDate]) RN,
[Amount]
FROM
MyTable
WHERE
[Account] = 'abc'
)
SELECT
[Account],
[Voucher],
[DocumentDate],
max(case when RN = 1 THEN [Amount] else null end) OriginalAmount,
sum([Amount]) SUM_Amount,
count(*) Records
from cte
GROUP BY
[Account], [Voucher], [DocumentDate]
HAVING
SUM([Amount]) = 0

Can the same query be done with Pivot?

I have a table as
CREATE TABLE #FinalRates
(
id int primary key identity(1,1),
RateDesc nvarchar(50),
Amt decimal(18,2)
)
insert into #FinalRates values('100',200)
insert into #FinalRates values('100',300)
insert into #FinalRates values('50-80',100)
insert into #FinalRates values('50-80',300)
insert into #FinalRates values('30-50',500)
insert into #FinalRates values('30-50',250)
Looking for an output as
RateDesc Amount1 Amount2
100 200 300
50-80 100 300
30-50 500 250
I have done this as
;with cte as(
select
RateDesc
,Amounts=
STUFF((Select ','+ cast(cast(Amt as int) as varchar(10))
from #FinalRates T1
where T1.RateDesc=T2.RateDesc
FOR XML PATH('')),1,1,'')
from #FinalRates T2
group by T2.RateDesc
)
select
RateDesc,
Amount1 = PARSENAME(REPLACE(Amounts,',','.'),2),
Amount2 = PARSENAME(REPLACE(Amounts,',','.'),1)
From Cte
Drop table #FinalRates
Can the same be done using PIVOT?
That's so complicated. How about this?
select ratedesc,
max(case when seqnum = 1 then amt end) as Amount1,
max(case when seqnum = 2 then amt end) as Amount2
from (select ft.*,
row_number() over (partition by ratedesc order by id) as seqnum
from #finalrates fr
) fr
group by ratedesc;
You could use a similar approach using pivot but conditional aggregation often performs better.
Plus, if you know you have no holes in id, you can do:
select ratedesc,
max(case when id % 2 = 1 then amt end) as Amount1,
max(case when id % 2 = 0 then amt end) as Amount2
from #finalrates fr
group by ratedesc;
Using PIVOT,
Assuming you have 2 Amt for each RateDesc.
Select RateDesc, [Amount1], [Amount2] From
(
Select RateDesc, Amt
, 'Amount' + cast(row_number() over (partition by RateDesc order by Amt) as varchar(5)) RowVal
from #FinalRates
) x
PIVOT
(
MAX(Amt) For RowVal in ([Amount1], [Amount2])
) p

SQL identify duplicate and update

i need help in below issue.i have a customer table CustA which is having columns custid, first name , surname, phone1, phone2,lastupdateddate. This table has duplicate records.a record is considered duplicate in CustA table when
first name & surname & (phone1 or phone2) is duplicated
custid firstname surname phone1 phone2 lastupdateddate
1000 Sam Son 334566 NULL 1-jan-2016
1001 sam son NULL 334566 1-feb-2016
i have used cte for this scenario to Partition by firstname, lastname, phone1, phone2 based on rownumber. But the OR condition is remaining as challenge for phone1 or phone2 in CTE query. Please share your thoughts. Appreciate it.
Trick here is COALESCE
With cte as
(
select Count()over(partition by firstname, lastname, coalesce(phone1, phone2)) as cnt,*
From yourtable
)
Select * from CTE
WHere cnt > 1
Though if it isn't the case that one is always null You can use a CASE expression to ensure that the values are presented in a consistent order.
WITH cte
AS (SELECT COUNT(*)
OVER(
partition BY firstname,
lastname,
CASE WHEN phone1 < phone2 THEN phone1 ELSE phone2 END,
CASE WHEN phone1 < phone2 THEN phone2 ELSE phone1 END) AS cnt,
*
FROM yourtable)
SELECT *
FROM CTE
WHERE cnt > 1
This one will also give you the list of dupes (optional custid<>A.custid)
Declare #Yourtable table (custid int,firstname varchar(50),surname varchar(50),phone1 varchar(25),phone2 varchar(25),lastupdate date)
Insert into #Yourtable values
(1000,'Sam','Son' ,'334566',NULL ,'1-jan-2016'),
(1001,'sam','son' ,NULL ,'334566','1-feb-2016'),
(1003,'sam','son' ,NULL ,NULL ,'2-feb-2016'),
(1002,'Not','ADupe',NULL ,NULL ,'1-feb-2016')
Select A.*
,B.Dupes
From #YourTable A
Cross Apply (Select Dupes=(Select Stuff((Select Distinct ',' + cast(custid as varchar(25))
From #YourTable
Where custid<>A.custid
and firstname=A.firstname
and surname =A.surname
and (IsNull(A.phone1,'') in (IsNull(phone1,''),IsNull(phone2,'')) or IsNull(A.phone2,'') in (IsNull(phone1,''),IsNull(phone2,'')) )
For XML Path ('')),1,1,'')
)
) B
Where Dupes is not null
Returns
custid firstname surname phone1 phone2 lastupdate Dupes
1000 Sam Son 334566 NULL 2016-01-01 1001,1003
1001 sam son NULL 334566 2016-02-01 1000,1003
1003 sam son NULL NULL 2016-02-02 1000,1001

Resources