SQL select statement that returns records where a set of events have occurred more than once on the same ID - sql

Say I have a table:
CREATE TABLE births
(
childid INT,
momid INT,
eclampsia VARCHAR(1),
preeclampsia VARCHAR(1),
hypertension VARCHAR(1)
);
Insert records:
INSERT INTO BIRTHS (CHILDID, MOMID, ECLAMPSIA)
VALUES (654321, 123456, 'Y'),
(654321, 123456, 'Y'),
INSERT INTO BIRTHS (CHILDID, MOMID, HYPERTENSION)
VALUES (987652, 465468, 'Y'),
(987987, 465468, 'Y')
INSERT INTO BIRTHS (CHILDID, MOMID)
VALUES (687765, 465468)
INSERT INTO BIRTHS (CHILDID, MOMID, PREECLAMPSIA)
VALUES (649870, 846587, 'Y')
INSERT INTO BIRTHS (CHILDID, MOMID)
VALUES (787463, 846587);
I want to return records for all mothers who have had more than one child and have had one of these three diagnoses in more than one pregnancy.
My expected results are:
child momid eclampsia preeclampsia hypertension
-------------------------------------------------------------
654321 123456 Y
431265 123456 Y
987652 465468 Y
987987 465468 Y
How would I do write this?
I have a sloppy query that does not quite do what I want. It works to some degree, but still gives me records where the momid has had a diagnosis only for one pregnancy.
select distinct
a.*, b.eclampsia, b.preeclampsia, b.hypertension
from
births a
join
births b on a.momid = b.momid
where
a.childid != b.childid
and a.eclampsia = 'y'
and (b.eclampsia = 'y' or b.preeclampsia = 'y' or b.hypertension = 'y')
or a.preeclampsia = 'y'
and (b.preeclampsia = 'y' or b.eclampsia = 'Y' or b.hypertension = 'y')
or a.hypertension = 'y'
and (b.hypertension = 'y' or b.eclampsia = 'y' or b.preeclampsia = 'y')
order by
mapersonid

I would solve your problem with this query:
SELECT * FROM births
WHERE momid IN(
SELECT momid FROM births GROUP BY momid
HAVING COUNT(1) >1 AND
SUM(CASE WHEN eclampsia = 'Y' THEN 1 WHEN preeclampsia = 'Y' THEN 1 WHEN hypertension = 'Y' THEN 1 ELSE 0 END) > 1)
AND (eclampsia = 'Y' OR preeclampsia = 'Y' OR hypertension = 'Y')
Basicly, you filter the momids via grouping and formulate your conditions within the HAVING clause and then using this list of momids to build your desired output.

This is one way of doing it. It counts records in the births table which display one of the symptoms for each mother, using that count > 1 as a condition to display the record, as long as the record also shows one of the conditions:
SELECT childid, momid,
COALESCE(eclampsia, '') AS eclampsia,
COALESCE(preeclampsia, '') AS preeclampsia,
COALESCE(hypertension, '') AS hypertension
FROM births b1
WHERE (SELECT COUNT(*) FROM births b2 WHERE b2.momid = b1.momid AND
(ECLAMPSIA = 'Y' OR PREECLAMPSIA = 'Y' OR HYPERTENSION = 'Y')
GROUP BY momid) > 1 AND
(ECLAMPSIA = 'Y' OR PREECLAMPSIA = 'Y' OR HYPERTENSION = 'Y')
Output
child momid eclampsia preeclampsia hypertension
654321 123456 Y
431265 123456 Y
987652 465468 Y
987987 465468 Y

First get the total complications for each mom using CTE and CASEexpression , then join the CTE with Births table on Momid, then filter the moms who have more than one complication. Something like below -
;WITH BirthCTE as(
Select momid,
SUM(CASE WHEN ECLAMPSIA = 'Y' OR PREECLAMPSIA = 'Y' OR HYPERTENSION = 'Y' THEN 1 ELSE 0 END) As TotalComl
FROM births
GROUP BY momid
)
select b.* from births b
inner join BirthCTE cte on b.momid = cte.momid
Where TotalComl > 1 -- More than one complication
and (ECLAMPSIA = 'Y' OR PREECLAMPSIA = 'Y' OR HYPERTENSION = 'Y') -- atleast one complication

This is the wrong data structure. You want a table of births with no complications. Then you want a table birthComplications with one row per complication, if any.
You can restructure the data on the fly. And then aggregation:
select b.momid
from births b outer apply
(select v.complication
from (values ('eclampsia', b.eclampsia), ('hypertension', b.hypertension), ('preeclampsia', b.preeclampsia)
) v(complication, flag)
where flag = 'y'
)
group by b.momid
having count(*) > 1 and -- more than one pregnancy
count(distinct case when v.complication is not null then b.childid end) > 1;
Actually, you can simplify the logic to moms who have had complications in more than one pregnancy. This looks like:
select b.momid
from births b apply -- only keep pregnancies with complications
(select v.complication
from (values ('eclampsia', b.eclampsia), ('hypertension', b.hypertension), ('preeclampsia', b.preeclampsia)
) v(complication, flag)
where flag = 'y'
)
group by b.momid
having count(distinct b.childid) > 1;

try this
select momid, count(*) as "children",
count(eclampsia) as "eclampsia",
count(preeclampsia) as "preeclampsia",
count(hypertension) as "hypertension"
from births
group by momid
having count(*) > 1 and
(
count(eclampsia) > 1 or
count(preeclampsia) > 1 or
count(hypertension) > 1
);
you will get something like:

Related

Sql Query Modification

I have a table called Customer with following schema.
Create Table Customer(id Number,customer_type varchar(20),customer_status char(1),account_number varchar(20));
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'RETAIL','A','32456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'RETAIL','I','92456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'RETAIL','P','22456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'PERSONAL','A','42456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'PERSONAL','I','52456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(123,'PERSONAL','P','62456798');
Insert into Customer(id,customer_type,customer_status,account_number)values(243,'PERSONAL','A','02456798');
commit;
I am trying get Id where customer status is active.Customer_type can be of two types RETAIL or PERSONAL.I just want return Retail true if id have any active reatils accounts else false,
same with Personal
I tried below query but i have trouble returning id
select REATIL,PERSONAL from (select case when customer_status = 'A' then 'Y' else 'N' end as REATIL from Customer where customer_status='A' and customer_type='RETAIL')
,(select id, case when customer_status = 'A' then 'Y' else 'N' end as PERSONAL from Customer where customer_status='A' and customer_type='PERSONAL');
Expected Output:
|---------------------|------------------|----------------|
| id | Retail | Personal |
|---------------------|------------------|----------------|
| 123 | Y | Y |
|---------------------|------------------|----------------|
| 243 | N | Y |
|---------------------|------------------|----------------|
Ay help would be appreciated.
Try the following (make modifications as per your further requirements). I did this as per given data but I feel that the data is not broad enough to test the code. And I made some assumptions as putting blank when there is no match.
Basically to determine whether Retail is Y or N, I used a case statement and did the same for Personal
-- This solution didn't fit as the OP needs 1 record per ID
select ID,
Case When customer_type = 'RETAIL' and customer_status = 'A' then 'Y'
When customer_type = 'RETAIL' and customer_status != 'A' then 'N'
Else ''
End as Retail,
Case When customer_type = 'PERSONAL' and customer_status = 'A' then 'Y'
When customer_type = 'PERSONAL' and customer_status != 'A' then 'N'
Else ''
End as PERSONAL,
account_number
from Customer
Here is the required solution
Select ID, Max(RETAIL) as RETAIL, Max(PERSONAL) as PERSONAL
from
(
select ID,
Case When customer_type = 'RETAIL' and customer_status = 'A' then 'Y'
When customer_type = 'RETAIL' and customer_status != 'A' then 'N'
Else ''
End as Retail,
Case When customer_type = 'PERSONAL' and customer_status = 'A' then 'Y'
When customer_type = 'PERSONAL' and customer_status != 'A' then 'N'
Else ''
End as PERSONAL
from Customer
) Q
Group by ID
try using a join.
the following query returns the account number instead of 'Y'
select C.id, max(R.account_number), max(P.account_number) from Customer C
left join Customer R on R.id = c.id
left join Customer P on P.id = c.id
where R.customer_type = 'RETAIL'
and R.customer_status = 'A'
and P.customer_type = 'PERSONAL'
and P.customer_status = 'A'
group by C.id

Updating a table using Case statements in SQL

I am trying to add a 0, 1, or null to a column in a specific category where a relativepersonid of a person has a diagdate up to a person's servicedate. Here are my tables:
DROP TABLE ICDCodes_w;
GO
CREATE TABLE ICDCodes_w
(
AnxietyDisorder VARCHAR(6),
DepressiveDisorder VARCHAR(6),
PTSD VARCHAR(6)
);
INSERT INTO ICDCodes_w
(
AnxietyDisorder,
DepressiveDisorder,
PTSD
)
VALUES
('293.84', '296.2', '309.81'),
('300', '296.21', 'F43.1'),
('305.42', 'F11.28', 'F31.76'),
('305.81', 'F43.8', 'F31.78'),
('F40.00', 'F43.10', '305.52');
GO
DROP TABLE DiagHX_w;
GO
CREATE TABLE DiagHX_w
(
ArchiveID VARCHAR(10),
RelativePersonID VARCHAR(10),
ICDCode VARCHAR(6),
DiagDate DATE
);
INSERT INTO DiagHX_w
(
ArchiveID,
RelativePersonID,
ICDCode,
DiagDate
)
VALUES
('1275741', '754241', '293.84', '1989-01-03'),
('2154872', '754241', '293.84', '1995-04-07'),
('4587215', '754241', '998.4', '1999-12-07'),
('4588775', '711121', 'F11.28', '2001-02-07'),
('3545455', '711121', NULL, NULL),
('9876352', '323668', '400.02', '1988-04-09'),
('3211514', '112101', 'F31.78', '2005-09-09'),
('3254548', '686967', 'F40.00', '1999-12-31'),
('4411144', '686967', '305.52', '2000-01-01'),
('6548785', '99999999','F40.00', '2000-02-03');
GO
DROP TABLE PatientFlags_w;
GO
CREATE TABLE PatientFlags_w
(
PersonID VARCHAR(10),
RelativePersonID VARCHAR(10),
AnxietyDisorder VARCHAR(2),
DepressiveDisorder VARCHAR(2),
PTSD VARCHAR(2),
);
INSERT INTO PatientFlags_w
(
PersonID,
RelativePersonID
)
VALUES
('99999999', '754241'),
('88888888', '754241'),
('77777777', '754241'),
('66666666', '711121'),
('55555555', '711121'),
('44444444', '323668'),
('33333333', '112101'),
('22222222', '686967'),
('11111111', '686967'),
('32151111', '887878'),
('78746954', '771125'),
('54621333', '333114'),
('55648888', '333114');
GO
DROP TABLE Person_w;
GO
CREATE TABLE Person_w
(
PersonID VARCHAR(10),
ServiceDate date
);
INSERT INTO Person_w
(
PersonID,
ServiceDate
)
VALUES
('99999999', '2000-12-31'),
('88888888', '2000-11-01'),
('69876541', '2000-09-04'),
('66666666', '2000-01-15'),
('55555555', '2000-07-22'),
('44444444', '2000-07-20'),
('65498711', '2000-11-17'),
('22222222', '2000-09-02'),
('11111111', '2000-02-04'),
('32151111', '2000-02-17'),
('78746954', '2000-03-29'),
('54621333', '2000-08-22'),
('55648888', '2000-10-20');
Here is my update statement:
UPDATE a
SET AnxietyDisorder = CASE
WHEN ICDCode IN
(
SELECT AnxietyDisorder FROM
Project..ICDCodes_w
) THEN
1
ELSE
0
END,
DepressiveDisorder = CASE
WHEN ICDCode IN
(
SELECT DepressiveDisorder FROM
Project..ICDCodes_w
) THEN
1
ELSE
0
END,
PTSD = CASE
WHEN ICDCode IN
(
SELECT PTSD FROM Project..ICDCodes_w
) THEN
1
ELSE
0
END
FROM PatientFlags_w a
JOIN DiagHX_w b
ON a.relativepersonid = b.RelativePersonID
JOIN Person_w p
ON a.personid = p.PersonID
WHERE diagdate <= p.servicedate;
This works on some values, but there are some that don't get updated. I know the issue is with my case statement and probably a join issue. What is a better way to write this? Here is an example query I used to check. The PTSD column should have a 1.
SELECT * FROM project..patientflags_w a
JOIN project..diaghx_w b
ON a.relativepersonid = b.RelativePersonID
JOIN project..person_w p
ON a.personid = p.personid
WHERE b.icdcode IN (SELECT PTSD FROM Project..ICDCodes_w)
AND b.diagdate <= p.servicedate
I did ask this question the other day, but my sample tables were all messed up, so I've verified that they work this time.
At first glance, the problem with your query is that you update the target (PatientFlags_w) multiple times: once for each flag. In some cases you seem to be ending up with the correct result, but its just by luck.
It's hard to tell if you want one row per person in the flag table, or one row per flag.
Can you review these queries and let us know if they are close to your desired results:
-- If you want one row per Person:
select RelativePersonID,
[AnxietyDisorder] = max(case when c.AnxietyDisorder is not null then 1 else 0 end),
[DepressiveDisorder] = max(case when c.DepressiveDisorder is not null then 1 else 0 end),
[PTSD] = max(case when c.PTSD is not null then 1 else 0 end)
from DiagHX_w d
left
join ICDCodes_w c on d.ICDCode in (c.AnxietyDisorder, c.DepressiveDisorder, c.PTSD)
group
by RelativePersonID;
-- If you want one row per Flag:
select RelativePersonID,
d.ICDCode,
[AnxietyDisorder] = case when c.AnxietyDisorder is not null then 1 else 0 end,
[DepressiveDisorder] = case when c.DepressiveDisorder is not null then 1 else 0 end,
[PTSD] = case when c.PTSD is not null then 1 else 0 end
from DiagHX_w d
left
join ICDCodes_w c on d.ICDCode in (c.AnxietyDisorder, c.DepressiveDisorder, c.PTSD);
If the diagnoses are not related to each other (I assumed since they are in the same table), you might want this instead:
select RelativePersonID,
[AnxietyDisorder] = max(case when c.AnxietyDisorder = d.ICDCode then 1 else 0 end),
[DepressiveDisorder] = max(case when c.DepressiveDisorder = d.ICDCode then 1 else 0 end),
[PTSD] = max(case when c.PTSD = d.ICDCode then 1 else 0 end)
from DiagHX_w d
left
join ICDCodes_w c on d.ICDCode in (c.AnxietyDisorder, c.DepressiveDisorder, c.PTSD)
group
by RelativePersonID;

SQL Query to Update Object Count Based on Event Name

Imagine I am an owner of many bookstores. I keep a database of all events that occur in all of my many bookstores. Two events of note are "Book Added" and "Book Removed", for when a book is added to the inventory of a story, and when it is sold from a store. An example schema would be bookstore_id, event_name, `time.
Now say I have a second table, which maintains the current state of each bookstore, so the schema would be bookstore_id, num_books.
I want to be able to use the first table to get the count of all the "Book Added" events per bookstore, subtract the count of all the "Book Removed" events per bookstore, and then update the number of books in each bookstore in the second table.
The only way I can think to do it requires using a cursor, but I'm assuming there's a more "SQL-esque" way to do it that is more set-based and doesn't require a cursor.
You can count the events by using a GROUP BY clause.
If we would create 2 subtables where we count the added respectively the removed books, we can simply subtract the results and update these in the parent table. This will look like:
UPDATE b
SET b.numbooks = AddedBooks.BooksAdded - RemovedBooks.BooksRemoved
FROM dbo.Books b
INNER JOIN (SELECT be.book_id, count(*) AS BooksAdded
FROM dbo.BookEvents be
WHERE be.event = 'BookAdded'
GROUP BY be.book_id, be.event) AS AddedBooks
ON b.bookid = AddedBooks.book_id
INNER JOIN (SELECT be.book_id, count(*) AS BooksRemoved
FROM dbo.BookEvents be
WHERE be.event = 'BookRemoved'
GROUP BY be.book_id, be.event) AS RemovedBooks
ON b.bookid = RemovedBooks.book_id
select bookstore_id
, sum(case when event_name = "Book Removed" then -1 else 1 end) as "num books"
from bookstores
group by bookstore_id
if more than 2 events
select bookstore_id
, sum(case when event_name = "Book Removed" then -1
when event_name = "Book Added" then 1
end) as "num books"
from bookstores
group by bookstore_id
And I would just make it a view unless you come up with performance issues
We can use CTEs to get details individually and process them.
With CTE_Add AS
(
Select Bkstr_ID, Count(event_Name) As Added From temp Where event = 'Added' Group by Bkstr_ID
), CTE_Rem As
(
Select Bkstr_ID, Count(event_Name) As Removed From temp Where event = 'Removed' Group by Bkstr_ID
)
Select A.Bkstr_ID, Added - Removed
From CTE_Add A
Left Join CTE_Rem R On A.Bkstr_ID= R.Bkstr_ID
This will give you ID and count.
Instead of select, you can use Insert statement
I'd use SUM(CASE WHEN ...). Below is an example.
If object_id('tempdb..#BoookStores') Is Not Null Drop Table #BoookStores
Create Table #BoookStores (bookstore_id int, num_books int)
/* We have 3 stores */
Insert #BoookStores (bookstore_id, num_books)
Values (1, 0), (2, 0), (3, 0)
If object_id('tempdb..#Events') Is Not Null Drop Table #Events
Create Table #Events (bookstore_id int, event_name varchar(10), time dateTime Default(GetDate()) )
Insert #Events (bookstore_id, event_name)
Values
(1, 'Added'), (1, 'Added'), (1, 'Added'), (1, 'Added'), -- Added 4 books to 1. store
(2, 'Added'), (2, 'Added'), (2, 'Added'), -- Added 3 books to 2. store
(3, 'Added'), (3, 'Added'), -- Added 2 books to 3. store
/* removed 2 books from each stores */
(1, 'Removed'), (1, 'Removed'),
(2, 'Removed'), (2, 'Removed'),
(3, 'Removed'), (3, 'Removed')
/* Calculate adds and removes. Update the results */
;With Tmp As (
Select E.bookstore_id,
Sum(Case When E.event_name = 'Added' Then 1 Else 0 End) As AddCount,
Sum(Case When E.event_name = 'Removed' Then 1 Else 0 End) As RemoveCount
From #Events E
Group By E.bookstore_id
)
Update BS Set num_books = T.AddCount-T.RemoveCount
From #BoookStores BS
Inner Join Tmp T On T.bookstore_id = BS.bookstore_id
/* check results*/
Select * From #BoookStores BS
Something like this will get you in the ball park. Similar logic could be used for INSERT.
UPDATE tableA
SET tableA.num_books = tableB.num_books
FROM secondTable AS TableA
INNER JOIN (
SELECT bookstore_id,
SUM(CASE
WHEN event_name = 'Books Added'
THEN 1
END) - SUM(CASE
WHEN event_name = 'Books Removed'
THEN 1
END
) AS num_books
FROM firstTable
GROUP BY bookstore_id
) TableB ON TableA.bookstore_id = tableB.bookstore_id
You can try a query like below:
update t1
set num_books=inventory
FROM bs t1 LEFT JOIN
(select bookstore_id,SUM(case when event_name like 'A' then 1 when event_name like 'R' then -1 else NULL end) as inventory
from bse
group by bookstore_id) t2
on t1.bookstore_id=t2.bookstore_id
Live SQL demo
UPDATE bsc
SET bsc.num_books = bse.num_books
FROM bookstorecounts bsc
JOIN (SELECT bookstore_id,
SUM(CASE event_name
WHEN 'Book Removed' THEN -1
WHEN 'Book Added' THEN 1
END) AS num_books
FROM bookstoreevents
GROUP BY bookstore_id
) bse ON bsc.bookstore_id = bse.bookstore_id

SQL query to get count based on filtered status

I have a table which has two columns, CustomerId & Status (A, B, C).
A customer can have multiple status in different rows.
I need to get the count of different status based on following rules:
If the status of a customer is A & B, he should be counted in Status A.
If status is both B & C, it should be counted in Status B.
If status is all three, it will fall in status A.
What I need is a table with status and count.
Could please someone help?
I know that someone would ask me to write my query first, but i couldn't understand how to implement this logic in query.
You could play with different variations of this:
select customerId,
case when HasA+HasB+HasC = 3 then 'A'
when HasA+HasB = 2 then 'A'
when HasB+HasC = 2 then 'B'
when HasA+HasC = 2 then 'A'
when HasA is null and HasB is null and HasC is not null then 'C'
when HasB is null and HasC is null and HasA is not null then 'A'
when HasC is null and HasA is null and HasB is not null then 'B'
end as overallStatus
from
(
select customerId,
max(case when Status = 'A' then 1 end) HasA,
max(case when Status = 'B' then 1 end) HasB,
max(case when Status = 'C' then 1 end) HasC
from tableName
group by customerId
) as t;
I like to use Cross Apply for this type of query as it allows for use of the calculated status in the Group By clause.
Here's my solution with some sample data.
Declare #Table Table (Customerid int, Stat varchar(1))
INSERT INTO #Table (Customerid, Stat )
VALUES
(1, 'a'),
(1 , 'b'),
(2, 'b'),
(2 , 'c'),
(3, 'a'),
(3 , 'b'),
(3, 'c')
SELECT
ca.StatusGroup
, COUNT(DISTINCT Customerid) as Total
FROM
#Table t
CROSS APPLY
(VALUES
(
CASE WHEN
EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'a')
AND EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'b')
THEN 'A'
WHEN
EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'b')
AND EXISTS
(SELECT 1 FROM #Table x where x.Customerid = t.CustomerID and x.Stat = 'c')
THEN 'B'
ELSE t.stat
END
)
) ca (StatusGroup)
GROUP BY ca.StatusGroup
I edited this to deal with Customers who only have one status... in which case it will return A, B or C dependant on the customers status

counting records on the same table with different values possibly none sql server 2008

I have a inventory table with a condition i.e. new, used, other, and i am query a small set of this data, and there is a possibility that all the record set contains only 1 or all the conditions. I tried using a case statement, but if one of the conditions isn't found nothing for that condition returned, and I need it to return 0
This is what I've tried so far:
select(
case
when new_used = 'N' then 'new'
when new_used = 'U' then 'used'
when new_used = 'O' then 'other'
end
)as conditions,
count(*) as count
from myDB
where something = something
group by(
case
when New_Used = 'N' then 'new'
when New_Used = 'U' then 'used'
when New_Used = 'O' then 'other'
end
)
This returns the data like:
conditions | count
------------------
new 10
used 45
I am trying to get the data to return like the following:
conditions | count
------------------
new | 10
used | 45
other | 0
Thanks in advance
;WITH constants(letter,word) AS
(
SELECT l,w FROM (VALUES('N','new'),('U','used'),('O','other')) AS x(l,w)
)
SELECT
conditions = c.word,
[count] = COUNT(x.new_used)
FROM constants AS c
LEFT OUTER JOIN dbo.myDB AS x
ON c.letter = x.new_used
AND something = something
GROUP BY c.word;
try this -
DECLARE #t TABLE (new_used CHAR(1))
INSERT INTO #t (new_used)
SELECT t = 'N'
UNION ALL
SELECT 'N'
UNION ALL
SELECT 'U'
SELECT conditions, ISNULL(r.cnt, 0) AS [count]
FROM (
VALUES('U', 'used'), ('N', 'new'), ('O', 'other')
) t(c, conditions)
LEFT JOIN (
SELECT new_used, COUNT(1) AS cnt
FROM #t
--WHERE something = something
GROUP BY new_used
) r ON r.new_used = t.c
in output -
new 2
used 1
other 0
You can do it as a cross-tab:
select
sum(case when new_used = 'N' then 1 else 0 end) as N,
sum(case when new_used = 'U' then 1 else 0 end) as U,
sum(case when new_used = 'O' then 1 else 0 end) as Other
from myDB
where something = something