How to make everything null when there is null - sql-server

I want values to be null if there is any null in particular group.
When I am using aggregate functions, ie sum, then it is handling null itself but I don't want to handle null, I need null should make null when doing sum on it.
declare #tbl table (id int,grpid char )
insert into #tbl
values(1,'a'), (2,'b'),(null,'b')
select grpid,sum(id) as val
from #tbl
group by grpid
required output :
grpid val
a 1
b null

Simple as this I guess...
declare #tbl table (id int,grpid char )
insert into #tbl
values(1,'a'), (2,'b'),(null,'b')
;WITH cte AS(
SELECT DISTINCT grpid
FROM #tbl
WHERE id IS NULL
)
select t.grpid,sum(id) as val
from #tbl t
WHERE grpid NOT IN (SELECT grpid FROM cte)
GROUP BY t.grpid
UNION
SELECT grpid, NULL
FROM cte

You can try these methods:
Method 1:
select grpid,
case
when count(*)<>count(id) then null
else sum(id)
end as val
from #tbl
group by grpid
Note: Count(*) always return the number of records but Count(ColumnName) always return the number of records where ColumnName is not null
Method 2:
select grpid,
case
when exists(Select top 1 * from #tbl where grpid = t.grpid and id is null) then null
else sum(id)
end as val
from #tbl as t
group by grpid
Method 3:
select grpid, sum(id) as val
from #tbl as t
where grpid NOT IN (Select grpid from #tbl where id is null)
group by grpid
union
select grpid , null as val from #tbl where id is null

Another approach
declare #tbl table (id int, grpid char );
insert into #tbl
values (1,'a'), (2,'b'), (null,'b'), (3, 'a'), (null, 'c');
select t1.grpid
, case when t2.grpid is null then sum(t1.id)
else null
end as 'ssum'
from #tbl t1
left join #tbl t2
on t2.grpid = t1.grpid
and t2.id is null
group by t1.grpid, t2.grpid
order by t1.grpid;
Similar to some existing but might be more efficient
UNION will perform a distinct
declare #tbl table (id int, grpid char );
insert into #tbl
values (1,'a'), (2,'b'), (null,'b'), (3, 'a'), (null, 'c');
with cte as
( select id, grpid
from #tbl
where id is null
)
select grpid, id
from cte
union
select grpid, sum(id) as val
from #tbl
where not exists (select 1 from cte where cte.grpid = [#tbl].grpid)
group by grpid
order by grpid;

Related

SQL Synapse Compare data between Column(multiple values) and Column(multiple values)

I need to compare 2 columns from 2 tables.
table a:
ID|TEL
----------------
A1|1111,2222,3333
TABLE B:
ID|TEL
----------------
A1|2222,4444
Result should update in TABLE A
A1|1111,2222,3333,4444
As I know, maybe I should use select value from string_split (B.Tel,'|') to split it. However, I don't know how to loop to compare between A and B.
Please help.
Here is what I've tried but it's not working.
with split_tel as
(select id,
Value tel
from B
CROSS APPLY STRING_SPLIT(tel, ','))
,pre as (select sp.id
,sp.tel as split
,A.tel as target
from split_tel sp
inner join A
on sp.id = A.id)
select id,split,target
from pre
where split like '%' + target + '%' ;
First of all you should not really be storing your data like this, particularly if you are having to conduct set-based operations on it. However there is a simple solution using STRING_SPLIT and STRING_AGG if you are always effectively adding numbers, not taking away:
IF OBJECT_ID('tempdb..#tmpA') IS NOT NULL DROP TABLE #tmpA
IF OBJECT_ID('tempdb..#tmpB') IS NOT NULL DROP TABLE #tmpB
CREATE TABLE #tmpA (
id VARCHAR(5) NOT NULL,
tel VARCHAR(100) NOT NULL
);
CREATE TABLE #tmpB (
id VARCHAR(5) NOT NULL,
tel VARCHAR(100) NOT NULL
);
--INSERT INTO #tmpA VALUES ( 'A1', '1111,2222,3333' );
--INSERT INTO #tmpB VALUES ( 'A1', '2222,4444' );
INSERT INTO #tmpA
SELECT 'A1', '1111,2222,3333'
UNION ALL
SELECT 'B1', '2222'
INSERT INTO #tmpB
SELECT 'A1', '2222,4444'
UNION ALL
SELECT 'B1', '3333'
SELECT id, STRING_AGG( value, ',' ) tel
FROM
(
SELECT a.id, x.value
FROM #tmpA a
CROSS APPLY STRING_SPLIT( a.tel, ',' ) x
UNION
SELECT b.id, x.value
FROM #tmpB b
CROSS APPLY STRING_SPLIT( b.tel, ',' ) x
) y
GROUP BY id;

What is the optimal way to get only latest ID's from table in SQL

I'm trying to get only a single row per Appointment Number in a table storing a history of appointments. It works fine with a few rows but then gets slower? Is this the best way to do this kind of check and I'm just missing some indexes or is there a better way?
DECLARE #temptable TABLE
(
id INT PRIMARY KEY NOT NULL
, ApptNumber INT NOT NULL
, ApptDate DATE NOT NULL
, Notes VARCHAR(50) NULL
)
INSERT INTO #temptable VALUES (1,1,'01-DEC-2018','First Appointment')
INSERT INTO #temptable VALUES (2,1,'01-DEC-2018','')
INSERT INTO #temptable VALUES (3,1,'01-DEC-2018','Rescheduled')
INSERT INTO #temptable VALUES (4,2,'02-DEC-2018','Second Appointment')
INSERT INTO #temptable VALUES (5,2,'02-DEC-2018','Cancelled')
INSERT INTO #temptable VALUES (6,3,'03-DEC-2018','Third Appointment')
INSERT INTO #temptable VALUES (7,4,'04-DEC-2018','Fourth Appointment')
SELECT * FROM #temptable
SELECT MAX(id) FROM #temptable GROUP BY ApptNumber
SELECT tt.* FROM #temptable tt
INNER JOIN (SELECT MAX(id) [Id] FROM #temptable GROUP BY ApptNumber) appts ON appts.Id = tt.id
Solution 1:
select * from (
SELECT f1.*, row_number() over(partition by ApptNumber order by id desc ) rang FROM #temptable f1
) tmp where rang=1
Solution 2:
with tmp as (
select ApptNumber, max(ID) MaxID
from #temptable
group by ApptNumber
)
select f1.* from #temptable f1 inner join tmp f2 on f1.ID=f2.MaxID
Solution 3:
select distinct f3.* from #temptable f1
cross apply
(
select top 1 * from #temptable f2
where f1.ApptNumber=f2.ApptNumber
order by f2.ID desc
) f3
Window function
SELECT tt.*
FROM (
SELECT *, row_number() over (partition by ApptNumber order by id desc) as rn
) tt
where tt.rn = 1

Leave Approval Status in SQL Server

I am using SQL Server 2008 R2, I want to display leave application status that can be either apply, approve, reject or cancel.
If all leave approve then status = approve, like other leaves
but if there are mix status e.g some leave approve , some rejected then status = Partial.
I have written the code but I feel it is complicated, can I get it in one, single query?
create table #t
(
employeeID int,
LeaveCode nvarchar(10),
status nvarchar(50)
)
insert into #t
values(1, 'PL', 'Approve'), (1, 'PL', 'Reject'), (1, 'PL', 'Approve')
;with ct1 as
(
select status, count(status) Cnt
from #t
group by status
),
counters as
(
select count(*) as TotalLeave
from #t
)
select top(1)
CASE
WHEN C1.Cnt = C2.TotalLeave
THEN C1.status
ELSE 'Partial'
END [status]
from
ct1 C1
cross join
counters C2
drop table #t
try this,
create table #t
(
employeeID int,
LeaveCode nvarchar(10),
status nvarchar(50)
)
insert into #t values(1,'PL','Approve'),(1,'PL','Reject'),(1,'PL','Approve')
insert into #t values(2,'PL','Approve'),(2,'PL','Approve'),(2,'PL','Approve')
SELECT
employeeID,
CASE WHEN count(DISTINCT status) = 1 THEN MAX(status) ELSE 'Partail' END [status]
FROM #t
GROUP BY employeeID
drop table #t
SELECT
CASE
WHEN COUNT(DISTINCT t.status) > 1 THEN 'Partial'
ELSE MAX(t.status)
END OveralLeaveStatus
FROM #t t
simply group by employee and check for max(status) <> min(status), if it is difference than it means at least one the status is different
select employeeID,
[status] = case when min([status]) <> max([status]) then 'Partial'
else min([status])
end
from #t
group by employeeID

Split Data and transforming them into Columns

I have an Input table as under
Id Data
1 Column1: Value1
2 Column2: Value11
3 Column3: Value111
4 Column1: Value2
5 Column2: Value22
6 Column3: Value222
I am looking for an output as under
Column1 Column2 Column3
Value1 Value11 Value111
Value2 Value22 Value222
How can I achieve so? It could have been done easily by using a WHILE LOOP and by a bit of mathematical logic, but I am looking for a more optimized one if possible by only SELECT queries (no LOOPS).
I have tried also by splitting using (':') as delimiter and then transforming ROWS to COLUMNS (PIVOT) but somewhat could not be able to proceed. (That's my thought, peoples may have more better thoughts).
My shot so far
Declare #t table(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222')
Select *
FROM #t
SELECT
F1.id,
F1.Data,
O.splitdata
FROM
(
SELECT *,
cast('<X>'+replace(F.Data,':','</X><X>')+'</X>' as XML) as xmlfilter from #t F
)F1
CROSS APPLY
(
SELECT fdata.D.value('.','varchar(50)') as splitdata
FROM f1.xmlfilter.nodes('X') as fdata(D)) O
This will work if you want a pure SQL solution:
Select [Column1], [Column2], [Column3] From (
Select col, val, id = ROW_NUMBER() over(partition by d.col order by d.id)
From (
Select id
, col = LEFT(Data, CHARINDEX(':', Data)-1)
, val = RIGHT(Data, LEN(DATA) - CHARINDEX(':', Data))
From #t
) as d
) as p
pivot(
MAX(val)
FOR col in([Column1], [Column2], [Column3])
) as piv
But it supposes that data for Row 1 are always before data for Row 2. There is no way to distinguish them using your sample.
If the number of column is not fixed, it has to use Dynamic SQL.
SQL Server may not be the best options for this kind of thing.
With Dynamic SQL, the above query would be like this one:
create table #t(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222')
Declare #sql nvarchar(max)
Select #sql = '
Select '+left(c, len(c)-1)+' From (
Select col, val, id = ROW_NUMBER() over(partition by d.col order by d.id)
From (
Select id
, col = LEFT(Data, CHARINDEX('':'', Data)-1)
, val = RIGHT(Data, LEN(DATA) - CHARINDEX('':'', Data))
From #t
) as d
) as p
pivot(
MAX(val)
FOR col in('+left(c, len(c)-1)+')
) as piv
'
From (
Select Distinct '['+LEFT(Data, CHARINDEX(':', Data)-1)+'], '
From #t
FOR XML PATH('')
) as d(c)
EXEC sp_executesql #sql
SQL Fiddle
This should work:
Declare #t table(Id int identity(1,1),Data varchar(1000))
Insert into #t Values
('Column1: Value1'),('Column2: Value11'),('Column3: Value111')
,('Column1: Value2'),('Column2: Value22'),('Column3: Value222');
WITH Splitted AS
(
SELECT *
,CAST('<X>'+REPLACE(F.Data,':','</X><X>')+'</X>' AS XML) AS xmlfilter
FROM #t AS F
)
SELECT p.*
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY xmlfilter.value('X[1]','varchar(max)') ORDER BY Id) AS Inx
,xmlfilter.value('X[1]','varchar(max)') AS ColName
,xmlfilter.value('X[2]','varchar(max)') AS ColVal
FROM Splitted
) AS tbl
PIVOT
(
MAX(ColVal) FOR ColName IN(Column1,Column2,Column3)
) AS p

Comparing two select statements

I need to write a stored procedure in T-SQL that would do the following:
Get a list of items related to a certain SectionID
Select and return all of the SectionIDs that have the same list of items or more (not less)
The table structure is as follows:
Section ID | Item Name
1 Item1
2 Item1
1 Item2
1 Item3
2 Item2
So if I pass 1 as an ID, this should not return anything as SectionID 2 only has 2 of the 3 items that SectionID = 1 has, but if I pass SectionID = 2 as the parameter, this should return SectionID = 1.
Hopefully, I explained that properly. What would be a good approach for this?
Here is a full example of what you need:
create table #test (
SectionID int,
ItemName varchar(10)
)
insert into #test values (1, 'Item1')
insert into #test values (2, 'Item1')
insert into #test values (1, 'Item2')
insert into #test values (1, 'Item3')
insert into #test values (2, 'Item2')
insert into #test values (3, 'Item1')
insert into #test values (3, 'Item2')
insert into #test values (3, 'Item3')
declare #test int
select #test = 3
declare #dist int
select #dist = count(distinct ItemName) from #test where SectionID = #test
select distinct t0.SectionID from #test t0
left join (select distinct SectionID, ItemName from #test where SectionID = #test) t1
on t0.ItemName = t1.ItemName and t0.SectionID != t1.SectionID
where t0.SectionID != #test
group by t0.SectionID
having count(distinct t1.ItemName) >= #dist
drop table #test
In your case you just need this part:
declare #test int
select #test = 3 --input argument from stored procedure
declare #dist int
select #dist = count(distinct ItemName) from tablename where SectionID = #test
select distinct t0.SectionID from tablename t0
left join (select distinct SectionID, ItemName from tablename where SectionID = #test) t1
on t0.ItemName = t1.ItemName and t0.SectionID != t1.SectionID
where t0.SectionID != #test
group by t0.SectionID
having count(distinct t1.ItemName) >= #dist
Assuming the following Table...
DECLARE #Sections AS TABLE (Id INT, Item VARCHAR(25))
INSERT INTO #Sections
(Id, Item)
SELECT 1, 'Item1'
UNION SELECT 2, 'Item1'
UNION SELECT 1, 'Item2'
UNION SELECT 1, 'Item3'
UNION SELECT 2, 'Item2'
You can do this...
DECLARE #SectionId INT, #ItemCount INT
SELECT #SectionId = 2 --You'd change this to whatever
, #ItemCount = 0
SELECT #ItemCount = COUNT(*)
FROM #Sections
WHERE Id = #SectionId
SELECT s.Id
FROM #Sections AS p
JOIN #Sections AS s
ON s.Id != p.Id
AND s.Item = p.Item
WHERE p.Id = #SectionId
GROUP BY s.Id
HAVING COUNT(*) >= #ItemCount

Resources