Display equation result - sql

I want to display the results of this equation (B/A+B)*100
I dont want rows of stage=1 and state is open
A are the rows where stage=1 and state is lost
B are the rows where stage=2 or higher irrespective of status
------------------------------------
name |stage| state
------------------------------------
| ABC | 1 | open
| DEF | 1 | open
| ABB | 1 | lost
| ABD | 1 | lost
| PQR | 2 | won
| PQF | 3 | lost
| PQY | 4 | open
| PQN | 5 | won
| PQM | 6 | lost
| PQM | 7 | lost
The result should be (6/6+2)*100= 75 %

SELECT
[equation] = CASE
WHEN (ISNULL([a].[cnt], 0) + ISNULL([b].[cnt], 0)) = 0 THEN NULL
ELSE (ISNULL([b].[cnt], 0) / (ISNULL([a].[cnt], 0) + ISNULL([b].[cnt], 0))) * 100
END
FROM
(
SELECT [cnt] = CAST(0 AS MONEY)
) AS [x]
OUTER APPLY
(
SELECT [cnt] = CAST(COUNT(*) AS MONEY) FROM [my_table] WHERE [stage] = 1 AND [state] = 'lost'
) AS [a]
OUTER APPLY
(
SELECT [cnt] = CAST(COUNT(*) AS MONEY) FROM [my_table] WHERE [stage] > 1
) AS [b];

No need for subqueries or outer apply().
select [B/(A+B)]=
(sum(case when stage >1 then 1.0 else 0.0 end)
/
sum(case
when stage =1 and state = 'lost'
then 1.0
when stage > 1
then 1.0
else null
end))*100
from t
test setup: http://rextester.com/THWUY43139
create table t (name char(3), stage int, state varchar(4))
insert into t values
('ABC',1,'open')
, ('DEF',1,'open')
, ('ABB',1,'lost')
, ('ABD',1,'lost')
, ('PQR',2,'won' )
, ('PQF',3,'lost')
, ('PQY',4,'open')
, ('PQN',5,'won' )
, ('PQM',6,'lost')
, ('PQM',7,'lost');
query:
select
[B]=sum(case when stage >1 then 1 else 0 end)
, [(A+B)]=sum(case
when stage =1 and state = 'lost'
then 1
when stage > 1
then 1
else 0
end)
, [B/(A+B)]=(sum(case when stage >1 then 1.0 else 0.0 end)
/
sum(case
when stage =1 and state = 'lost'
then 1.0
when stage > 1
then 1.0
else null
end))*100
from t
results:
+---+-------+----------+
| B | (A+B) | B/(A+B) |
+---+-------+----------+
| 6 | 8 | 75,00000 |
+---+-------+----------+

Related

How to get column wise sum in SQL?

I have a complex query where I am getting the count of various categories in separate columns.
Here's the output of my query:
district | colA | colB | colC
------------------------------------
DistA | 1 | 1 | 3
DistB | 2 | 0 | 2
DistC | 2 | 1 | 0
DistD | 0 | 3 | 4
..
And here's my query:
select
q1."district",
coalesce(max(case q1."type" when 'colA' then q1."type_count" else 0 end), 0) as "colA",
coalesce(max(case q1."type" when 'colB' then q1."type_count" else 0 end), 0) as "colB",
coalesce(max(case q1."type" when 'colC' then q1."type_count" else 0 end), 0) as "colC"
from (
select
d."name" as "district",
t."name" as "type",
count(t.id) as "type_count"
from
main_entity as m
inner join type_entity as t on
m."type_id" = t.id
inner join district as d on
m."district_id" = d.id
where
m."delete_at" is null
group by
d."name",
t.id
) as q1
group by
q1."district"
I want to modify this query so that I can get the sum of each column in the last row, something like this:
district | colA | colB | colC
------------------------------------
DistA | 1 | 1 | 3
DistB | 2 | 0 | 2
DistC | 2 | 1 | 0
DistD | 0 | 3 | 4
..
Total | 5 | 5 | 9
I have tried using group by + rollup with the above query by just adding the following:
...
group by rollup (q1."district")
It adds a row at the bottom but the values are similar to the values of a row before it, and not the sum of all the rows before it, so basically something like this:
district | colA | colB | colC
------------------------------------
DistA | 1 | 1 | 3
..
DistD | 0 | 3 | 4
Total | 0 | 3 | 4
So, how can I get the column-wise some from my query?
Try this:
With temp as
( --your query from above
select
q1."district",
coalesce(max(case q1."type" when 'colA' then q1."type_count" else 0 end), 0) as "colA",
coalesce(max(case q1."type" when 'colB' then q1."type_count" else 0 end), 0) as "colB",
coalesce(max(case q1."type" when 'colC' then q1."type_count" else 0 end), 0) as "colC"
from (
select
d."name" as "district",
t."name" as "type",
count(t.id) as "type_count"
from
main_entity as m
inner join type_entity as t on
m."type_id" = t.id
inner join district as d on
m."district_id" = d.id
where
m."delete_at" is null
group by
d."name",
t.id
) as q1
group by
q1."district"
)
select t.* from temp t
UNION
select sum(t1.colA),sum(t1.colB),sum(t1.colC) from temp t1

Update next column in table if previous column value is not null

When a person receives a score, an entry is added into the table #uniqueScores:
Pid | Date | Score
I have a stored procedure returning a table #people with the score columns containing the data from #uniqueScores (that fall within the past 3 months)
Pid | S1 | S2 | S3 | S4 | S5
I have a small test dataset, however I'm having trouble getting any scores beyond the first score registered to a user to appear in Score2 or beyond.
Here is my test dataset
Pid | Date | Score
#1 | 2020/07/01 | 8
#1 | 2020/09/15 | 8
#2 | 2020/09/21 | 3
#3 | 2020/10/01 | 5
#4 | 2020/10/18 | 6
#4 | 2020/10/31 | 2
My update statement, to update the Person column with the data
BEGIN
UPDATE #people
SET [Score5] = (CASE WHEN ( [p].[Score4] is not null and [p].[Score5] is null ) THEN [us].[Score] ELSE NULL END)
,[Score4] = (CASE WHEN ( [p].[Score3] is not null and [p].[Score4] is null ) THEN [us].[Score] ELSE NULL END)
,[Score3] = (CASE WHEN ( [p].[Score2] is not null and [p].[Score3] is null ) THEN [us].[Score] ELSE NULL END)
,[Score2] = (CASE WHEN ( [p].[Score1] is not null and [p].[Score2] is null ) THEN [us].[Score] ELSE NULL END)
,[Score1] = (CASE WHEN ( [p].[Score1] is null ) THEN [us].[Score] ELSE NULL END)
FROM #people [p] inner join #uniqueScores [us]
on [p].[PersonID] = [us].[PersonID]
WHERE [Date] >= #DateLimit -- within the previous 3 months
END
However, the query isn't updating the table with any but the first eligible values. The returned table looks like this
Pid | S1 | S2 | S3 | S4 | S5
#1 | 8 | null | null | null | null
#2 | 3 | null | null | null | null
#3 | 5 | null | null | null | null
#4 | 6 | null | null | null | null
The first table entry which is ineligible to be considered for the table isn't included which is great, however Person #4's second score is also missing.
I've been looking at PIVOT, WHILE and a CURSOR but I've got no closer to making this work. I'm sure I've missed something simple however I just can't see it.
UPDATE updates each row once. Preaggregate for multiple updates:
UPDATE p
SET Score1 = us.score_1,
Score2 = us.score_2,
Score3 = us.score_3,
Score4 = us.score_4,
Score5 = us.score_5
FROM #people [p] inner join
(SELECT us.PersonID,
MAX(CASE WHEN seqnum = 1 THEN Score END) as score_1,
MAX(CASE WHEN seqnum = 2 THEN Score END) as score_2,
MAX(CASE WHEN seqnum = 3 THEN Score END) as score_3,
MAX(CASE WHEN seqnum = 4 THEN Score END) as score_4,
MAX(CASE WHEN seqnum = 5 THEN Score END) as score_5
FROM (SELECT us.*,
ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY Date) as seqnum
FROM #uniqueScores us
WHERE [Date] >= #DateLimit -- within the previous 3 months
) us
GROUP BY us.PersonID
) s
ON us.PersonID = p.PersonId;
Note: You don't specify what order you want the scores in. This puts the oldest ones first. Use ORDER BY DESC if you want the newer ones first.

Count who paid group by 1, 2 or 3+

I have a payment table like the example below and I need a query that gives me how many IDs paid (AMOUNT > 0) 1 time, 2 times, 3 or more times. Example:
+----+--------+
| ID | AMOUNT |
+----+--------+
| 1 | 50 |
| 1 | 0 |
| 2 | 10 |
| 2 | 20 |
| 2 | 15 |
| 2 | 10 |
| 3 | 80 |
+----+--------+
I expect the result:
+-----------+------------+-------------+
| 1 payment | 2 payments | 3+ payments |
+-----------+------------+-------------+
| 2 | 0 | 1 |
+-----------+------------+-------------+
ID 1: Paid 1 time (50). The other payment is 0, so I did not count. So, 1 person paid 1 time.
ID 2: Paid 3 times (10,20,15). So, 1 person paid 3 or more time.
ID 3: Paid 1 time (80). So, 2 persons paid 1 time.
I'm doing manually on excel right now but I'm pretty sure there is a more practical solution. Any ideas?
A little sub-query will do the trick
Declare #YOurTable table (ID int, AMOUNT int)
Insert into #YourTable values
( 1 , 50 ),
( 1 , 0) ,
( 2 , 10) ,
( 2 , 20) ,
( 2 , 15) ,
( 2 , 10) ,
( 3 , 80)
Select [1_Payment] = sum(case when Cnt=1 then 1 else 0 end)
,[2_Payment] = sum(case when Cnt=2 then 1 else 0 end)
,[3_Payment] = sum(case when Cnt>2 then 1 else 0 end)
From (
Select id
,Cnt=count(*)
From #YourTable
Where Amount<>0
Group By ID
) A
Returns
1_Payment 2_Payment 3_Payment
2 0 1
To get the output you want try using a table to form the data and then SELECT from that:
with c as (
select count(*) count from mytable where amount > 0 group by id)
select
sum(case count when 1 then 1 else 0 end) "1 Payment"
, sum(case count when 2 then 1 else 0 end) "2 Payments"
, sum(case when count > 2 then 1 else 0 end) "3 Payments"
from c
Here is an example you can play with to see how the query is working.

Rewriting SQL query to get record id and customer number

Sample data
+-------------------+-------------+-----------------+---------------------+
| RECORD_ID | CUST_NO | IsAccntClosed | Code |
+-------------------+-------------+-----------------+---------------------+
|159045 | 2439123 | N | 13 |
+-------------------+-------------+-----------------+---------------------+
|159048 | 6376150 | Y | 13 |
+-------------------+-------------+-----------------+---------------------+
|159048 | 9513035 | N | 13 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 2398524 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6349269 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6350690 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6372163 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6393810 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159049 | 6402062 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 2677512 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 6349382 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159050 | 6378137 | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 2336197 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6349293 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6350682 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159051 | 6367895 | N | 12 |
+-------------------+-------------+-----------------+---------------------+
|159060 | yyyyyy | Y | 12 |
+-------------------+-------------+-----------------+---------------------+
IsAccntClosed column indicates if the account is Open (Y) or account is closed (Y).
I need to select Record_ID and cust_no for only those rows for which which Record_Id satisfies one of the below condition :
1. Only one cust account is open , there might be one or multiple closed customers
2. No open customer and only one closed customer
Expected output :
159045 2439123
159048 9513035
159049 2398524
159060 yyyyyy
A query like this would take each row as a single group and the count will come as 1
select RECORD_ID, CUST_NO, IsAccntClosed, count(IsAccntClosed), Code
from table1
group by RECORD_ID, CUST_NO, IsAccntClosed, Code
Any suggestions on how this query could be written to get the expected output?
Being IsAccntClosed a CHAR column, you should be able to get what you want with a query like this:
SELECT a.record_id,
(CASE
WHEN a.CountOpen=1 THEN a.CustNoOpen
ELSE a.CustNoClosed
END) AS cust_no
FROM (
SELECT b.record_id,
MAX(CASE WHEN b.IsAccntClosed='N' THEN b.cust_no ELSE NULL END) AS CustNoOpen ,
SUM(CASE WHEN b.IsAccntClosed='N' THEN 1 ELSE 0 END) AS CountOpen ,
MAX(CASE WHEN b.IsAccntClosed='Y' THEN b.cust_no ELSE NULL END) AS CustNoClosed,
SUM(CASE WHEN b.IsAccntClosed='Y' THEN 1 ELSE 0 END) AS CountClosed
FROM table1 b
GROUP BY b.record_id
) a
WHERE a.CountOpen=1 OR (a.CountOpen=0 AND a.CountClosed=1)
The inner query is grouping the table. It counts the open and closed accounts and takes one (random) cust_no of any of the closed accounts and one (random) of any of the open accounts, per group.
The outer query filters the data and cleans everything up, placing the open or closed cust_no in the output result column.
Notice that the WHERE condition of the outer query has the collateral effect that, since you are looking for records that have just a single open or a single closed account, those random cust_no which have been selected by the inner query, are now significant.
EDIT: I fixed the query and tested it on SQLFiddle.
In the below I added another record:
insert into tbl values (159060, 'zzzzzz', 'N', 12);
to illustrate what would happen if a record_id has just one open cust_no and just one closed cust_no. Note how in the result the cust_no returned is the zzzzz one, because that account is open, which you mentioned wanting to take precendence over closed, in the event of a tie 1:1 (zzzzzz should take over yyyyyy in this case, because yyyyyy is closed whereas zzzzzz is open)
Fiddle: http://sqlfiddle.com/#!4/5cb60/1/0
with one_open as
(select record_id
from tbl
where IsAccntClosed = 'N'
group by record_id
having count(distinct cust_no) = 1),
one_closed as
(select record_id
from tbl
where IsAccntClosed = 'Y'
group by record_id
having count(distinct cust_no) = 1),
bothy as
(select record_id from one_open intersect select record_id from one_closed)
select *
from tbl
where (record_id in (select record_id from one_open) and
IsAccntClosed = 'N')
or (record_id not in (select record_id from one_open) and
record_id in (select record_id from one_closed) and
IsAccntClosed = 'Y' and
record_id not in (select record_id from bothy))
select
record_id,
cust_no
from (
select
record_id,
cust_no,
count(case when isAccntClosed='Y' then 1 else null end)
over (partition by record_id) closed_accounts,
count(case when isAccntClosed='N' then 1 else null end)
over (partition by record_id) open_accounts
from
table1
)
where (open_accounts = 1)
or (open_accounts = 0 and closed_accounts = 1)
You can do this with conditional aggregation:
select RECORD_ID,
(case when sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 1
then max(case when IsAccntClosed = 'N' then max(CUST_NO) end)
else max(CUST_NO)
end) as cust_no
from table1
group by RECORD_ID
having sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 1 or
(sum(case when IsAccntClosed = 'N' then 1 else 0 end) = 0 and
sum(case when IsAccntClosed = 'Y' then 1 else 0 end) = 1
)
It is probably easier to understand the logic using subqueries:
select recordId,
(case when numOpen > 0 then OpenCustNo else closedCustNo end) as CustNo
from (select t1.RecordId, sum(case when IsAccntClosed = 'N' then 1 else 0 end) as numOpen,
sum(case when IsAccntOpen = 'N' then 1 else 0 end) as numClosed,
max(case when IsAccntClosed = 'N' then cust_no end) as OpenCustNo,
max(case when IsAccntClosed = 'Y' then cust_no end) as ClosedCustNo
from table1 t1
group by record_id
) r
where numOpen = 1 or numOpen = 0 and numClosed = 1;

Can we use two pivot in a single query?

Declare #tbl table(SessionId varchar(max),ItemID_FK int,Roles varchar(max))
insert into #tbl
select distinct SessionID,ItemID_FK,Roles from tbl_Answers where ID_FK=#ID
SELECT ItemID_PK,ItemName,case when [Role1] IS NULL then 0 else [Role1] end as [Role1],
case when [Role2] IS NULL then 0 else [Role2] end as [Role2],
case when [Role3] IS NULL then 0 else [Role3] end as [Role3],
case when [Role4] IS NULL then 0 else [Role4] end as [Role4],
case when [Role5] IS NULL then 0 else [Role5] end as [Role5],
case when [Role6] IS NULL then 0 else [Role6] end as [Role6],
case when [Role7] IS NULL then 0 else [Role7] end as [Role7]
FROM
(
select items.ItemID_PK ,items.ItemName,count(ans.Roles) as cntRoles,ans.Roles from tbl_Items items Full join #tbl ans
on items.ItemID_PK=ans.ItemID_FK where items.ID_FK= #ID group by Roles,ItemName , items.ItemID_PK
) d PIVOT
(
max(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt order by ItemID_PK
I used the above stored procedure and got the output as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
I have another query and got the following output.
Select Category,Answer,Roles
from tbl_Answers where ID_FK=1 and Category='OtherText'
+---------+--------+-----+
|Category |Answer |Roles|
+---------+--------+-----+
|OtherText| xxx |Role1|
|OtherText| yyy |Role1|
|OtherText| zzz |Role2|
|OtherText| xzx |Role3|
+---------+--------+-----+
I need to merge the above two outputs to generate the result as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
| Null | Othertext| xxx | zzz | xzx | saa | | xxx | |
| Null | Othertext| yyy | | | zxz | | | |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
How to combine the second query to the first pivot query to get the result mentioned above?
Thanks in advance.
You could just use UNION ALL to combine the two results, you would need to convert the roles from the top query from int to VARCHAR though:
DECLARE #ID INT = 1;
WITH Ans AS
( SELECT DISTINCT SessionID,ItemID_FK,Roles
FROM tbl_Answers
WHERE ID_FK = #ID
), PivotData AS
( SELECT items.ItemID_PK,
items.ItemName,
cntRoles = COUNT(ans.Roles),
ans.Roles
FROM tbl_Items items
FULL JOIN Ans
ON items.ItemID_PK = ans.ItemID_FK
WHERE items.ID_FK = #ID
GROUP BY Roles,ItemName, items.ItemID_PK
)
SELECT ItemID_PK,
ItemName,
[Role1] = CAST(ISNULL([Role1], 0) AS VARCHAR(255)),
[Role2] = CAST(ISNULL([Role2], 0) AS VARCHAR(255)),
[Role3] = CAST(ISNULL([Role3], 0) AS VARCHAR(255)),
[Role4] = CAST(ISNULL([Role4], 0) AS VARCHAR(255)),
[Role5] = CAST(ISNULL([Role5], 0) AS VARCHAR(255)),
[Role6] = CAST(ISNULL([Role6], 0) AS VARCHAR(255)),
[Role7] = CAST(ISNULL([Role7], 0) AS VARCHAR(255))
FROM PivotData
PIVOT
( MAX(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
UNION ALL
SELECT ItemID_PK = NULL,
ItemName = Category,
[Role1] = ISNULL([Role1], ''),
[Role2] = ISNULL([Role2], ''),
[Role3] = ISNULL([Role3], ''),
[Role4] = ISNULL([Role4], ''),
[Role5] = ISNULL([Role5], ''),
[Role6] = ISNULL([Role6], ''),
[Role7] = ISNULL([Role7], '')
FROM ( SELECT Category,Answer,Roles
FROM tbl_Answers
WHERE ID_FK = 1
AND Category = 'OtherText'
) pd
PIVOT
( MAX(Answer)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
ORDER BY ItemID_PK;
Note, I have changed this expression:
case when [Role2] IS NULL then 0 else [Role2] end
to
ISNULL([Role2], 0)
as the effect is the same, but it is much shorter. I have also removed the table variable, and just placed the same query within a Common Table Expression, as it seems redundant to fill a table variable then only refer to it once. You are removing the use of indexes and statistics on the actual table and gaining no benefit for it.
Use a UNION to combine the two pivots.