Use count and case together using SQL - sql-server

I want to use a query to return 3 columns, how many type-A blood patients are patient sets,
how many type-B blood patients are there, and how many countries are there based on patients.
Each patient is identified using an unique ID, so patientID is what I'm doing count on. Each State is just using state abbreviation and blood type just letters.
And there are sets of patients, sets are just group of patients lumped together, so like a bunch of patientIDs, they are also unique like patientsID
So far I have something like this, I don't want to use SUM because that would add each patientsID numbers together, I should be using Count. Is there way to count using a case scenario? Or is there better way to accomplish what I want?
select distinct PTID,
select count (patientID CASE WHEN bloodtype = 'A') as totalAbloodtype,
select count (patientID CASE WHEN bloodtype = 'AB') as totalABbloodtype,
select count (distinct countrycode) as totalcountriesinset
from patientsinfo
and PTID is not null
group by PTID

You can use sum with a case expression. You don't need distinct with group by and you seem to be missing where with an abundance of selects so the code you have will just be a syntax error.
Obviously with no sample data or desired results I cannot test, but the idea is
select PTID,
sum (CASE WHEN bloodtype = 'A' then 1 else 0 end) as totalAbloodtype,
sum (CASE WHEN bloodtype = 'AB' then 1 else 0 end) as totalABbloodtype,
count (distinct countrycode) as totalcountriesinset
from patientsinfo
where PTID is not null
group by PTID

Related

Join two queries that are grouped by same column from same tables but different parameters to join

I have 3 tables with items, owners and statuses and I need to show the count of items that were sold/discarded grouped by every owner for the year passed as parameter.
I am able to get ownername and soldcount as one query and ownername and discardcount as second query but is there a way to structure so that ownername, soldcount and discardcount come in one query?
declare #QueryYear integer = 2020
--SOLD
select O1.pk_owner_id,count(P1.pk_Property_ID) as [SaleCount]
from
Item P1, Owner O1, Status S1
WHERE
(C1.fkl_owner_ID = O1.pk_owner_ID and C1.fkl_item_ID=P1.pk_item_ID and O1.isactive=1 and year(P1.dtList_Date)=#QueryYear and P1.fkl_status_ID=1)
group by
O1.pk_owner_id
--DISCARD
select O2.pk_owner_id,count(P2.pk_item_ID) as [DiscardCount]
from
item P2, owner O2, status C2
WHERE
(C2.fkl_Owner_ID = O2.pk_owner_ID and C2.fkl_item_ID=P2.pk_item_ID and O2.isactive=1 and year(P2.dtList_Date)=#QueryYear and P2.fkl_item_status_ID=2)
group by
O2.pk_owner_id
I used a Union and it gives answer in 2 columns only.
Move your status filter to case statements in your select clause.
select o.pk_owner_id,
SaleCount = count(case when i.fkl_status_ID = 1 then 1 end),
DiscardCount = count(case when i.fkl_item_status_ID = 2 then 1 end)
from Status s
join Item i on s.fkl_item_ID = i.pk_item_ID
join Owner o on s.fkl_owner_ID = o.pk_owner_ID
where o.isactive = 1
and year(i.dtList_Date) = #QueryYear
group by o.pk_owner_id
Also, use relational operators to express relationships between tables, don't use the where clause. In this case, because the nature of the relationship is 'horizontal' in that each row in one table matches to each row in another table, you're looking for an (inner) join.
Finally, if you have more status types than '1' and '2', then you can add another condition to your joining of status with item, or put it in your where statement. Namely, you can do something like:
and i.fkl_status_ID in (1,2)
But I notice that the status_id columns have different names for SaleCount and DiscardCount. So if that's not an error, you'll need to do a parenthesized or version. But the main point is that your query will be more efficient if the processor knows to ignore statuses that are not '1' or '2'.

T-SQL Selecting TOP 1 In A Query With Aggregates/Groups

I'm still fairly new to SQL. This is a stripped down version of the query I'm trying to run. This query is suppose to find those customers with more than 3 cases and display either the top 1 case or all cases but still show the overall count of cases per customer in each row in addition to all the case numbers.
The TOP 1 subquery approach didn't work but is there another way to get the results I need? Hope that makes sense.
Here's the code:
SELECT t1.StoreID, t1.CustomerID, t2.LastName, t2.FirstName
,COUNT(t1.CaseNo) AS CasesCount
,(SELECT TOP 1 t1.CaseNo)
FROM MainDatabase t1
INNER JOIN CustomerDatabase t2
ON t1.StoreID = t2.StoreID
WHERE t1.SubmittedDate >= '01/01/2017' AND t1.SubmittedDate <= '05/31/2017'
GROUP BY t1.StoreID, t1.CustomerID, t2.LastName, t2.FirstName
HAVING COUNT (t1.CaseNo) >= 3
ORDER BY t1.StoreID, t1.PatronID
I would like it to look something like this, either one row with just the most recent case and detail or several rows showing all details of each case in addition to the store id, customer id, last name, first name, and case count.
Data Example
For these I usually like to make a temp table of aggregates:
DROP TABLE IF EXISTS #tmp;
CREATE TABLE #tmp (
CustomerlD int NOT NULL DEFAULT 0,
case_count int NOT NULL DEFAULT 0,
case_max int NOT NULL DEFAULT 0,
);
INSERT INTO #tmp
(CustomerlD, case_count, case_max)
SELECT CustomerlD, COUNT(tl.CaseNo), MAX(tl.CaseNo)
FROM MainDatabase
GROUP BY CustomerlD;
Then you can join this "tmp" table back to any other table you want to display the number of cases on, or the max case number on. And you can limit it to customers that have more than 3 cases with WHERE case_count > 3

Querying in SQL-

I have a table named "Results" like below:
I'd like to count personnel who have been completely scored. It means the ones who have no zero in score column. For example based on the uploaded picture just person with ID 1004 should be counted and the outcome should be one.
I used this code:
select Count(PrsID) from results
where Score <> 0
group by PrsID
But it wouldn't help me cause if a person has just one non-zero score, he will be counted!
Thanks in advance.
If I understand you correctly, I think this is what you want: SELECT COUNT(DISTINCT(PrsID)) FROM results WHERE PrsID NOT IN (SELECT DISTINCT PrsID FROM results WHERE score = 0)
If a person's min score is greater than zero, then they should be counted.
select count(1)
from (
select PrsID
from results
group by PrsID
having min(Score) > 0) as results
You can use conditional aggregation as below:
Select PrsId from results
group by PrsId
having sum(case when score = 0 then 1 else 0 end) > 1
I think you want something like:
select PrsID, Count(1) from results
where Score = 0
group by PrsID
This one returns count of person who has non zero score
SELECT
t1.PrsID,
Count(t1.PrsID)
FROM
( SELECT
*
FROM results
GROUP BY results.PrsID
ORDER BY results.Score ASC
) AS t1
WHERE t1.Score <> 0;

Complex grouping algorithm with combinations in Sql server

I have a complex grouping problem. I should solve it on Sql server 2005 but a solution that works on a more recent release is ok (we will upgrade soon).
test table:
CREATE TABLE [dbo].[testGrouping](
[Family] [varchar](50) NOT NULL,
[Person] [varchar](50) NOT NULL,
[transNr] [int] NOT NULL,
[Amount] [numeric](6, 2) NOT NULL,
[ExpectedGroup] [int] NULL
)
test data
INSERT INTO [testGrouping]([Family],[Person],[transNr],[Amount],[ExpectedGroup])
SELECT 'f1','p1',1, 10.00,1
union SELECT 'f1','p1',2 , -9.00,1
union SELECT 'f1','p2',3 , -1.00,1
union SELECT 'f2','p3',4 , 50.00,2
union SELECT 'f2','p4',5 ,-50.01,2
union SELECT 'f2','p5',6 ,-30.00,3
union SELECT 'f2','p5',7 , 20.00,3
union SELECT 'f2','p5',8 , 10.00,3
union SELECT 'f3','p7',9 , -1.00,4
union SELECT 'f3','p7',10, -2.00,4
union SELECT 'f3','p7',11, -6.00,null
union SELECT 'f3','p9',12, 2.00,null
union SELECT 'f3','p7',13, 3.00,4
union SELECT 'f2','p6',14,100.00,null
Now the problem. The ExpectedGroup starts at null, I must fill it with my code.
The requirement is to identify groups of records with ABS(sum(amount)) <= 0.01
In details:
I can group 2 or more records of the same "person";
After grouping by persons, I can search groups in the same "family"
Each record can belong to 1 group only
Each person can belong to 1 family only
Records that cannot be grouped have group = null
A group can have more than 2 records (and that's the real challenge!)
In the real data each "Family" can have up to 200 records, and each "Person" can have up to 10 records.
Amount is always <> 0
Explanation of grouping in sample data:
Group 1:
Include all the records of family f1 because no partial combination of person in that family has ABS(sum(amount)) <= 0.01
Group 2:
Persons p3 and p4 have ABS(sum(amount)) <= 0.01.
Group 3:
Persons p5 has ABS(sum(amount)) <= 0.01. So Family f2 is divided into 2 groups and a single record (transNr 13) has no group
Group 4:
In family f3 you could group transNr 9 and 11 but there is a group that belongs to Person p7 only, therefore it has higher priority.
I could easily find groups like Group 1
select family, sum(amount) from testGrouping group by Family HAVING ABS(sum(amount)) <= 0.01
and also group 3
select person, sum(amount) from testGrouping group by Person HAVING ABS(sum(amount)) <= 0.01
But other cases are trickyer (see Family f2: there are several ways to construct groups there, grouping by p5 is trivial but the other records are not so easy)
My idea in pseudo code is:
-- process the easy cases...
group by person, set a group number to persons having ABS(sum(amount)) <= 0.01
group by family, set a group number to families having ABS(sum(amount)) <= 0.01
-- process the remaining records
For each person
Generate all combinations of not grouped records of that person
For each combination of records
IF ABS(sum(amount)) of the combination <= 0.01 THEN
Assign a group to records of the combination
Recalculate the combinations (we have less records to work with)
END IF
Next combination
Next person
For each family
Generate all combinations of not grouped records of that family
For each combination of records
IF ABS(sum(amount)) of the combination <= 0.01 THEN
Assign a group to records of the combination
Recalculate the combinations (we have less records to work with)
END IF
Next combination
Next family
(on each step I can use only record not assigned to a group in previous steps, the For each translates into cursors)
My questions are:
Can you suggest me a better algorithm? (the solution must be SQL only but to describe it pseudocode is ok) I think that my pseudocode translates into a spaghetti code of nested loops, cursors, goto and other ugly code.(performance is not so critical, a few minutes to process about 10.000 records is acceptable)
How can I implement the "Generate all combinations" part? In the sample, for family F2 I should try all the possible groups of 2 records, then all the possible groups of 3 and so on till testing all the combinations. transNr is unique record ID.

Distinct count needed within case when statement

I am creating a query of all people who were screened for smoking status and need a count of unique patients. I am pulling from an encounter table, so the patient could have been asked multiple times. In my case when statement I would like to limit the "Then..." result to something like "Then count distinct patients" but it is giving me an error about aggregates not being allowed within an aggregate. If I remove it, it will then not produce a total as I wish and it's telling me I need it in the group by clause, which I do not want. limit is not an option in sql-server to the best of my knowledge
,count(case when soc.tobacco_user_c in (1, 2, 4, 5) and dmw.SMOKING_CESS_CNSL_YN ='y' then enc.PAT_ID **Here is where I want a unique count of patients** end) Compliant
You can combine DISTINCT with a CASE expression.
Example
SELECT
COUNT(DISTINCT CASE WHEN tobacco = 1 THEN PAT_ID ELSE NULL END)
...
;
I've abbreviated your example to make it easier to read. NULLs will not be included in the final count, so there is no need to worry about off by one errors.
case when soc.tobacco_user_c in (1, 2, 4, 5) and dmw.SMOKING_CESS_CNSL_YN ='y' then COUNT(DISTINCT enc.PAT_ID) ELSE 0 end Compliant
I ended up creating two subqueries and then doing a select count distinct on each of the max columns in those queries to limit the results to one

Resources