SQL Query in finding out the max number from a table with Null value - sql

How Can I work out this requirement. Please help.
Client Table - CT
ClientID Balance
123 10
123 20
123 30
123 40
124 50
124 60
124 Null
I want to find our the max(Balance) from the CT Table.
Condition - > If there is no null value then I have to find out max(Balance) otherwise It should be Null. See below result, that am expecting.
ClientID Balance
123 40
124 Null
Am writing the query as below. But Is there any more dynamic way to do?
Select ClientID,
CASE WHEN MIN(Balance) = NULL THEN
NULL
ELSE
MAX(Balance) END AS 'MaxBalance'
From CT
Group by clientID
Please let me know, is there any otheralternative?

Try this:
Select ClientID,
(CASE WHEN count(balance) < count(*)
THEN NULL
ELSE MAX(Balance)
END) AS MaxBalance
From CT
Group by clientID
Or, a bit more cumbsersome, but perhaps clearer:
Select ClientID,
(CASE WHEN sum(case when balance is null then 1 else 0 end) > 0
THEN NULL
ELSE MAX(Balance)
END) AS MaxBalance
From CT
Group by clientID

How about:
SELECT clientid
, balance
FROM
(
SELECT clientid
, balance
, row_number()
over( partition by clientid
order by CASE WHEN balance IS NULL THEN 0 ELSE 1 END
, balance DESC
) r
FROM ct
) n
WHERE r = 1

I'm not sure what datatype [Balance] is, but if it is an int, you can do the following:
Select ClientID, NULLIF(MAX(ISNULL(Balance,2147483647)),2147483647)
From CT
GROUP BY ClientID
If [Balance] is not an int, just replace 2147483647 with the max value of that datatype.
The danger, of course, would be if you really do have a client with a balance of 2147483647. In such a case their max balance would show as null.

This will work on SQL 2005+
; WITH a AS (
SELECT DISTINCT ClientID
FROM CT
WHERE Balance IS NULL )
SELECT t.ClientID, MAX(t.Balance) "MaxBalance"
FROM CT t
LEFT JOIN a ON t.CLientID = a.ClientID
WHERE a.ClientID IS NULL
GROUP BY t.CLientID
UNION ALL
SELECT a.ClientID, NULL
FROM a

This works but is not very elegant:
SELECT SUB.ClientID, CASE (SELECT COUNT(ClientID)
FROM MyTable MT
WHERE Balance IS NULL
AND MT.ClientID = SUB.ClientID)
WHEN 0 THEN (SELECT MAX(Balance)
FROM MyTable MT
WHERE MT.ClientID = SUB.ClientID)
ELSE NULL END AS Balance
FROM (SELECT ClientID
FROM [MyTable]
GROUP BY ClientID) SUB

Related

How to merge two query results joining same date

let's say there's a table have data like below
id
status
date
1
4
2022-05
2
3
2022-06
I want find count of id of each month by their status. Something like this below
date
count(status1) = 4
count(status2) =3
2022-05
1
null
2022-06
null
1
I tried doing
-- select distinct (not working)
select date, status1, status2 from
(select date, count(id) as "status1" from myTable
where status = 4 group by date) as myTable1
join
(select date, count(id) as "status2" from myTable
where status = 3 group by date) as myTable2
on myTable1.date = myTable2.date;
-- group by (not working)
but it does duplicate the data needed.
and I am using SQL Server.
select d.date,
sum
(
case
when d.status=4 then 1
else 0
end
)count_status_4,
sum
(
case
when d.status=5 then 1
else 0
end
)count_status_5
from your_table as d
group by d.date

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

Checking if all values for user_id IS NOT NULL

I have dataset which looks like this:
UserID AccountID CloseDate
1 1000 14/3/2022
1 2000 16/3/2022
2 1000 NULL
2 2000 4/3/2022
2 3000 NULL
And I would like to check if within one user_id all of the close dates are not null. In other words if all accounts within user_id are closed. I was trying using MAX or MIN but it is not working as I expected, because it is simply avoiding NULL values. Is there any other function which can check it? Let's say that my output would be another column which will assign 1 when all CloseDates are not null and else 0.
Sample output:
UserID AccountID CloseDate Check
1 1000 14/3/2022 1
1 2000 16/3/2022 1
2 1000 NULL 0
2 2000 4/3/2022 0
2 3000 NULL 0
Use conditional aggregation to explicitly COUNT the rows where the column has the value NULL:
SELECT GroupedColumn,
COUNT(CASE WHEN NullableColumn IS NULL THEN 1 END) AS NullCount
FROM dbo.YourTable
GROUP BY GroupedColumn;
If you want to just have a 1 or 0 just wrap the count in a CASE expression:
CASE COUNT(CASE WHEN NullableColumn IS NULL THEN 1 END) WHEN 0 THEN 1 ELSE 0 END
You can try to use FIRST_VALUE condition window function
SELECT *,
FIRST_VALUE(IIF(CloseDate IS NULL,0,1)) OVER(PARTITION BY UserID ORDER BY CloseDate )
FROM T
sqlfiddle
with dataset as (select 1 as UserId, 1000 as AccountID, '14/3/2022' as CloseDate
union all select 1, 2000, '16/3/2022'
union all select 2, 1000, NULL
union all select 2, 2000, '4/3/2022'
union all select 2, 3000, NULL)
select userid from dataset
group by userid
having sum(case when closedate is null then 1 else 0 end) = 0;
select d.*, iif(chk>0, 0, 1) chk
from d
outer apply (
select UserId, COUNT(*) CHK
from d dd
WHERE d.UserId = dd.UserId
and dd.CloseDate IS NULL
group by UserId
) C
You can also use "exists". e.g. :
select y.UserID, y.AccountID, y.CloseDate,
-- [Check]: returns 0 if there is a row in the table for the
-- UserID where CloseDate is null, else 1
(case when exists(select * from YourTable y2 where y2.UserID = y.UserID
AND y2.CloseDate is null) then 0 else 1 end) as [Check]
from YourTable y

SQL Select Distinct Records From Two Tables

I am trying to write a SQL statement that will return a set of Distinct set of CompanyNames from a table based on the most recent SaleDate withing a specified date range from another table.
T01 = Account
T02 = TransHeader
The fields of importance are:
T01.ID, T01.CompanyName
T02.AccountID, T02.SaleDate
T01.ID = T02.AccountID
What I want to return is the Max SaleDate for each CompanyName without any duplicate CompanyNames and only the Max(SaleDate) as LastSale. I will be using a Where Clause to limit the SaleDate range.
I tried the following but it returns all the records for all SalesDates in the range. This results in the same company being listed multiple times.
Current MS-SQL Query
SELECT T01.CompanyName, T02.LastSale
FROM
(SELECT DISTINCT ID, IsActive, ClassTypeID, CompanyName FROM Account) T01
FULL OUTER JOIN
(SELECT DISTINCT AccountID, TransactionType, MAX(SaleDate) LastSale FROM TransHeader group by AccountID, TransactionType, SaleDate) T02
ON T01.ID = T02.AccountID
WHERE ( ( T01.IsActive = 1 )AND
( (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL)
BETWEEN '01/01/2016' AND '12/31/2018 23:59:00' AND (Select Max(SaleDate)From TransHeader Where AccountID = T01.ID AND TransactionType in (1,6) AND SaleDate is NOT NULL) IS NOT NULL
)
)
ORDER BY T01.CompanyName
I thought the FULL OUTER JOIN was the ticket but it did not work and I am stuck.
Sample data Account Table (T01)
ID CompanyName IsActive ClassTypeID
1 ABC123 1 1
2 CDE456 1 1
3 EFG789 1 1
4 Test123 0 1
5 Test456 1 1
6 Test789 0 1
Sample data Transheader table (T02)
AccountID TransactionType SaleDate
1 1 02/03/2012
2 1 03/04/2013
3 1 04/05/2014
4 1 05/06/2014
5 1 06/07/2014
6 1 07/08/2015
1 1 08/09/2016
1 1 01/15/2016
2 1 03/20/2017
2 1 03/21/2017
3 1 03/04/2017
3 1 04/05/2018
3 1 05/27/2018
4 1 06/01/2018
5 1 07/08/2018
5 1 08/01/2018
5 1 10/11/2018
6 1 11/30/2018
Desired Results
CompanyName LastSale (Notes note returned in the result)
ABC123 01/15/2016 (Max(SaleDate) LastSale for ID=1)
CDE456 03/21/2017 (Max(SaleDate) LastSale for ID=2)
EFG789 05/27/2018 (Max(SaleDate) LastSale for ID=3)
Testing456 10/11/2018 (Max(SaleDate) LastSale for ID=5)
ID=4 & ID=6 are note returned because IsActive = 0 for these records.
One option is to select the maximum date in the select clause.
select
a.*,
(
select max(th.saledate)
from transheader th
where th.accountid = a.id
and th.saledate >= '2016-01-01'
and th.saledate < '2019-01-01'
) as max_date
from account a
where a.isactive = 1
order by a.id;
If you only want to show transaction headers with sales dates in the given date range, then you can just inner join the maximum dates with the accounts. In order to do so, you must group your date aggregation per account:
select a.*, th.max_date
from account a
join
(
select accountid, max(saledate) as max_date
from transheader
and saledate >= '2016-01-01'
and saledate < '2019-01-01'
group by accountid
) th on th.accountid = a.id
where a.isactive = 1
order by a.id;
select CompanyName,MAX(SaleDate) SaleDate from Account a
inner join Transheader b on a.id = b.accountid
group by CompanyName
order by 1

Count 2 different columns in one result set

I am facing a problem and getting incorrect results. I'd like to count records that have leavedates and deceaseddates for each year and display them in one table.
This is my query so far:
select year(leavedate) as leave_year, count(year(leavedate)) as cnt_leave , count(year(DeceasedDate)) as cnt_dod
from clients
where leavedate is not null and DeceasedDate is not null
group by year(leavedate),year(DeceasedDate)
order by 1 desc
This is the goal I'd like like to have:
+------+-----------+---------+
| Year | Cnt_leave | Cnt_dod |
+------+-----------+---------+
| 2018 | 542 | 5685 |
| 2017 | 8744 | 5622 |
| 2016 | 556 | 325 |
| etc | | |
+------+-----------+---------+
Should I do it in two separate select statements or is it possible to do it in one?
I'd appreciate a feedback!
I would do:
with
l as (
select year(leavedate) as y, count(*) as c
from clients
group by year(leavedate)
),
d as (
select year(deceaseddate) as y, count(*) as c
from clients
group by year(deceaseddate)
)
select
coalesce(l.y, d.y) as year,
coalesce(l.c, 0) as cnt_leave,
coalesce(d.c, 0) as cnt_dod
from l
full outer join d on l.y = d.y
order by coalesce(l.y, d.y) desc
If i understand, you are looking for something like this :
select year(leavedate) as leave_year, sum(case leavedate when null then 0 else 1 end) as cnt_leave,
sum(case DeceasedDate when null then 0 else 1 end) as cnt_dod
from clients
where leavedate is not null or DeceasedDate is not null
group by year(leavedate)
order by 1 desc
You'll need to count in two steps as Year(LeaveDate) is not related to Year(DeceasedDate); but you combine those SELECTs via some CTE construction.
In fact, there are (at least) two ways you can do this, not sure which one will be the better performer; you'll need to try to figure out what works best on your data.
SELECT client_id = 1, LeaveDate = Convert(datetime, '20080118'), DeceasedDate = Convert(datetime, '20090323')
INTO #test
UNION ALL SELECT client_id = 2, LeaveDate = '20100118', DeceasedDate = '20100323'
UNION ALL SELECT client_id = 3, LeaveDate = '20100118', DeceasedDate = '20120323'
;WITH src
AS (SELECT *
FROM #test
WHERE LeaveDate IS NOT NULL
AND DeceasedDate IS NOT NULL),
leave
AS (SELECT yr = Year(LeaveDate), cnt = COUNT(*) FROM src GROUP BY Year(LeaveDate)),
deceased
AS (SELECT yr = Year(DeceasedDate), cnt = COUNT(*) FROM src GROUP BY Year(DeceasedDate))
SELECT yr = ISNULL(l.yr, d.yr),
cntLeaveDate = ISNULL(l.cnt, 0),
cntDeceasedDate = ISNULL(d.cnt, 0)
FROM leave l
FULL OUTER JOIN deceased d
ON d.yr = l.yr
ORDER BY 1 DESC
;WITH src
AS (SELECT *
FROM #test
WHERE LeaveDate IS NOT NULL
AND DeceasedDate IS NOT NULL),
years
AS (SELECT DISTINCT yr = Year(LeaveDate) FROM src
UNION -- Do not UNION ALL here, you want to avoid doubles!
SELECT DISTINCT yr = Year(DeceasedDate) FROM src)
SELECT yr,
cntLeaveDate = SUM ( CASE WHEN Year(LeaveDate) = yr THEN 1 ELSE 0 END),
cntDeceasedDate = SUM ( CASE WHEN Year(DeceasedDate) = yr THEN 1 ELSE 0 END)
FROM years, #test
WHERE LeaveDate IS NOT NULL
AND DeceasedDate IS NOT NULL
GROUP BY yr
ORDER BY 1 DESC
DROP TABLE #test
I would use apply for this . . . I think it is the simplest solution:
select year(dte) as year, sum(isleave) as num_leaves, sum(isdeceased) as numdeceased
from clients c cross apply
(values (leavedate, 1, 0),
(deceaseddate, 0, 1)
) v(dte, isleave, isdeceased)
where dte is not null
group by year(dte)
order by 1 desc;
SQL FIDDLE
Based on your comments, all you need is to remove the where clause and limit the group by.
select
year(leavedate) as leave_year,
count(year(leavedate)) as cnt_leave ,
count(year(DeceasedDate)) as cnt_dod
from clients
--where leavedate is not null and DeceasedDate is not null
group by year(leavedate) --,year(DeceasedDate)
--order by 1 desc
Or, if your data could have NULL's in either column, which I assume above and thus omitted the where clause you'd want:
select
coalesce(year(leavedate),year(DeceasedDate)) as the_year,
count(year(leavedate)) as cnt_leave ,
count(year(DeceasedDate)) as cnt_dod
from clients
--where leavedate is not null and DeceasedDate is not null
group by coalesce(year(leavedate),year(DeceasedDate))
--order by 1 desc
Here's the example:
declare #clients table (leavedate date null, DeceasedDate date null)
insert into #clients
values
('20180101',NULL)
,(null,'20170101')
,('20150101','20150101')
select
year(leavedate) as leave_year,
count(year(leavedate)) as cnt_leave ,
count(year(DeceasedDate)) as cnt_dod
from #clients
--where leavedate is not null and DeceasedDate is not null
group by year(leavedate) --,year(DeceasedDate)
--order by 1 desc
select
coalesce(year(leavedate),year(DeceasedDate)) as the_year,
count(year(leavedate)) as cnt_leave ,
count(year(DeceasedDate)) as cnt_dod
from #clients
--where leavedate is not null and DeceasedDate is not null
group by coalesce(year(leavedate),year(DeceasedDate))
--order by 1 desc