IF else Stored procedure confusion - sql

This is a table
name a b c normal loan status
abc 50 60 70 normal
bcd 50 50 50 loan
what i want first is to get total of values of columns a,b and c in accordance with their status.. meaning; total amount of name with status normal should come under normal column,and of status loan should come under loan column..
i should get abc's total 50+60+70 in normal
and bcd's total 50+50+50 in loan
how do i do that ??
i tried if else in SP , but cant seem to be getting it

I think the below is what you need. The below query is a standard ANSI query:
SELECT name
,SUM(CASE WHEN status = 'normal' THEN (a + b + c) ELSE 0 END) AS normal
,SUM(CASE WHEN status = 'loan' THEN (a + b + c) ELSE 0 END) AS loan
,status
FROM yourTable
GROUP BY name, status
OUTPUT:
name normal loan status
bcd 0 150 loan
abc 180 0 normal
As per your requirement in the comment to update the existing rows:
UPDATE yourTable
SET normal = CASE WHEN status = 'normal' THEN (a + b + c) ELSE 0 END,
loan = CASE WHEN status = 'loan' THEN (a + b + c) ELSE 0 END
FROM yourTable
Please note that you need to update the table every time you insert new record or to change the original INSERT statement to include those columns as well.

In single select you can make it.
Try
SELECT *,(CASE WHEN [status]='normal' THEN [a]+[b]+[c] ELSE null END) as normal,(CASE WHEN [status]='loan' THEN [a]+[b]+[c] ELSE null END) as loan FROM YourTable

Related

Sum a column and perform more calculations on the result? [duplicate]

This question already has an answer here:
How to use an Alias in a Calculation for Another Field
(1 answer)
Closed 3 years ago.
In my query below I am counting occurrences in a table based on the Status column. I also want to perform calculations based on the counts I am returning. For example, let's say I want to add 100 to the Snoozed value... how do I do this? Below is what I thought would do it:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
Snoozed + 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
I get this error:
Invalid column name 'Snoozed'.
How can I take the value of the previous SUM statement, add 100 to it, and return it as another column? What I was aiming for is an additional column labeled Test that has the Snooze count + 100.
You can't use one column to create another column in the same way that you are attempting. You have 2 options:
Do the full calculation (as #forpas has mentioned in the comments above)
Use a temp table or table variable to store the data, this way you can get the first 5 columns, and then you can add the last column or you can select from the temp table and do the last column calculations from there.
You can not use an alias as a column reference in the same query. The correct script is:
SELECT
pu.ID Id, pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+100 AS Snoozed
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
MSSQL does not allow you to reference fields (or aliases) in the SELECT statement from within the same SELECT statement.
To work around this:
Use a CTE. Define the columns you want to select from in the CTE, and then select from them outside the CTE.
;WITH OurCte AS (
SELECT
5 + 5 - 3 AS OurInitialValue
)
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM OurCte
Use a temp table. This is very similar in functionality to using a CTE, however, it does have different performance implications.
SELECT
5 + 5 - 3 AS OurInitialValue
INTO #OurTempTable
SELECT
OurInitialValue / 2 AS OurFinalValue
FROM #OurTempTable
Use a subquery. This tends to be more difficult to read than the above. I'm not certain what the advantage is to this - maybe someone in the comments can enlighten me.
SELECT
5 + 5 - 3 AS OurInitialValue
FROM (
SELECT
OurInitialValue / 2 AS OurFinalValue
) OurSubquery
Embed your calculations. opinion warning This is really sloppy, and not a great approach as you end up having to duplicate code, and can easily throw columns out-of-sync if you update the calculation in one location and not the other.
SELECT
5 + 5 - 3 AS OurInitialValue
, (5 + 5 - 3) / 2 AS OurFinalValue
You can't use a column alias in the same select. The column alias do not precedence / sequence; they are all created after the eval of the select result, just before group by and order by.
You must repeat code :
SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END)+ 100 AS Test
FROM
Prospects p
INNER JOIN
ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE
p.Store = '108'
GROUP BY
pu.Name, pu.Id
ORDER BY
Name
If you don't want to repeat the code, use a subquery
SELECT
ID, Name, LeadCount, Working, Uninterested,Converted, Snoozed, Snoozed +100 AS test
FROM
(SELECT
pu.ID Id,pu.Name Name,
COUNT(*) LeadCount,
SUM(CASE WHEN Status = 'Working' THEN 1 ELSE 0 END) AS Working,
SUM(CASE WHEN Status = 'Uninterested' THEN 1 ELSE 0 END) AS Uninterested,
SUM(CASE WHEN Status = 'Converted' THEN 1 ELSE 0 END) AS Converted,
SUM(CASE WHEN SnoozedId > 0 THEN 1 ELSE 0 END) AS Snoozed
FROM Prospects p
INNER JOIN ProspectsUsers pu on p.OwnerId = pu.SalesForceId
WHERE p.Store = '108'
GROUP BY pu.Name, pu.Id) t
ORDER BY Name
or a view

SQL Server - Matrix of multiple WHERE queries

If I have a table like below:
Source Event Date Qty
Site A Create Account 5/05/2018 6
Site B Create Account 4/05/2018 12
Site A Update Account 6/05/2018 1
Site A Update Notes 7/05/2018 2
Site B Add Dependant 5/05/2018 1
Site C Create Account 5/05/2018 14
And another like this:
Date OrdersRec
4/05/2018 162
5/05/2018 123
6/05/2018 45
7/05/2018 143
And want to build a sort of matrix table to be able to use again and again for various date ranged queries, like this:
Date Create Update UpdateNotes AddDependant OrdersRec
4/05/2018 12 0 0 0 162
5/05/2018 20 0 0 1 123
6/05/2018 0 1 0 0 45
7/05/2018 0 0 2 0 143
What would be the best way of doing so?
I started with an INSERT into for the dates and the CreateAccount numbers, but I'm not sure how to go back and update the other counts for particular dates, or even if thats stupid and I'm missing something obvious.
Thanks!
Just use union all or join. There is no reason to create a new table for this:
select date, sum(v_create) as v_create, sum(v_update) as v_update,
sum(v_updatenotes) as v_updatenotes, sum(v_adddependent) as v_adddependent,
sum(orderrec) as orderec
from ((select t1.date, v.*, 0 as OrdersRec
from table1 t1 cross apply
(values ( (case when event = 'Create Account' then quantity else 0 end),
(case when event = 'Update Account' then quantity else 0 end),
(case when event = 'Update Notes' then quantity else 0 end),
(case when event = 'Add Dependent' then quantity else 0 end)
)
) v(v_create, v_update, v_updatenotes, v_adddependent)
) union all
(select date, 0, 0, 0, 0, orderrec
from table2
)
) t
group by date
order by date;
This handles a lot of edge cases, such as when dates are missing from one or the other tables.
Why don't you create a variable table having all the dates, and use it as your main table.
Then you would so something like this
select m.date,
sum(case when event = 'Create Account' then 1 else 0) as create,
sum(case when event = 'Update Account' then 1 else 0) as updates,
sum(o.ordersrec) as orders
from #main as m
inner join #orders as o on o.date = m.date
inner join #events as e on e.date = m.date
group by m.date

SQL Server - Insert Rows in another table based on complex matching conditions (includes FIFO)

I am working on a complex problem - Bill Marking for Ageing Analysis. The data is like this :
Source Table :
TrxnID, Date, CustomerID, DebitCredit, Amount
-----------------------------------------------
D1 01-Apr, RAS12, D, 2000
D2 01-Apr, RAS12, D, 3000
C3 02-Apr, RAS12, C, 4000
D4 03-Apr, RAS12, D, 5000
C5 04-Apr, RAS12, C, 1000
C6 10-Apr, RAS12, C, 6000
D7 25-Apr, RAS12, D, 3000
So, Total Debit : 13,000 and Total Credit : 11,000 and Total Balance is 2,000 Debit.
So the objective is to mark all Credit records with the respective debit records based on the date (FIFO), and store the marking scheme for each record in a separate table as shown below.
The target table has marking detail of each record
Target Table
TrxnID, MarkedTrxnID, MarkedAmount
------------------------------------
d1,c3,2000 **Rem : D1 balance=0, c3 balance= 1000
d2,c3,2000 **Rem : D2 balance=1000, c3 balance= 0
d2,c5,1000 **Rem : D2 balance=0, c5 balance= 0
d4,c6,5000 **Rem : D4 balance=0, c6 balance= 1000
d7,c6,1000 **Rem : D7 balance=2000, c6 balance= 0
I have been using old fashioned cursor based approach and that too becomes quite complex, but believe that there must be a set based mechanism to handle this.
Any help would be greatly appreciated. We are using SQL Server 2008 R2.
Thanks
I have a solution but probably it will have to be tested using more data than the 7 given rows.
First cross join the source table with itself. This will give all combinatiuons of the debet and the credit rows (7 * 7 = 49). Then select only the debet rows from the first source table and the credit rows from the second source table. We have 4 debet rows and 3 credit rows so this results in an output table of 12 rows. Each debet row will be linked to 3 credit rows. Add a running total of the credited amounts per debet transaction id (the column "Available" in my demo code). Add a column with the sum of all previous debet amounts (sum the debet amounts of all rows with a lower debet transaction id). This column I named "UsedBefore". Subtract "UsedBefore" from "Available" giving "Remains". Then subtract the current debet amount from "Remains" giving "RemainsAfter"
Use this table as input for a second select. Now only select rows with "Remains" greater than 0. Add a column with the amount "Paid". If "Remains" is greater than the debet amount this will be the debet amount and otherwise the "Remains" amount. Add a row_number that numbers the rows in each debet transacton id group.
Use the resulting table as input for the third select. Now only select the rows with a row_number equal to 1 or with an amount "RemainsAfter" less or equal to 0. You now have the rows you want but not yet the right amount credited.
Use the result of the previous selection as input for the fouth selection. Add a column "PaidNow". This is the amount "Paid" minus the previous amounts paid within the same debet transaction id group.
Try this:
select tab3.DebetTrnxID
, tab3.CreditTrnxID
, paid - sum(paid) over (partition by debetTrnxID order by rn) + paid as PaidNow
from (
select * from (
select *
, row_number() over (partition by tab.DebetTrnxID order by tab.CreditDate, tab.CreditTrnxID) as rn
, case when remains >= debetamount then
DebetAmount
else
Remains
end as Paid
from (
select a.TrnxID as DebetTrnxID
, a.Date as DebetDate
, a.DebitCredit as DebetDebetCredit
, a.Amount as DebetAmount
, b.TrnxID as CreditTrnxID
, b.Date as CreditDate
, b.DebitCredit as CreditDebetCredit
, b.Amount as CreditAmount
, sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID) as Available
, isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0) as UsedBefore
, sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID)
- isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0) as Remains
, sum(b.amount) over (partition by a.trnxid order by a.Date, a.TrnxId, b.Date, b.TrnxID)
- isnull((select sum(c.amount) from source c where c.DebitCredit='D' and c.TrnxID<a.TrnxID),0)
- a.amount as RemainsAfter
from source a
cross join source b
where a.DebitCredit='D'
and b.DebitCredit='C'
) tab
where remains>0
) tab2
where tab2.rn=1
or tab2.RemainsAfter<=0
) tab3
order by tab3.DebetTrnxID
, tab3.CreditTrnxID
Albert
I was able to resolve the issue using cursors only. Here are the steps followed :
Create a cursor with Sum of Debit and Sum of Credit, grouped by Customers - so this gives me a list of Customers for whom the Adjustment is to be run
Start a Loop
Create separate cursors for Debit & Credit Individual Records for that customer
Run through loop sequentially putting proper logic and ensuring that debit and credit records are correctly matched - any matching records gets inserted into the separate table. It was more of a logical flow issue then technical issue to ensure that only the desired cursor (either debit or credit or both) move to next record, and only the balance amount are matched.
The whole procedure ran in less than 4 minutes for more than 600,000 records, which is very much acceptable
Conclusion : sometimes cursor based operations are faster then set based operations
--step 1
select cv.customercode,sum(case when cv.drcr='D' then cv.Amount else 0
end) as PendAmountDr,
sum(case when cv.drcr='C' then cv.Amount else 0 end) as PendAmountCr
From SourceTable cv
Group by cv.CustomerCode
having sum(case when cv.drcr='D' then cv.Amount else 0 end) <> 0
and sum(case when cv.drcr='C' then cv.Amount else 0 end) <> 0
--Use Fetch Commands & While Loop
--STEP 2 : Create Cursor for Debit Records for that customer order by date
While ##Fetch_Status = 0
DECLARE CVDR CURSOR READ_ONLY FORWARD_ONLY STATIC FOR
select TRANSACTIONID, cv.Amount
From Cashvchr cv
where cv.CompanyID=#CompanyID and isnull(cv.deleted,0) = 0
AND isnull(cv.CustomerCode,'') = #Cust
AND CV.DRCR = 'D'
ORDER BY VCHRDATE,TRANSACTIONID
--STEP 3 : Create Cursor for Credit Records for that customer order by date
DECLARE CVCR CURSOR READ_ONLY FORWARD_ONLY STATIC FOR
select TRANSACTIONID, cv.Amount
From Cashvchr cv
where cv.CompanyID=#CompanyID and isnull(cv.deleted,0) = 0
AND isnull(cv.CustomerCode,'') = #Cust
AND CV.DRCR = 'C'
ORDER BY VCHRDATE,TRANSACTIONID
set #Balamtdr = #amtdr
set #balamtcr = #amtcr
WHILE #AMTTOMARK > 0
BEGIN
SET #CVID = #CVIDDR
SET #MCVID = #CVIDCR
SET #CURRMARKAMT=0
set #skipdr = 0
set #skipcr = 0
IF #BALAMTDR > #BALAMTCR -- i.e. balance debit amount to be marked is bigger from bal.credit amt THEN skip CREDIT
begin
SET #CURRMARKAMT = #balAMTCR
set #skipCr = 1
set #balamtdr = #balamtdr - #balamtcr
end
ELSE IF #balAMTDR < #balAMTCR -- i.e. balance Credit amount is bigger THEN skip Debit
begin
SET #CURRMARKAMT = #balAMTDR
set #skipdr = 1
set #balamtcr = #balamtCr - #balamtdr
end
ELSE -- i.e. balance Credit & Debit amount is same, mark and skip both
begin
SET #CURRMARKAMT = #balamtdr
set #balamtcr = #balamtCr - #balamtdr
set #skipdr = 1
set #skipcr = 1
end
----
INSERT INTO TargetTable
(CASHVCHRID,AMOUNT,MARKEDCASHVCHRID) VALUES
(#CVIDDR,#CURRMARKAMT,#CVIDCR)
set #amttomark = #amttomark - #currmarkamt
if #skipdr = 1 and #amttomark > 0
begin
FETCH next from CVDR INTO #CVIDDR, #AMTDR
if ##fetch_status <> 0
begin
set #amttomark = 0 -- no further marking possible some problem, should exit
end
else
begin
set #balamtdr = #amtdr
end
-- end if
end
-- end if #skipdr = 1
if #skipcr = 1 and #amttomark > 0
begin
FETCH next from CVCR INTO #CVIDCR, #AMTCR
if ##fetch_status <> 0
begin
set #amttomark = 0 -- no further marking possible some problem, should exit
end
else
begin
set #balamtcr = #amtcr
end
-- end if
end
-- end if #skipcr = 1
END
-- END WHILE #AMTTOMARK > 0 FOR DR/CR skip FOR ONE CUSTOMER

Conditional SUM with SELECT statement

I like to sum values in a table based on a condition taken from the same table called. The structure of the table as per below. The table is called Data
Data
Type Value
1 5
1 10
1 15
1 25
1 15
1 20
1 5
2 10
3 5
If the Value of Type 2 is larger than the Value of Type 3 then I like to subtract the Value of Type 2 from the sum of all the Values in the table. I'm not sure how to write the IF statements using Values looked up in the table. I have tried below but it doesn't work.
SELECT SUM(Value)-IF(SELECT Value FROM Data WHERE Type=2>SELECT Value
FROM Data WHERE Type=3 THEN SELECT Value FROM Data
WHERE Type=2 ELSE SELECT Value FROM Data WHERE Type=3) FROM Data
or
SELECT SUM(d.Value)-IIF(a.type2>b.type3, a.type2, b.type3)
FROM Data d, (SELECT Value AS type2 FROM Data WHERE Type=2) a,
(SELECT Value AS type3 FROM Data WHERE Type=3) b
If I follow your logic correctly, then this would seem to do what you want:
select d.value - (case when d2.value > d3.value then d2.value else 0 end)
from data d cross join
(select value from data where type = 2) d2 cross join
(select value from data where type = 3) d3 ;
EDIT:
If you want just one number, then use conditional aggregation:
select sum(value) -
(case when sum(case when type = 2 then value else 0 end) >
sum(case when type = 3 then value else 0 end)
then sum(case when type = 2 then value else 0 end)
else 0
end)
from data;
Thanks for pointing me in the right direction. This is what I came up with in the end. It is a little bit different to the reply above since I'm using MS Access
SELECT SUM(Value)-IIf(SUM(IIf(Type=2, Value, 0)>SUM(IIf(Type=3, Value, 0), SUM(IIf(Type=2, Value, 0), SUM(IIf(Type=3, Value, 0) FROM Data
It is them same as the second suggestion above but adapted to MS Access SQL.

SQL - Count( ) issue

I have a table with a charge/credit column:
Item | PriceVal | CostVal | CHARGE_CODE
1 5 3 CH
2 8 5 CH
1 -5 -3 CR
3 7 1 CH
4 15 10 CH
1 5 3 CH
I've got the query I need to get the NET price and cost, but I'm also interested in the NET charges. Right now I have:
SELECT Item, SUM(PriceVal), SUM(CostVal)
FROM Table
GROUP BY Item
How do I get another column with the value
COUNT(SUM(CHARGE_CODE=CH)-SUM(CHARGE_CODE=CR))
I'm at a loss.
count() is going to count one for every value thats not null, so I don't think thats exactly what you want. Take the count out and just take the
sum(case when charge_code = CH then costval else 0 end)
- sum(case when charge_code = 'CR' then costval else 0 end)
Since you have the dollar values entered as negatives in the table already, you can use the simple formula:
select
Item,
sum(PriceVal),
sum(CostVal),
sum(PriceVal-CostVal)
from Table
group by Item
I don't believe you should be subtracting the credit items as they're already negative.
If you really do want want the net count of transactions:
select
Item,
sum(PriceVal),
sum(CostVal),
sum(case when charge_code = 'CH' then 1 else 0 end) -
sum(case when charge_code = 'CR' then -1 else 0 end)
from Table
group by Item
or, if there are only two charge codes, substitute:
sum(case when charge_code = 'CH' then 1 else -1 end)
for the last column.
Not 100% sure what you want, but you can count only certain rows like this:
SELECT COUNT(IF(CHARGE_CODE=CH,1,NULL)) ...
And similarly sum certain values from certain rows like this:
SELECT SUM(IF(CHARGE_CODE=CH,PriceVal,0)) ...