Waterfall Logic for SQL Server Columns - sql

Can you please help me with the following.
I have the data with flags as the following and i need to add 5 additional columns based on those flag columns in a waterfall/Cascade way. I tried to accomplish this with case statement but the logic is becoming more confusing.
Here is the sample data and how the end result should look like.
DECLARE #T AS TABLE
(
ID INT,
Mortality VARCHAR(10),
Readmission varchar(10),
EDVisit varchar(10),
Return_to_OR varchar(10),
Sepsis varchar(10)
);
DECLARE #endresult AS TABLE
(
ID INT,
Mortality VARCHAR(10),
Readmission varchar(10),
EDVisit varchar(10),
Return_to_OR varchar(10),
Sepsis varchar(10),
Indicator1 varchar(15),
Indicator2 varchar(15),
Indicator3 varchar(15),
Indicator4 varchar(15),
Indicator5 varchar(15)
);
insert into #T VALUES
(1,'Y', 'N', 'Y','Y','Y'),
(2,'N','Y','N','Y','Y'),
(3,'N','N','N','Y','Y')
insert into #endresult VALUES
(1,'Y', 'N', 'Y','Y','Y','Mortality','EDVisit','Return_to_OR','Sepsis',null),
(2,'N','Y','N','Y','Y','Readmission','Return_to_OR','Sepsis',null,null),
(3,'N','N','N','Y','Y','Return_to_OR','Sepsis',null,null,null)
select * from #T
select * from #endresult

Here's an option which uses a bit of JSON in concert with a conditional aggregation.
On a side note: Assuming you had a typo EDVisit vs ERVisit
Example or dbFiddle
select A.*
,B.*
From #T A
Cross Apply (
Select Indicator1 = max(case when Seq=1 then [Key] end)
,Indicator2 = max(case when Seq=2 then [Key] end)
,Indicator3 = max(case when Seq=3 then [Key] end)
,Indicator4 = max(case when Seq=4 then [Key] end)
,Indicator5 = max(case when Seq=5 then [Key] end)
From (
Select [Key]
,Value
,Seq = row_number() over (order by (select null))
From OpenJson((Select A.* For JSON Path,Without_Array_Wrapper ))
Where [Key] not in ('ID')
and Value<>'N'
) B1
) B
Returns

Related

Rewrite SQL with LEFT JOIN INSTEAD OF OUTER APPLY

CREATE TABLE #ledgertxn (
txnno int,
lid int,
flid int,
txndate date,
lname varchar(50),
debit int,
credit int,
ledgername varchar(50),
drcr varchar(2),
txntype varchar(30)
)
SELECT
Limit1.Txnno,
Limit1.Txndate,
Limit1.Particulars,
Limit1.Debit,
Limit1.Credit
FROM
(SELECT DISTINCT
txnno
FROM
#ledgertxn ) Distinct1
OUTER APPLY
(SELECT TOP 1
Project2.Txnno,
Project2.Txndate,
Project2.Particulars,
Project2.Debit,
Project2.Credit
FROM
( SELECT
Extent2.txnno,
Extent2.txndate,
Extent2.ledgername AS Particulars,
Extent2.debit,
Extent2.credit,
Extent2.lid
FROM
#ledgertxn Extent2
WHERE
Distinct1.txnno = Extent2.txnno ) Project2
ORDER BY
Project2.Lid desc ) AS Limit1

T-SQL: convert columns to rows and insert/update another table

Looking for non fancy, easily debugable for junior developer solution...
In SQL Server 2008 R2, I have to update data from #data table to #tests table in desired format. I am not sure how I would archive result using T-SQL query?
NOTE: temp tables have only 3 columns for sample purpose only but real table have more than 50 columns for each set.
Here is what my tables look like:
IF OBJECT_ID('tempdb..#tests') IS NOT NULL
DROP TABLE #tests
GO
CREATE TABLE #tests
(
id int,
FirstName varchar(100),
LastName varchar(100),
UniueNumber varchar(100)
)
IF OBJECT_ID('tempdb..#data') IS NOT NULL
DROP TABLE #data
GO
CREATE TABLE #data
(
id int,
FirstName1 varchar(100),
LastName1 varchar(100),
UniueNumber1 varchar(100),
FirstName2 varchar(100),
LastName2 varchar(100),
UniueNumber2 varchar(100),
FirstName3 varchar(100),
LastName3 varchar(100),
UniueNumber3 varchar(100),
FirstName4 varchar(100),
LastName4 varchar(100),
UniueNumber4 varchar(100),
FirstName5 varchar(100),
LastName5 varchar(100),
UniueNumber5 varchar(100),
FirstName6 varchar(100),
LastName6 varchar(100),
UniueNumber6 varchar(100),
FirstName7 varchar(100),
LastName7 varchar(100),
UniueNumber7 varchar(100)
)
INSERT INTO #data
VALUES (111, 'Tom', 'M', '12345', 'Sam', 'M', '65432', 'Chris', 'PATT', '54656', 'Sean', 'Meyer', '865554', 'Mike', 'Max', '999999', 'Tee', 'itc', '656546444', 'Mickey', 'Mul', '65443231')
INSERT INTO #data
VALUES (222, 'Kurr', 'P', '22222', 'Yammy', 'G', '33333', 'Saras', 'pi', '55555', 'Man', 'Shey', '666666', 'Max', 'Dopit', '66666678', '', '', '', '', '', '')
INSERT INTO #data
VALUES (333, 'Mia', 'K', '625344', 'Tee', 'TE', '777766', 'david', 'mot', '4444444', 'Jeff', 'August', '5666666', 'Mylee', 'Max', '0000000', '', '', '', 'Amy', 'Marr', '55543444')
SELECT *
FROM #data
I want to insert/update data into #tests table from #data table.
Insert data into #tests table if id and UniqueNumber combination does not exists from #data table. If combination exists then update data into #tests table from #data table
This is desired output into #tests table
Here is an option that will dynamically UNPIVOT your data without using Dynamic SQL
To be clear: UNPIVOT would be more performant, but you don't have to enumerate the 50 columns.
This is assuming your columns end with a NUMERIC i.e. FirstName##
Example
Select ID
,FirstName
,LastName
,UniueNumber -- You could use SSN = UniueNumber
From (
SELECT A.ID
,Grp
,Col = replace([Key],Grp,'')
,Value
FROM #data A
Cross Apply (
Select [Key]
,Value
,Grp = substring([Key],patindex('%[0-9]%',[Key]),25)
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
) B
) src
Pivot ( max(Value) for Col in ([FirstName],[LastName],[UniueNumber]) ) pvt
Order By ID,Grp
Results
UPDATE XML Version
Select ID
,FirstName
,LastName
,UniueNumber
From (
SELECT A.ID
,Grp = substring(Item,patindex('%[0-9]%',Item),50)
,Col = replace(Item,substring(Item,patindex('%[0-9]%',Item),50),'')
,Value
FROM #data A
Cross Apply ( values (convert(xml,(Select A.* for XML RAW)))) B(XData)
Cross Apply (
Select Item = xAttr.value('local-name(.)', 'varchar(100)')
,Value = xAttr.value('.','varchar(max)')
From B.XData.nodes('//#*') xNode(xAttr)
) C
Where Item not in ('ID')
) src
Pivot ( max(Value) for Col in (FirstName,LastName,UniueNumber) ) pvt
Order By ID,Grp
One way is to query each group of columns separately and UNION the results
SELECT
id int,
FirstName1 as FirstName,
LastName1 as LastName,
UniueNumber1 AS SSN
FROM #data
UNION
SELECT
id int,
FirstName2 as FirstName,
LastName2 as LastName,
UniueNumber2 AS SSN
FROM #data
UNION
...
There's not a way to cleanly "loop through" the 7 groups of columns - you'll spend more time building a loop to create the query dynamically than just copying and pasting the query 6 times and changing the number.
Of course, it's best to avoid the type of structure you have in #data now if at all possible.

is there way to apply row updates without looping

i would like to pay for different invoices using different credits i have.
drop table #InvoicesWithBalances
drop table #AvailableCredits
create table #InvoicesWithBalances
(
InvoiceKey decimal(18,0) not null,
APBalance decimal(18,6) null,
BalanceAfterCreditApplied decimal(18,6) null,
)
create table #AvailableCredits
(
credit_id int identity(1,1),
StartingBalance decimal(18,6) null,
CurrentBalance decimal(18,6) null,
)
insert into #InvoicesWithBalances values (5452, 13744.080000, 13744.080000)
insert into #InvoicesWithBalances values (7056, 13744.080000, 13744.080000)
insert into #InvoicesWithBalances values (7438, 500.000000, 500.000000 )
insert into #AvailableCredits values ( -13744.080000, -13744.080000)
insert into #AvailableCredits values ( -13700.080000, -13700.080000)
insert into #AvailableCredits values ( -500.000000, -500.000000)
insert into #AvailableCredits values ( -500.000000, -500.000000)
select * from #InvoicesWithBalances
select * from #AvailableCredits
If I was doing a looping solution I would take the largest credit and start applying it to the invoices in order of largest to smallest until the balance of the credit is zero, then I would move on to the next to the next credit until I had no credits and no invoices left.
In the example below the first 2 credits should be fully used. The third credit should be partially used and the last credit should go untouched
Any advice?
I have tried to simulate your example here:
create table InvoicesWithBalances
(
InvoiceKey int not null,
APBalance int null,
BalanceAfterCreditApplied int null,
);
create table AvailableCredits
(
credit_id int identity(1,1),
StartingBalance int null,
CurrentBalance int null,
);
insert into InvoicesWithBalances values (5452, 13744, 13744);
insert into InvoicesWithBalances values (7056, 13744, 13744);
insert into InvoicesWithBalances values (7438, 500, 500);
insert into AvailableCredits values ( -13744, -13744);
insert into AvailableCredits values ( -13700, -13700);
insert into AvailableCredits values ( -500, -500);
insert into AvailableCredits values ( -500, -500);
create table #invoice (invoice_row_num int, InvoiceKey int, APBalance int, BalanceAfterCreditApplied int);
insert into #invoice
select ROW_NUMBER() OVER (ORDER BY APBalance desc) as row_num, InvoiceKey, APBalance, BalanceAfterCreditApplied FROM InvoicesWithBalances;
create table #credits (credit_row_num int, StartingBalance int, CurrentBalance int);
insert into #credits
select ROW_NUMBER() OVER (ORDER BY StartingBalance asc) as row_num, StartingBalance, CurrentBalance FROM AvailableCredits;
create table #invoice_credit_list (invoice_credit_row_num int, init_invoice int, init_credit int);
if ((select max(invoice_row_num) from #invoice) > (select max(credit_row_num) from #credits))
insert into #invoice_credit_list
select i.invoice_row_num , i.APBalance, (-isnull(c.StartingBalance,0)) from
#invoice i
left join
#credits c
on
i.invoice_row_num = c.credit_row_num;
else
insert into #invoice_credit_list
select c.credit_row_num, isnull(i.APBalance,0), (-c.StartingBalance) from
#credits c
left join
#invoice i
on
i.invoice_row_num = c.credit_row_num;
with cte as
(
select
invoice_credit_row_num,
init_invoice,
init_credit,
case when init_invoice >= init_credit then
init_invoice - init_credit
else
0
end as 'invoice_remaining',
case when init_credit >= init_invoice then
init_credit - init_invoice
else
0
end as 'credit_remaining'
from
#invoice_credit_list i
where
i.invoice_credit_row_num = 1
UNION ALL
select
i.invoice_credit_row_num,
i.init_invoice + cte.invoice_remaining as 'init_invoice',
i.init_credit + cte.credit_remaining as 'init credit',
case when (i.init_invoice + cte.invoice_remaining) >= (i.init_credit + cte.credit_remaining ) then
(i.init_invoice + cte.invoice_remaining) - (i.init_credit + cte.credit_remaining )
else
0
end as 'invoice_remaining',
case when (i.init_credit + cte.credit_remaining) >= (i.init_invoice + cte.invoice_remaining) then
(i.init_credit + cte.credit_remaining) - (i.init_invoice + cte.invoice_remaining)
else
0
end as 'credit_remaining'
from
#invoice_credit_list i
inner join
cte
ON
i.invoice_credit_row_num - 1 = cte.invoice_credit_row_num
AND
i.invoice_credit_row_num > 1
)
select * from cte;
and you can also find this simulation with output here: https://rextester.com/SJYGV76640
The 'cte' table in the simulation will give you all the details.
Eventhough, this is now done in db side, I am not sure of its performance. So, please compare and evaluate its performance.
Note:
if this is performing faster, well and good. But, at most, don't opt for loops in T-SQL.
If this is not performing better, go for loops in any other programming language like C#, VB,.. if that is possible.
If no other option works for you, go for loops in T-SQL. But, with increasing data, I am not sure how the server will react :(
Hope this helps you :)

tsql : Pivot a table with multiple measures

This is the first time I am trying to pivot a table. I have managed to pivot the table with only one measure and failed to do it with multiple measures. Please can I get some advice? Below is the test data I have and I would like to see. Please note that I have around 20 to 30 measures.
Thanks in advance!
-- Test Data HAVE
CREATE TABLE #have
(Name VARCHAR(50),Subject Varchar(20), Marks1 INT,Marks2 INT, Result Varchar(20) )
GO
INSERT INTO #have VALUES('Jsmith','Maths',65,56,'Pass')
INSERT INTO #have VALUES('Jsmith','Science',42,72,'Failed')
GO
-- Test Data WANT
CREATE TABLE #want
(Name VARCHAR(50),Maths_Marks1 INT ,Maths_Marks2 INT,Science_Marks1 INT,Science_Marks2 INT, Maths_Result Varchar(20), Science_Result Varchar(20))
GO
INSERT INTO #want VALUES('Jsmith',65,56,42,72,'Pass','Failed')
GO
select * from #have
select * from #want
-- Pivot table
SELECT Name , [Maths] AS [Maths_Marks1], [Science] AS [Science_Marks1]
FROM
(SELECT Name, Subject, Marks1 FROM #have) as SourceTable
PIVOT
(SUM(Marks1)
FOR Subject in ([Maths],[Science])
) as PivotTable1
-- Also Tried..Unable to get it working
SELECT Name , [Maths] AS [Maths_Marks1], [Science] AS [Science_Marks1]
FROM
(SELECT Name, Subject, Marks1 FROM #have) as SourceTable
PIVOT
(SUM(Marks1)
FOR Subject in ([Maths])
) as PivotTable1
(SELECT Name, Subject, Marks2 FROM #have) as SourceTable
PIVOT
(SUM(Marks2)
FOR Subject in ([Science])
) as PivotTable2
This trick builds on Marks properties : they are integers , mark <1000, exactly 2 marks columns (Marks1, Marks2) are in the table.
CREATE TABLE #have
(Name VARCHAR(50),Subject Varchar(20), Marks1 INT,Marks2 INT )
INSERT INTO #have VALUES('Jsmith','Maths',65,56)
INSERT INTO #have VALUES('Jsmith','Science',42,72)
SELECT Name, [Maths]/1000 AS [Maths_Marks1], [Maths]%1000 AS [Maths_Marks2], [Science]/1000 AS [Science_Marks1], [Science]%1000 AS [Science_Marks2]
FROM (SELECT Name, Subject, 1000*Marks1 + Marks2 Marks FROM #have) t
PIVOT (SUM(Marks)FOR Subject in ([Maths],[Science]) ) as p1
Otherwise , generally do it with conditional aggregates
SELECT Name
, max(case Subject when 'Maths' then Marks1 end ) AS [Maths_Marks1]
, max(case Subject when 'Maths' then Marks2 end ) AS [Maths_Marks2]
, max(case Subject when 'Maths' then Result end ) AS [Maths_Result]
, max(case Subject when 'Science' then Marks1 end ) AS [Science_Marks1]
, max(case Subject when 'Science' then Marks2 end ) AS [Science_Marks2]
, max(case Subject when 'Science' then Result end ) AS [Science_Result]
FROM #have
GROUP BY name
You can try with pivot as below:
Select [Name], Max(Maths_Marks1) as Maths_Marks1, Max(Maths_Marks2) as Maths_Marks2, max([Science_Marks1]) as [Science_Marks1]
,max([Science_Marks2]) as [Science_Marks2], max([Maths_Result]) as [Maths_Result], max([Science_Result]) as [Science_Result]
from (
Select [Name], [Subject] + '_Marks1' as [Subject1], [Subject] +'_Marks2' as [Subject2], Marks1, Marks2, Result, [Subject] +'_Result' as [SubjectRes] from #have ) a
pivot (max(marks1) for [Subject1] in ([Maths_Marks1],[Science_Marks1])) p
pivot (max(marks2) for [Subject2] in ([Maths_Marks2],[Science_Marks2])) p1
pivot (max(Result) for [SubjectRes] in ([Maths_Result],[Science_Result])) p2
group by [Name]

SQL join with previous row from the same group

I have a problem with sql query. I'm trying to create one time script which will put data to table. I have temporary table of new values ordered by date and i'm going to search previous value which have to have the same GroupId, TransactionId and FieldTypeId but ClaimModificationId have to be smaller.
Below i write a script which would be good if not error throwing on line:
where m2.ClaimModificationId < m1.ClaimModificationId
sql does not allow do referer to m1 table. Is there a method to write that condition differently?
create table #modifications (
[ClaimModificationId] INT IDENTITY(1,1),
[GroupId] INT,
[FieldTypeId] INT,
[FieldName] NVARCHAR(255),
[TransactionId] INT,
[NewValue] NVARCHAR(255),
[UserEmail] NVARCHAR(255),
[ModificationDate] DATETIME,
[Action] NVARCHAR(50))
select top 10
m1.[GroupId],
m1.[FieldTypeId],
m1.[FieldName],
m1.[TransactionId],
cm4.[NewValue] as OldValue,
m1.[NewValue],
m1.[UserEmail],
m1.[ModificationDate],
m1.[Action]
from #modifications m1
left join (
select max(m2.ClaimModificationId) as ClaimModificationId, m2.[GroupId], m2.[FieldTypeId], m2.TransactionId
from #modifications m2
where m2.ClaimModificationId < m1.ClaimModificationId
group by m2.GroupId, m2.FieldTypeId, m2.TransactionId) m3
on m3.groupId = m1.GroupId and m3.FieldTypeId = m1.FieldTypeId and m3.TransactionId = m1.TransactionId
LEFT JOIN #modifications cm4 ON m3.ClaimModificationId = cm4.ClaimModificationId
Try with OUTER APPLY:
SELECT TOP 10
m1.[GroupId] ,
m1.[FieldTypeId] ,
m1.[FieldName] ,
m1.[TransactionId] ,
cm4.[NewValue] AS OldValue ,
m1.[NewValue] ,
m1.[UserEmail] ,
m1.[ModificationDate] ,
m1.[Action]
FROM #modifications m1
OUTER APPLY ( SELECT MAX(m2.ClaimModificationId) AS ClaimModificationId ,
m2.[GroupId] ,
m2.[FieldTypeId] ,
m2.TransactionId
FROM #modifications m2
WHERE m2.ClaimModificationId < m1.ClaimModificationId
AND m2.groupId = m1.GroupId
AND m2.FieldTypeId = m1.FieldTypeId
AND m2.TransactionId = m1.TransactionId
GROUP BY m2.GroupId ,
m2.FieldTypeId ,
m2.TransactionId
) m3
LEFT JOIN #modifications cm4 ON m3.ClaimModificationId = cm4.ClaimModificationId
you can try something like this
create table #modifications
(
[ClaimModificationId] INT IDENTITY(1,1),
[GroupId] INT,
[FieldTypeId] INT,
[FieldName] NVARCHAR(255),
[TransactionId] INT,
[NewValue] NVARCHAR(255),
[UserEmail] NVARCHAR(255),
[ModificationDate] DATETIME,
[Action] NVARCHAR(50)
)
INSERT INTO #modifications values(1,1,'field',2,'new val1','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(1,2,'field',2,'val1','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(2,1,'field',3,'val2','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(1,1,'field',2,'val3','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(1,1,'field',2,'val4','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(1,1,'field',2,'val5','email#email.com',GETDATE(),'inserted')
INSERT INTO #modifications values(2,1,'field',3,'val5','email#email.com',GETDATE(),'inserted')
SELECT TOP 10
m1.[GroupId],
m1.[FieldTypeId],
m1.[FieldName],
m1.[TransactionId],
LAG([NewValue]) OVER(PARTITION by GroupId, FieldTypeId, TransactionId ORDER BY ClaimModificationId ASC) as OldValue,
m1.[NewValue],
m1.[UserEmail],
m1.[ModificationDate],
m1.[Action]
from #modifications m1