TSQL Distinct Counts - sql

I have a table that looks like this:
ID SuppressionTypeID PersonID
------------------------------
1 1 123
2 1 456
3 2 456
I want to get a rolling count (distinct people) rather than a normal group by count.
e.g. not this:
SuppressionTypeID Count
---------------------------
1 2
2 1
This:
SuppressionTypeID RecordsLost
----------------------------------
1 2
2 0
The latter being zero as we lost person 456 on suppresiontypeid 1.
Thanks in advance.

You may need to use a temporary table or a table variable as shown below
DECLARE #t TABLE (
ID INT
,SuppressionTypeID INT
,PersonID INT
)
INSERT INTO #t
SELECT 1
,1
,123
UNION ALL
SELECT 2
,1
,456
UNION ALL
SELECT 3
,2
,456
DECLARE #t1 TABLE (
ID INT
,SuppressionTypeID INT
,PersonID INT
,firstid INT
)
INSERT INTO #t1
SELECT *
,NULL
FROM #t
UPDATE t1
SET t1.firstid = t2.firstid
FROM #t1 AS t1
INNER JOIN (
SELECT personid
,min(SuppressionTypeID) AS firstid
FROM #t1
GROUP BY personid
) AS t2 ON t1.PersonID = t2.PersonID
SELECT coalesce(t2.firstid, t1.SuppressionTypeID) AS SuppressionTypeID
,count(DISTINCT t2.personid) AS count
FROM #t1 AS t1
LEFT JOIN #t1 AS t2 ON t1.personid = t2.personid
AND t1.SuppressionTypeID = t2.firstid
GROUP BY coalesce(t2.firstid, t1.SuppressionTypeID)
The result is
SuppressionTypeID count
----------------- -----------
1 2
2 0

You can try;
with tmp_tbl as (
select
x.SuppressionTypeID, count(x.PersonID) as RecordsLost
from (
select
min(SuppressionTypeID) as SuppressionTypeID,
PersonID
from tbl
group by PersonID
) as x
group by x.PersonID
order by x.SuppressionTypeID
)
select
distict t.SuppressionTypeID, coalesce(tmp.RecordsLost, 0) as RecordsLost
from tbl t
left join tmp_tbl tmp on tmp.SuppressionTypeID = t.SuppressionTypeID

Related

Case when duplicate add one more letter

For example: I have a table with these records below
1 A
2 A
3 B
4 C
...
and I need to migrate these record in to another table
1 AA
2 AB
3 B
4 C
...
Meaning if the record is duplicate, it will automatically add one more letter alphabetically.
Just a slightly different approach
Example
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',char(64+row_number() over ( partition by SomeCol order by ID ))) )
From #YourTable
Returns
ID SomeCol NewVal
1 A AA
2 A AB
3 B B
4 C C
EDIT - Requested UPDATE
Declare #YourTable Table (ID int,[SomeCol] varchar(50))
Insert Into #YourTable Values
(1,'A')
,(2,'A')
,(3,'B')
,(4,'C')
Select *
,NewVal = concat(SomeCol,IIF(sum(1) over (partition by SomeCol)=1,'',replace(char(63+row_number() over ( partition by SomeCol order by ID )),'#','')) )
From #YourTable
Returns
ID SomeCol NewVal
1 A A
2 A AA
3 B B
4 C C
We might be able to handle this requirement with the help of a calendar table mapping secondary letters to duplicate sequence counts:
WITH letters AS (
SELECT 1 AS seq, 'A' AS let UNION ALL
SELECT 2, 'B' UNION ALL
SELECT 3, 'C' UNION ALL
...
SELECT 26, 'Z' UNION ALL
...
),
cte AS (
SELECT id, let, ROW_NUMBER() OVER (PARTITION BY let ORDER BY id) rn,
COUNT(*) OVER (PARTITION BY let) cnt
FROM yourTable
)
SELECT t1.id, t1.let + CASE WHEN t1.cnt > 1 THEN t2.let ELSE '' END AS let
FROM cte t1
LEFT JOIN letters t2
ON t1.id = t2.seq
ORDER BY t1.id;
Demo

Distribute a header table value to another table several rows with the same id

I need help with SQL Server on how to distribute a header table value to another table several rows with the same id : -
Table A : -
Id | Value
---+--------
1 | 40
2 | 21
Table B : -
Id | Qty
---+-------
1 | 20
1 | 13
2 | 1
Result should be : -
Id | Value | Qty
--------------------
1 | 20 | 20
1 | 20 | 13
2 | 21 | 1
WITH TblA
AS (SELECT 1 AS id,
40 AS num
UNION
SELECT 2,
21),
TblB
AS (SELECT 1 AS id,
20 AS num
UNION
SELECT 1,
13
UNION
SELECT 2,
1)
SELECT *
FROM TblA a
INNER JOIN TblB b ON a.id = b.id;
This below logic will distribute the value equally to all rows.
SELECT *,
(SELECT Value FROM A WHERE A.id = B.id)/COUNT(*) OVER(PARTITION BY id) Value
FROM B
Output is-
Id Qty Value
1 13 20
1 20 20
2 1 21
But if you wants to distribute the same value for all rows, do this below-
SELECT *,
(SELECT Value FROM A WHERE A.id = B.id) Value
FROM B
OR
SELECT B.*,A.Value
FROM B
INNER JOIN A ON B.id= A.id
Output will be-
Id Qty Value
1 20 40
1 13 40
2 1 21
Substitute table variables #Header & #Detail with your tables and remove the insert statements. You could also do this with a sub query to your main query as proposed in some of the other answers but for larger tables it may become slow hence the table variable called #Counter to first gather the aggregated count based on the #Detail table variable.
declare #Header table
(
Id int identity(1, 1),
[Value] int
)
declare #Detail table
(
Id int identity(1, 1),
HeaderId int,
Qty int
)
declare #Counter table
(
HeaderId int,
[Count] int
)
insert into #Header values (40)
insert into #Header values (21)
insert into #Detail values (1, 20)
insert into #Detail values (1, 13)
insert into #Detail values (2, 1)
insert into #Counter
select d.HeaderId, count(d.HeaderId)
from #Detail d
group by d.HeaderId
select
h.Id,
h.[Value] / c.[Count] [Value],
d.Qty
from #Header h
join #Detail d
on h.Id = d.HeaderId
join #Counter c
on h.Id = c.HeaderId
Please try with this simple Join
SELECT ta.id,
ta.value / (SELECT ( Count(tmp.id) )
FROM tb AS tmp
WHERE tmp.id = ta.id
GROUP BY tmp.id) AS 'value',
tb.qty
FROM ta
INNER JOIN tb
ON ta.id = tb.id
Full example (check Image):
CREATE TABLE ta
(
id INT,
value INT
)
CREATE TABLE tb
(
id INT,
qty INT
)
INSERT INTO ta
VALUES (1,40),
(2,21)
SELECT *
FROM ta
INSERT INTO tb
VALUES (1,20),
(1,13),
(2,1)
SELECT *
FROM tb
SELECT *
FROM ta
SELECT *
FROM tb
SELECT ta.id,
ta.value / (SELECT ( Count(tmp.id) )
FROM tb AS tmp
WHERE tmp.id = ta.id
GROUP BY tmp.id) AS 'value',
tb.qty
FROM ta
INNER JOIN tb
ON ta.id = tb.id
Just use a window function to divide the value:
select tb.id, ta.value / count(*) over (partition by ta.id) as value, tb.qty
from ta join
tb
on ta.id = tb.id;
Note that because these are integers, you might want to avoid integer division by using:
select tb.id, ta.value * 1.0 / count(*) over (partition by ta.id) as value, tb.qty
Here is a db<>fiddle.

How can I select distinct by one column?

I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL

SQL Server Join two tables without duplicates in primary table

I have two tables that I want to join together such that all foreign rows are returned and the primary table's rows are not duplicated. For example:
T1
pk code value
1 One 100
2 Two 200
T2
fk value
1 10
1 15
1 30
2 25
I want all records of T2 without the T1 records duplicating, so the result set I want to look like this:
T2.fk T1.code T1.value T2.value
1 One 100 10
1 NULL NULL 15
1 NULL NULL 30
2 Two 200 25
Is there a SQL Server join method for achieving that?
You need to rank your rows in T2 and do a left join including rank as a join condition:
with cte as(select *, row_number() over(partition by fk order by value) as rn from T2)
select c.fk, t.code, t.value, c.value
from cte c
left join T1 t on c.fk = t.pk and c.rn = 1
Here is the full example:
DECLARE #t1 TABLE
(
pk INT ,
code VARCHAR(MAX) ,
value INT
)
INSERT INTO #t1
VALUES ( 1, 'One', 100 ),
( 2, 'Two', 200 )
DECLARE #t2 TABLE ( fk INT, value INT )
INSERT INTO #t2
VALUES ( 1, 10 ),
( 1, 15 ),
( 1, 30 ),
( 2, 25 );
WITH cte
AS ( SELECT * ,
ROW_NUMBER() OVER ( PARTITION BY fk ORDER BY value ) AS rn
FROM #t2
)
SELECT c.fk ,
t.code ,
t.value ,
c.value
FROM cte c
LEFT JOIN #t1 t ON c.fk = t.pk
AND c.rn = 1
Try this:
select T2.fk,
CASE
WHEN (SELECT COUNT(*) FROM t2 tother WHERE tother.fk = t2.fk
AND tother.value > t2.value) > 0 THEN NULL ELSE t1.code
END,
CASE
WHEN (SELECT COUNT(*) FROM t2 tother WHERE tother.fk = t2.fk
AND tother.value > t2.value) > 0 THEN NULL ELSE t1.value
END,T2.value
from t2
join t1
on t2.fk = t1.pk
DECLARE #t1 TABLE (pk int,code varchar(10),value int)
DECLARE #t2 TABLE (fk int,value int)
INSERT INTO #t1
SELECT 1,'one',100
UNION
SELECT 2,'two',200
INSERT INTO #t2
SELECT 1,10
UNION SELECT 1,15 UNION SELECT 1,30 UNION SELECT 2,25
;WITH cte AS(
SELECT t2.fk,t2.value t2val,t1.pk,t1.code,t1.value t1val,ROW_NUMBER() OVER(PARTITION BY fk ORDER BY fk) rno FROM #t2 t2 LEFT JOIN #t1 t1 on t2.fk=t1.pk)
SELECT fk,code=(CASE WHEN rno=1 THEN code ELSE null END),t1val=(CASE WHEN rno=1 THEN t1val ELSE NULL END),t2val FROM cte
output
fk code t1val t2val
1 one 100 10
1 NULL NULL 15
1 NULL NULL 30
2 two 200 25

Is it possible to write a sql query that is grouped based on a running total of a column?

It would be easier to explain with an example. Suppose I wanted to get at most 5 items per group.
My input would be a table looking like this:
Item Count
A 2
A 3
A 3
B 4
B 4
B 5
C 1
And my desired output would look like this:
Item Count
A 5
A>5 3
B 4
B>5 9
C 1
An alternative output that I could also work with would be
Item Count RunningTotal
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
I can use ROW_NUMBER() to get the top X records in each group, however my requirement is to get the top X items for each group, not X records. My mind is drawing a blank as to how to do this.
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.Item, t1.Count, sum(t2.count) as RunningTotal from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
Result:
Item Count RunningTotal
---- ----------- ------------
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
Considering the clarifications from your comment, you should be able to produce the second kid of output from your post by running this query:
select t.Item
, t.Count
, (select sum(tt.count)
from mytable tt
where t.item=tt.item and (tt.creating_user_priority < t.creating_user_priority or
( tt.creating_user_priority = t.creating_user_priority and tt.created_date < t.createdDate))
) as RunningTotal
from mytable t
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.row, t1.Item, t1.Count, sum(t2.count) as RunningTotal
into #RunTotal
from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
alter table #RunTotal
add GrandTotal int
update rt
set GrandTotal = gt.Total
from #RunTotal rt
left join (
select Item, sum(Count) Total
from #RunTotal rt
group by Item) gt
on rt.Item = gt.Item
select Item, max(RunningTotal)
from #RunTotal
where RunningTotal <= 5
group by Item
union
select a.Item + '>5', total - five
from (
select Item, max(GrandTotal) total
from #RunTotal
where GrandTotal > 5
group by Item
) a
left join (
select Item, max(RunningTotal) five
from #RunTotal
where RunningTotal <= 5
group by Item
) b
on a.Item = b.Item
I've updated the accepted answer and got your desired result.
SELECT Item, SUM(Count)
FROM mytable t
GROUP BY Item
HAVING SUM(Count) <=5
UNION
SELECT Item, 5
FROM mytable t
GROUP BY Item
HAVING SUM(Count) >5
UNION
SELECT t2.Item + '>5', Sum(t2.Count) - 5
FROM mytable t2
GOUP BY Item
HAVING SUM(Count) > 5
ORDER BY 1, 2
select 'A' as Name, 2 as Cnt
into #tmp
union all select 'A',3
union all select 'A',3
union all select 'B',4
union all select 'B',4
union all select 'B',5
union all select 'C',1
select Name, case when sum(cnt) > 5 then 5 else sum(cnt) end Cnt
from #tmp
group by Name
union
select Name+'>5', sum(cnt)-5 Cnt
from #tmp
group by Name
having sum(cnt) > 5
Here is what I have so far. I know it's not complete but... this should be a good starting point.
I can get your second output by using a temp table and an update pass:
DECLARE #Data TABLE
(
ID INT IDENTITY(1,1) PRIMARY KEY
,Value VARCHAR(5)
,Number INT
,Total INT
)
INSERT INTO #Data (Value, Number) VALUES ('A',2)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',5)
INSERT INTO #Data (Value, Number) VALUES ('C',1)
DECLARE
#Value VARCHAR(5)
,#Count INT
UPDATE #Data
SET
#Count = Total = CASE WHEN Value = #Value THEN Number + #Count ELSE Number END
,#Value = Value
FROM #Data AS D
SELECT
Value
,Number
,Total
FROM #Data
There may be better ways, but this should work.