SQL Server : SELECT ID having only a single condition - sql

I have a patients table with details such as conditions that the patient has. from the below table I want to select Patients, Claims which have ONLY a single condition - 'Hypertension'. Example Patient B is the expected output. Patient A will not be selected because he claimed for multiple conditions.
+----+---------+--------------+
| ID | ClaimID | Condition |
+----+---------+--------------+
| A | 14234 | Hypertension |
| A | 14234 | Diabetes |
| A | 63947 | Diabetes |
| B | 23853 | Hypertension |
+----+---------+--------------+
I tried using the NOT IN condition as below but doesn't seem to help
SELECT ID, ClaimID, Condition
FROM myTable
WHERE Condition IN ('Hypertension')
AND Condition NOT IN ('Diabetes')

One method uses not exists:
select t.*
from mytable t
where t.condition = 'Hypertension' and
not exists (select 1
from mytable t2
where t2.id = t.id and t2.condition <> t.condition
);

Or you can do it like this:
select
id,
claim_id,
condition
from
patient
where
id in
(
select
id
from
patient
group by
id having count (distinct condition) = 1
);
Result:
id claim_id condition
-- ----------- ----------------
B 23853 Hypertension
(1 rows affected)
Setup:
create table patient
(
id varchar(1),
claim_id int,
condition varchar(16)
);
insert into patient (id, claim_id, condition) values ('A', 14234, 'Hypertension');
insert into patient (id, claim_id, condition) values ('A', 14234, 'Diabetes');
insert into patient (id, claim_id, condition) values ('A', 63947, 'Diabetes');
insert into patient (id, claim_id, condition) values ('B', 23853, 'Hypertension');

You can do this with a CTE.
I set up this CTE with two parameters, one being the Condition you seek, and the other being the max number of combined conditions to find (in your case 1).
DECLARE #myTable TABLE (Id VARCHAR(1), ClaimID INT, Condition VARCHAR(100))
INSERT INTO #myTable (Id, ClaimID, Condition)
SELECT 'A',14234,'Hypertension' UNION ALL
SELECT 'A',14234,'Diabetes' UNION ALL
SELECT 'A',63947,'Diabetes' UNION ALL
SELECT 'B',23853,'Hypertension'
DECLARE #Condition VARCHAR(100)
DECLARE #MaxConditions TINYINT
SET #Condition='Hypertension'
SET #MaxConditions=1
; WITH CTE AS
(
SELECT *, COUNT(2) OVER(PARTITION BY ClaimID) AS CN
FROM #myTable T1
WHERE EXISTS (SELECT 1 FROM #myTable T2 WHERE T1.ClaimID=T2.ClaimID AND T2.Condition=#Condition)
)
SELECT *
FROM CTE
WHERE CN<=#MaxConditions
If you don't care about the fluff, and just want all ClaimID's with just ONE condition regardless of which condition it is use this.
DECLARE #myTable TABLE (Id VARCHAR(1), ClaimID INT, Condition VARCHAR(100))
INSERT INTO #myTable (Id, ClaimID, Condition)
SELECT 'A',14234,'Hypertension' UNION ALL
SELECT 'A',14234,'Diabetes' UNION ALL
SELECT 'A',63947,'Diabetes' UNION ALL
SELECT 'B',23853,'Hypertension'
DECLARE #MaxConditions TINYINT
SET #MaxConditions=1
; WITH CTE AS
(
SELECT *, COUNT(2) OVER(PARTITION BY ClaimID) AS CN
FROM #myTable T1
)
SELECT *
FROM CTE
WHERE CN<=#MaxConditions

Here is one method using Having clause
SELECT t.*
FROM mytable t
WHERE EXISTS (SELECT 1
FROM mytable t2
WHERE t2.id = t.id
HAVING Count(CASE WHEN condition = 'Hypertension' THEN 1 END) > 0
AND Count(CASE WHEN condition != 'Hypertension' THEN 1 END) = 0)

And yet a couple of other ways to do this:
declare #TableA table(Id char,
ClaimId int,
Condition varchar(250));
insert into #TableA (id, claimid, condition)
values ('A', 14234, 'Hypertension'),
('A', 14234, 'Diabetes'),
('A', 63947, 'Diabetes'),
('B', 23853, 'Hypertension')
select id, claimid, condition
from #TableA a
where not exists(select id
from #TableA b
where a.id = b.id
group by b.id
having count(b.id) > 1)
OR
;with cte as
(
select id, claimid, condition
from #TableA
)
,
cte2 as
(
Select id, count(Id) as counts
from cte
group by id
having count(id) < 2
)
Select cte.id, claimid, condition
From cte
inner join
cte2
on cte.id = cte2.id

I decided to revise my answer into an appropriate one.
A simple solution to your question is to count the rows instead of the ID values (since it's not an integer).
Here is a simple introduction:
SELECT
ID
FROM
#PatientTable
GROUP BY
ID
HAVING
ID = ID AND COUNT(*) = 1
This will Return the ID B
+----+
| ID |
+----+
| B |
+----+
Surely, this is not enough, as you may work with a large data and need more filtering.
So, we will go and use it as a sub-query.
Using it as a sub-query it's simple :
SELECT
ID,
ClaimID,
Condition
FROM
#PatientTable
WHERE
ID = (SELECT ID AS NumberOfClaims FROM #PatientTable GROUP BY ID HAVING ID = ID AND COUNT(*) = 1)
This will return
+----+---------+--------------+
| ID | ClaimID | Condition |
+----+---------+--------------+
| B | 23853 | Hypertension |
+----+---------+--------------+
So far so good, but there is another issue we may face. Let's say you have a multiple Claims from a multiple patients, using this query as is will only show one patient. To show all patients we need to use IN rather than = under the WHERE clause
WHERE
ID IN (SELECT ID AS NumberOfClaims FROM #PatientTable GROUP BY ID HAVING ID = ID AND COUNT(*) = 1)
This will list all patients that falls under this condition.
If you need more conditions to filter, you just add them to the WHERE clause and you'll be good to go.

SELECT id, sum(ct)
FROM (SELECT customer_id, CASE WHEN category = 'X' THEN 0 else 1
end ct
FROM MASTER_TABLE
) AS t1
GROUP BY id
HAVING sum(ct) = 0
id which will have sum(ct) more than 1, will have multiple conditions

Use joins instead of subquery. Joins are always better in performance. You can use below query.
SELECT T1.id, T1.claimid, T1.Condition
FROM mytable T1
INNER JOIN
(
select id, count(Condition) counter
from mytable
group by id HAVING COUNT(DISTINCT CONDITION)=1
) T2 ON T1.ID=T2.ID
WHERE T2.counter=1

Related

Select rows that contains unique value ( non-redundant)

Given this table:
id
Name
date
21
B1
2022-02-26
21
B
2022-02-26
01
C1
2022-02-26
04
T1
2022-02-16
04
T
2022-02-16
PS: This table is the result of join request.
I want to select the rows with corrurent day where Id doesn't repeated like:
id
Name
date
01
C1
2022-02-26
How I can do that with SQL query please.
DB
CREATE TABLE test (
id INT,
name CHAR,
date DATE
);
INSERT INTO test VALUES (1, 'A', '2022-02-22');
INSERT INTO test VALUES (1, 'B', '2022-02-21');
INSERT INTO test VALUES (2, 'C', '2022-02-20');
INSERT INTO test VALUES (3, 'D', '2022-02-19');
QUERY
select * from test
where id in (select id from test group by id having count(*) = 1)
OUTPUT
id name date
2 C 2022-02-20
3 D 2022-02-19
db-fiddle
with cte as
(
select id, count(Name) as cnt
from TableName
group by id
)
select cte.id, tableName.Name
from cte
inner join TableName on
cte.id = tableName.id
where cte.cnt = 1
select id,name from test
where ID NOT IN ( select id from test group by id having count(*)> 1)
select * from test where id not in
(select distinct id from
(select *,row_number ()
over (partition by id order by Name asc) rn
from test
) b
where rn>1)
Assuming the table name is Test. The below query would work.
Select *
From Test
Group by Id
Having count(*) = 1
The most efficient way to do this is likely to be using window functions to check if there is another row
SELECT *
FROM (
SELECT *,
LEAD(id) OVER (PARTITION BY id ORDER BY Name) rn
FROM YourTable t
) t
WHERE t.nxt IS NULL;

Basic code optimization, exclusion of double grouping

I want to show only one row per column (PN) from result of calculations base on 2 indicators (WK) and (Prio), and I think that doing this in way below using double grouping is... stupid, but I see no other solution. Is there other way to reach same result as query below?
CREATE TABLE #table
(
[PN] varchar(3) null
,[WK] int null
,[Prio] int null
);
INSERT INTO #table
(
[PN]
,[WK]
,[Prio]
)
VALUES
('AAA',37,1)
,('AAA',37,2)
,('AAA',38,3)
,('BBB',39,1)
,('BBB',39,2)
,('BBB',37,3)
,('BBB',38,4)
,('CCC',null,1)
,('CCC',null,2)
,('CCC',37,3)
,('CCC',38,4);
SELECT GTG.[PN]
,GTG.[WK]
,MIN([Prio]) [Prio]
FROM
(
SELECT [PN]
,MIN([WK]) [WK]
FROM #table
GROUP BY [PN]
) GTG
LEFT JOIN #table TMP
ON GTG.[PN] = TMP.[PN]
and GTG.[WK] = TMP.[WK]
GROUP BY GTG.[PN],GTG.[WK];
DROP TABLE #table;
Try using a Common Table Expression (CTE) with ROW_NUMBER():
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE MyTable
(
[PN] varchar(3) null
,[WK] int null
,[Prio] int null
);
INSERT INTO MyTable
(
[PN]
,[WK]
,[Prio]
)
VALUES
('AAA',37,1)
,('AAA',37,2)
,('AAA',38,3)
,('BBB',39,1)
,('BBB',39,2)
,('BBB',37,3)
,('BBB',38,4)
,('CCC',null,1)
,('CCC',null,2)
,('CCC',37,3)
,('CCC',38,4);
Query 1:
;WITH CTE
AS
( SELECT [PN],
[WK],
[Prio],
ROW_Number() OVER (Partition BY [PN] ORDER BY COALESCE([WK], 999), [Prio]) AS RN
FROM MyTable T1
)
SELECT [PN], [WK], [Prio]
FROM CTE
WHERE RN = 1
Results:
| PN | WK | Prio |
|-----|----|------|
| AAA | 37 | 1 |
| BBB | 37 | 3 |
| CCC | 37 | 3 |
do you find below
SELECT GTG.[PN]
,min(GTG.[WK])
,MIN([Prio]) [Prio]
FROM
#table GTG
LEFT JOIN #table TMP
ON GTG.[PN] = TMP.[PN]
and GTG.[WK] = TMP.[WK]
GROUP BY GTG.[PN]
You seem to want window functions. This should do what you want:
select t.*
from (select t.*, row_number() over (partition by pn order by coalesce(wk, 999), prio) as seqnum
from #table t
) t
where seqnum = 1;
Here is a db<>fiddle.
Note: wk possibly represents a week value, so I replaced it with 999 when it is NULL for the sorting to be correct. You could filter out NULL values or use a CASE expression if COALESCE() does not quite meet your needs.
I think, we can use Row_Number window function to sort result first and then apply where clause. Please try this-
;with cte as (
select
pn,
wk,
prio,
row_number() over (
partition by pn
order by (case when wk is not null then 0 else 1 end), wk, prio
) as rankid
from #table
)
select pn,wk,prio
from cte
where rankid =1;

trying to flatten rows into columns

I have a group of rows in a table that have an id. I am trying to flatten it out in rows with multiple column. I am almost certain I have done this with a cte and maybe partition.
I have used cte's to delete duplicate data and I thought I has done something similar to what I am trying to accomplish here. I was able to come up with workable solution (listed below) but still feel like a more elegant solution should be available.
CREATE TABLE #MyTable ( RowID int , field VARCHAR(10), value VARCHAR(10))
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 1, 'first', 'neil' )
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 2, 'first', 'bob' )
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 3, 'first', 'tom' )
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 1, 'last', 'young' )
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 2, 'last', 'dylan' )
INSERT INTO #MyTable ( RowID, field, value ) VALUES ( 3, 'last', 'petty' )
SELECT * FROM #mytable
--trying to accomplish this with cte/partition:
SELECT rowid,
[first] = (Select value FROM #mytable where field = 'first' and rowid = t.rowid),
[last] = (Select value FROM #mytable where field = 'last' and rowid = t.rowid)
FROM #mytable t
GROUP BY rowid
This data transformation is known as a PIVOT. In SQL Server 2005+ there is a function that will perform this process. :
select *
from
(
SELECT *
FROM mytable
) src
pivot
(
max(value)
for field in (first, last)
) piv
See SQL Fiddle with Demo.
Or you can use an aggregate function with a CASE expression:
select rowid,
max(case when field = 'first' then value end) first,
max(case when field = 'last' then value end) last
from MyTable
group by rowid
See SQL Fiddle with Demo.
You can also use multiple joins on your table:
select t1.rowid,
t1.value first,
t2.value last
from mytable t1
left join mytable t2
on t1.rowid = t2.rowid
and t2.field = 'last'
where t1.field = 'first'
See SQL Fiddle with Demo
The result for all versions is the same:
| ROWID | FIRST | LAST |
-------------------------
| 1 | neil | young |
| 2 | bob | dylan |
| 3 | tom | petty |

SQL select Distinct with where clause

I have i table like this.
PersonID, KvalifikationId
1 1
1 2
1 3
2 1
2 3
I want to write SQL querye returning all persons, that have not kvalifikation 2.
i Wrote
SELECT DISTINCT PersonID where NOT KvalifikationID = 2
But this return both person 1 and person 2.
How do i make select that only return personId's that not have kval2 ?
Try this,
SELECT DISTINCT PersonID
FROM tableName
WHERE PersonID NOT IN
(
SELECT PersonID
FROM tableName
WHERE KvalifikationId = 2
)
SQLFiddle Demo
Declare #t table(PersonID int,KvalifikationId int)
Insert Into #t Select 1 ,1
Insert Into #t Select 1, 2
Insert Into #t Select 1,3
Insert Into #t Select 2 ,1
Insert Into #t Select 2,3
Select PersonId From #t
Except
Select PersonID From #t where KvalifikationId = 2
Result
PersonId
2
By using your Person table rather than your N:N table in the outer query you can skip the distinct and the anti semi join to the sub query will have better performance since it is on a clustered index. (assuming PersonID is pk in the Person table)
SELECT PersonID
FROM tblPerson
WHERE NOT EXISTS
(
SELECT NULL
FROM tblPersonKvalifikation
WHERE KvalifikationId = 2 AND
tblPerson.PersonID = tblPersonKvalifikation.PersonID
)
SELECT DISTINCT person_id
FROM tableName t1
WHERE not exists
(
select 1
from tableName
where person_id = t1.person_id and KvalifikationId = 2
)
try this.
SELECT DISTINCT PersonID from tableName
WHERE KvalifikationId NOT IN ('2');

Sql query to return elements with same ID as comma separated string

I have two tables, table1 has a entry_ID, entry_date and other entry information. table2 has entry_ID and entry_subject. Each entry_ID can have arbitrarily many entry_subjects.
I want a query that will return an entry_ID, entry_date, and a list of the subjects corresponding to that entry separated by commas.
The first step in this seems to be just getting a query that returns an entry_ID and a comma separated list of subjects from table2. Once I have that the join should be easy.
I adapted the recursive CTE method from this site: to fit my case:
WITH RECURSIVE CTE (entry_ID, subjectlist, subject, length)
AS ( SELECT entry_ID, cast( '' as varchar(8000))
, cast( '' as varchar(8000)), 0
FROM table2
GROUP BY entry_ID
UNION ALL
SELECT t2.entry_ID,
cast(subjectlist || CASE length = 0 THEN '' ELSE ', ' END
|| entry_subject AS varchar(8000) ),
cast (t2.entry_subject as varchar(8000)),
length +1
FROM CTE c
INNER JOIN table2 t2
on c.entry_ID=t2.entry_ID where t2.entry_subject > c.subject)
SELECT entry_ID, subjectlist FROM (
SELECT entry_ID, subjectlist, RANK() OVER (
PARTITION BY entry_ID order by length DESC)
FROM CTE) D (entry_ID, subjectlist, rank) where rank = 1;
And it works, I get the response I expect. To achieve my final goal the query I use is this:
SELECT t1.* t2.subjectlist FROM table1
JOIN (ABOVE QUERY) AS t2 on t1.entry_ID=t2.entry_ID;
This seems very unwieldy. Is this really the best way to do this?
If I understand correctly, then there should be a much simpler solution.
Test setup
According to your description - you could have done that for us:
CREATE TABLE table1 (
entry_id int4 PRIMARY KEY
, entry_date date
);
CREATE TABLE table2 (
entry_id int4 REFERENCES table1 (entry_id)
, entry_subject text
, PRIMARY KEY (entry_id, entry_subject)
);
INSERT INTO table1 VALUES (1, '2011-09-01'), (2, '2011-09-02'),(3, '2011-09-03');
INSERT INTO table2 VALUES (1, 'foo1'), (2, 'foo2'), (2, 'bar2')
, (3, 'foo3'), (3, 'baz3'), (3, 'bar3');
Answer
string_agg() requires Postgres 9.0+
SELECT t1.entry_id, t1.entry_date
, string_agg(t2.entry_subject, ', ') AS entry_subjects
FROM table1 t1
JOIN table2 t2 USING (entry_id)
GROUP BY 1,2
ORDER BY 1;
entry_id | entry_date | entry_subjects
----------+------------+------------------
1 | 2011-09-01 | foo1
2 | 2011-09-02 | bar2, foo2
3 | 2011-09-03 | baz3, bar3, foo3
Or, if you want the entry_subjects sorted:
SELECT DISTINCT ON (1)
t1.entry_id
, t1.entry_date
, string_agg(t2.entry_subject, ', ') OVER (
PARTITION BY t1.entry_id ORDER BY t2.entry_subject
RANGE BETWEEN UNBOUNDED PRECEDING
AND UNBOUNDED FOLLOWING) AS entry_subjects
FROM table1 t1
JOIN table2 t2 USING (entry_id)
ORDER BY 1;
entry_id | entry_date | entry_subjects
----------+------------+------------------
1 | 2011-09-01 | foo1
2 | 2011-09-02 | bar2, foo2
3 | 2011-09-03 | bar3, baz3, foo3
You could do the same with a subselect on table2 to first ORDER BY entry_subject.