Select based on group membership - sql

Suppose I have two tables t and o. I want to select all the rows of t in which t.feature = o.feature (ergo first two rows).
In addition, I also want to select all the rows whose t.grouping = t.grouping of the previously selected rows. In this case t.grouping 1, 2 ergo rows ('E', 1), ('F', 2) and ('G', 1). What is the most elegant way of achieving this?
CREATE TABLE t
( feature VARCHAR,
grouping BIGINT
);
INSERT INTO t (feature, grouping)
VALUES ('A', 1),
('B', 2),
('C', 5),
('D', 4),
('E', 1),
('F', 2),
('G', 1);
CREATE TABLE o
( feature VARCHAR
);
INSERT INTO o (feature)
VALUES ('A'),
('B');

If I understood correctly try this...
with cte as
(select grouping from t inner join o on t.feature=o.feature)
select t.*
from t
inner join cte on t.grouping=cte.grouping
or
select
*
from t
where grouping in
(select grouping from t inner join o on t.feature=o.feature )
DEMO

Related

Using lookup table vs. values for 'else' case when the list of possibilities is not defined

I am trying to design a lookup table, but I do not know how to design storage for values that do not match.
Example: for lookup values 'A', 'B' and 'C' I want lookup value 1, but for all the others (and the list is not defined) I want a value -1.
using case I would have written
CASE
WHEN VALUE IN('A','B','C') THEN 1
ELSE -1
END
But how to do the same using a lookup table?
You can use left join:
SELECT t.*,
COALESCE(v.result, -1)
FROM t LEFT JOIN
(VALUES ('A', 1), ('B', 1), ('C', 1)) v(value, result)
ON t.value = v.value;
You can include a default value as well, say using NULL:
WITH reference AS (
SELECT *
FROM (VALUES ('A', 1), ('B', 1), ('C', 1), (NULL, -1)
) v(value, result)
)
SELECT t.*,
COALESCE(r.result, r_default.result) as result
FROM t LEFT JOIN
reference r
ON r.value = t.value LEFT JOIN
reference r_default
ON r_default.value IS NULL;

Logic to check if exact ids (3+ records) are present in a group in SQL Server

I have some sample data like:
INSERT INTO mytable
([FK_ID], [TYPE_ID])
VALUES
(10, 1),
(11, 1), (11, 2),
(12, 1), (12, 2), (12, 3),
(14, 1), (14, 2), (14, 3), (14, 4),
(15, 1), (15, 2), (15, 4)
Now, here I am trying to check if in each group by FK_ID we have exact match of TYPE_ID values for 1, 2 & 3.
So, the expected output is like:
(10, 1) this should fail
As in group FK_ID = 10 we only have one record
(11, 1), (11, 2) this should also fail
As in group FK_ID = 11 we have two records.
(12, 1), (12, 2), (12, 3) this should pass
As in group FK_ID = 12 we have two records.
And all the TYPE_ID are exactly matching 1, 2 & 3 values.
(14, 1), (14, 2), (14, 3), (14, 4) this should also fail
As we have 4 records here.
(15, 1), (15, 2), (15, 4) this should also fail
Even though we have three records, it should fail as the TYPE_ID here (1, 2, 4) are not matching with required match (1, 2, 3).
Here is my attempt:
select * from mytable t1
where exists (select COUNT(t2.TYPE_ID)
from mytable t2 where t2.FK_ID = t1.FK_ID
and t2.TYPE_ID IN (1, 2, 3)
group by t2.FK_ID having COUNT(t2.TYPE_ID) = 3);
This is not working as expected, because it also pass for FK_ID = 14 which has four records.
Demo: SQL Fiddle
Also, how we can make it generic so that if we need to check for 4 or more TYPE_ID values like (1,2,3,4) or (1,2,3,4,5), we can do that easily by updating few values.
The following query will do what you want:
select fk_id
from t
group by fk_id
having sum(case when type_id in (1, 2, 3) then 1 else 0 end) = 3 and
sum(case when type_id not in (1, 2, 3) then 1 else 0 end) = 0;
This assumes that you have no duplicate pairs (although depending on how you want to handle duplicates, it might be as easy as using, from (select distinct * from t) t).
As for "genericness", you need to update the in lists and the 3.
If you want something more generic:
with vals as (
select id
from (values (1), (2), (3)) v(id)
)
select fk_id
from t
group by fk_id
having sum(case when type_id in (select id from vals) then 1 else 0 end) = (select count(*) from vals) and
sum(case when type_id not in (select id from vals) then 1 else 0 end) = 0;
You can use this code:
SELECT y.fk_id FROM
(SELECT x.fk_id, COUNT(x.type_id) AS count, SUM(x.type_id) AS sum
FROM mytable x GROUP BY (x.fk_id)) AS y
WHERE y.count = 3 AND y.sum = 6
For making it generic, you can equal y.count with N and y.sum with N*(N-1)/2, where N is the number you are looking for (1, 2, ..., N).
You can try this query. COUNT and DISTINCT used for eliminate duplicate records.
SELECT
[FK_ID]
FROM
#mytable T
GROUP BY
[FK_ID]
HAVING
COUNT(DISTINCT CASE WHEN [TYPE_ID] IN (1,2,3) THEN [TYPE_ID] END) = 3
AND COUNT(CASE WHEN [TYPE_ID] NOT IN (1,2,3) THEN [TYPE_ID] END) = 0
Try this:
select FK_ID,count(distinct TYPE_ID) from mytable
where TYPE_ID<=3
group by FK_ID
having count(distinct TYPE_ID)=3
You should use CTE with Dynamic pass Value which you have mentioned in Q.
WITH CTE
AS (
SELECT FK_ID,
COUNT(*) CNT
FROM #mytable
GROUP BY FK_ID
HAVING COUNT(*) = 3) <----- Pass Value here What you want to Display Result,
CTE1
AS (
SELECT T.[ID],
T.[FK_ID],
T.[TYPE_ID],
ROW_NUMBER() OVER(PARTITION BY T.[FK_ID] ORDER BY
(
SELECT NULL
)) RN
FROM #mytable T
INNER JOIN CTE C ON C.FK_ID = T.FK_ID),
CTE2
AS (
SELECT C1.FK_ID
FROM CTE1 C1
GROUP BY C1.FK_ID
HAVING SUM(C1.TYPE_ID) = SUM(C1.RN))
SELECT TT1.*
FROM CTE2 C2
INNER JOIN #mytable TT1 ON TT1.FK_ID = C2.FK_ID;
From above SQL Command which will produce Result (I have passed 3) :
ID FK_ID TYPE_ID
4 12 1
5 12 2
6 12 3

SQL count multiple cells as combination

I have the following SQL table which shows case number and their value, the case number always appear 2 cases in a group, I want to count how many the combinations with same case number appearing in the table. Be ware the order could be different, see case A and C, both of them should be count as the same combination.
case value
A 1992
A 1956
B 2000
B 2001
C 1956
C 1992
The goal is to get the total number of each combination, so the output format doesn't matter. One of expected result:
Seq value frequency
1 1992 2
1 1956 2
2 2000 1
2 2001 1
What about if there are 3 cases as a combination?
This works with any number of values for any case. It only increment frequency count when cases have the same number of values and each one have a match, no matter in which order.
CREATE TABLE #Table1
([Case] varchar(1), [Value] int)
;
INSERT INTO #Table1
([Case], [Value])
VALUES
('A', 1992), ('A', 1956), ('A', 1997), ('B', 2000), ('B', 2001), ('C', 1956),
('C', 1992), ('C', 1997), /*('C',1993),*/ ('D', 2005), ('D', 2008), ('E', 1956),
('E', 1992) , ('F', 1956), ('F', 1992), ('G', 1956), ('G', 1992) ;
--Query
select min(a.[Case]) [Case], [Values], count(*) frequency
from (
SELECT t.[Case],
stuff(
(
select ',' + cast (t1.[Value] as varchar(20))
from #table1 t1
where t1.[Case] = t.[Case]
order by t1.[Value]
for xml path('')
),1,1,'') [Values]
FROM #table1 t
GROUP BY t.[Case]
)a
group by [Values]
order by [Case]
Result whith values sorted in ascending order
Case Values frequency
A 1956,1992,1997 2
B 2000,2001 1
D 2005,2008 1
E 1956,1992 3
Data sample, SQL Server 2014
CREATE TABLE Table1
([case] varchar(1), [value] int)
;
INSERT INTO Table1
([case], [value])
VALUES
('A', 1992),
('A', 1956),
('B', 2000),
('B', 2001),
('C', 1956),
('C', 1992)
;
Query
select dense_rank() over (ORDER BY min(a.[case])) seq, a.value, count(*) freq
from table1 a left join table1 b
on a.value=b.value and a.[case]<>b.[case]
group by a.value
order by a.value
http://sqlfiddle.com/#!6/40a87/3
This is not exactly as you post expected results, but respond on what you just request in the previous comment.
select min, max, count (*) freq
from (
select a.[case] [case], min(a.value) min, max(a.value) max
from table1 a
group by a.[case]) b
group by min, max
order by min, max
http://sqlfiddle.com/#!6/40a87/18

Delete rows in table that are sum of other rows per group

Group rows by T, and in each group find the row that is the largest or smallest (if values are negative) sum of other rows from that group, and delete that row (one for each group), if group does not have enough elements to find sum or enough but none of the rows indicates sum of others nothing happens
CREATE TABLE Test (
T varchar(10),
V int
);
INSERT INTO Test
VALUES ('A', 4),
('B', -5),
('C', 5),
('A', 2),
('B', -1),
('C', 10),
('A', 2),
('B', -4),
('C', 5),
('D', 0);
expected result:
A 2
A 2
B -1
B -4
C 5
C 5
D 0
Like the comments, the requirements seem strange. The below code assumes that the summing is already pre-populated and merely removes the largest/smallest as long as the highest value is not 0.
if object_id('tempdb..#test') is not null
drop table #test
CREATE TABLE #Test (
T varchar(10),
V int
);
INSERT INTO #Test
VALUES ('A', 4), ('B', -5), ('C', 5), ('A', 2), ('B', -1), ('C', 10), ('A', 2), ('B', -4), ('C', 5), ('D', 0);
if object_id('tempdb..#test2') is not null
drop table #test2
SELECT
T,
V,
ABS(V) as absV
INTO #TEST2
FROM #TEST
SELECT * FROM #TEST2
if object_id('tempdb..#max') is not null
drop table #max
SELECT
T,
MAX(absV) AS MaxAbsV
INTO #Max
FROM #TEST2
GROUP BY T
HAVING MAX(AbsV) != 0
DELETE #TEST2
FROM #TEST2
INNER JOIN #MAX ON #TEST2.T = #MAX.T AND #TEST2.absV = #Max.MaxAbsV
SELECT * FROM #TEST2
ORDER BY T ASC
; with cte as
(
select T, V,
R = row_number() over (partition by T order by ABS(V) desc),
C = count(*) over (partition by T)
from Test
)
delete c
from cte c
inner join
(
select T, S = sum(V)
from cte
where R <> 1
group by T
) s on c.T = s.T
where c.C >= 3
and c.R = 1
and c.V = s.S
Using ABS and NOT Exists
DECLARE #Test TABLE (
T varchar(10),
V int
);
INSERT INTO #Test
VALUES ('A', 4), ('B', -5), ('C', 5), ('A', 2), ('B', -1), ('C', 10), ('A', 2), ('B', -4), ('C', 5), ('D', 0);
;WITH CTE as (
select T,max(ABS(v ))v from #Test
WHERE V <> 0
GROUP BY T )
SELECT T,V FROM #Test T where NOT exists (Select 1 FROM cte WHERE T = T.T AND v = ABS(T.V) )
ORDER BY T.T
Determine first if the rows are positive or negative by checking if SUM(V) is positive. And then determine if the smallest or largest value is equal to the SUM of the other rows, by subtracting from SUM(V) the MIN(V) if negative or MAX(V) if positive:
DELETE t
FROM Test t
INNER JOIN (
SELECT
T,
SUM(V) - CASE WHEN SUM(V) >= 0 THEN MAX(V) ELSE MIN(V) END AS ToDelete
FROM Test
GROUP BY T
HAVING COUNT(*) >= 3
) a
ON a.T = t.T
AND a.ToDelete = t.V
ONLINE DEMO
You can use the below query to get the required output :-
select * into #t1 from test
select * from
(
select TT.T as T,TT.V as V
from test TT
JOIN
(select T,max(abs(V)) as V from #t1
group by T) P
on TT.T=P.T
where abs(TT.V) <> P.V
UNION ALL
select A.T as T,A.V as V from test A
JOIN(
select T,count(T) as Tcount from test
group by T
having count(T)=1) B on A.T=B.T
) X order by T
drop table #t1
You are looking for a value per group that is the sum of all the group's other values. E.g. 4 of (2,2,4) or -5 of (-5,-4,-1).
This is usually only one record per group. But it can be multiple times the same number. Here are examples for ties: (0,0) or (-2,2,4,4), or (-2,-2,4,4,4) or (-10,3,3,3,3,4).
As you see, you are looking in any way for values that equal half of the group's total sum. (Of course. We are looking for n+n, where one n is in one record and the other n is the sum of all the other records.)
The only special case is when there is only one value in the group which is zero. That we don't want to delete of course.
Here is an update statement that cannot deal with ties, but would delete all maximum values instead of just one:
delete from test
where 2 * v =
(
select case when count(*) = 1 then null else sum(v) end
from test fullgroup
where fullgroup.t = test.t
);
In order to deal with ties you would need artificial row numbers, so as to delete only one record of all candidates.
with candidates as
(
select t, v, row_number() over (partition by t order by t) as rn
from
(
select
t, v,
sum(v) over (partition by t) as sumv,
count(*) over (partition by t) as cnt
from test
) comparables
where sumv = 2 * v and cnt > 1
)
delete
from candidates
where rn = 1;
SQL fiddle: http://sqlfiddle.com/#!6/6d97e/1
See if below query helps:
DELETE [Audit].[dbo].[Test] FROM [Audit].[dbo].[Test] as AA
INNER JOIN (select T,
CASE
WHEN MAX(V) < 0 THEN MIN(V)
WHEN MIN(V) > 0 THEN MAX(V) ELSE MAX(V)
END as MAX_V,
CASE
WHEN SUM(V) > 0 THEN SUM(V) - MAX(V)
WHEN SUM(V) < 0 THEN SUM(V) - MIN(V) ELSE SUM(V)
END as SUM_V_REST
from [Audit].[dbo].[Test]
Group by T
Having Count(V) > 1) as BB ON AA.T = BB.T and AA.V = BB.MAX_V

SQL - ALL, Including all values

I have two tables:
create table xyz
(campaign_id varchar(10)
,account_number varchar)
Insert into xyz
values ( 'A', '1'), ('A', '5'), ('A', '7'), ('A', '9'), ('A', '10'),
( 'B', '2'), ('B', '3'),
( 'C', '1'), ('C', '2'), ('C', '3'), ('C', '5'), ('C', '13'), ('C', '15'),
('D', '2'), ('D', '9'), ('D', '10')
create table abc
(account_number varchar)
insert into abc
values ('1'), ('2'), ('3'), ('5')
Now, I want to write a query where all the four account_number 1, 2, 3, 5 are included in a Campaign_id.
The answer is C.
[My aim is to find the Campaign Code that includes account_number 1, 2, 3 & 5. This condition is only satisfied by campaign code C.]
I tried using IN and ALL, but don't work. Could you please help.
I think what you are after is a inner join. Not sure from your questions which way around you want your data. However this should give you a good clue how to procede and what keywords to lock for in the documentation to go further.
SELECT a.*
FROM xyz a
INNER JOIN abc b ON b.account_number = a.account_number;
EDIT:
Seems I misunderstood the original question.. sorry. To get what you want you can just do:
SELECT campaign_id
FROM xyz
WHERE account_number IN ('1', '2', '3', '5')
GROUP BY campaign_id
HAVING COUNT(DISTINCT account_number) = 4;
This is called relational division if you want to investigate further.
SELECT campaign_id
FROM (
SELECT campaign_id, COUNT(*) AS c, total_accounts
FROM xyz
JOIN abc ON xyz.account_number = abc.account_number
CROSS JOIN (SELECT COUNT(*) AS total_accounts
FROM abc) AS x
GROUP BY campaign_id
HAVING c = total_accounts) AS subq
DEMO
select xyz.campaign_id
from xyz
join abc
on xyz.account_number = abc.account_number
group by xyz.campaign_id
having count(xyz.campaign_id) =
(select count(account_number) from abc);
Caution: t-sql implementation