GROUP BY affecting WHERE clause - sql

I have a SQL query where I want to see claims where there is no service type associated with any of the lines. My query below is returning the one line where it meets the criteria. However, there is another line with a service type which means I don't want to see this. Because of the group by my query is seeing line and returning it and then looking at line two separately and removing it
select distinct a.claim_id,
count(a.receipt_id) as Count_Receipts,
sum(a.billed_amount) as Total_Billed_Amount,
(case
when a.service_type_id is null then
count(a.receipt_id)
else 0
end) test
from cd_roc_claim_item a,
cd_hospital b ,
cd_Claim_header c
where a.hospital_id = b.hospital_id
and a.claim_id=c.claim_id (+)
and b.hos_cat_id='PUBL'
and c.claim_status is null
and a.claim_id='123456'
group by a.claim_id,
a.service_type_id
having count(a.receipt_id) =1
and sum(a.billed_amount) in
('80','160','240','320','400','480','560','640','720','800')
;

If you just want to report the claims where there are no values for service_type in any claim item:
select ci.claim_id
, count(ci.receipt_id) as count_receipts
, sum(ci.billed_amount) as total_billed_amount
, count(*) as claim_items
from cd_roc_claim_item ci
left join cd_hospital ho
on ho.hospital_id = ci.hospital_id
left join cd_claim_header cl
on cl.claim_id = ci.claim_id
where cl.claim_status is null
group by ci.claim_id
-- , ci.service_type_id -- Edit (following comments): this was part of the problem
having count(ci.service_type_id) = 0
and sum(ci.billed_amount) in (80, 160, 240, 320, 400, 480, 560, 640, 720, 800);
CLAIM_ID COUNT_RECEIPTS TOTAL_BILLED_AMOUNT CLAIM_ITEMS
---------- -------------- ------------------- -----------
1 2 160 2
The problem is the hospital category, because every claim item could be for a different hospital with a different category. Do you want to see only claims where all items are for hospital category PUBL, or where there is at least one, or what?
Test setup:
create table cd_hospital
( hospital_id integer primary key
, hos_cat_id varchar2(10) );
create table cd_claim_header
( claim_id varchar2(10) primary key
, claim_status varchar2(10) );
create table cd_roc_claim_item
( claim_item_id integer generated always as identity primary key
, claim_id references cd_claim_header not null
, hospital_id references cd_hospital not null
, billed_amount number not null
, service_type_id integer
, receipt_id integer );
insert into cd_hospital (hospital_id, hos_cat_id) values (1, 'PUBL');
insert into cd_hospital (hospital_id, hos_cat_id) values (2, 'PRIV');
insert into cd_claim_header(claim_id, claim_status) values (1, null);
insert into cd_claim_header(claim_id, claim_status) values (2, 'CLOSED');
insert into cd_roc_claim_item (claim_id, hospital_id, billed_amount, service_type_id, receipt_id)
select ch.claim_id, ho.hospital_id, 80, null, 1
from cd_claim_header ch
cross join cd_hospital ho
where ch.claim_id = 1
union all
select ch.claim_id, ho.hospital_id, 640, 123, 1
from cd_claim_header ch
cross join cd_hospital ho
where ch.claim_id = 2;
Edit in response to comments:
To include a case where a claim has items with and without a service type, we'll update one row:
update cd_roc_claim_item set service_type_id = null
where claim_id = 2 and hospital_id = 1;
Now the test data looks like this:
select ci.claim_id, ch.claim_status, ci.hospital_id, ho.hos_cat_id, ci.service_type_id, ci.receipt_id
from cd_roc_claim_item ci
join cd_claim_header ch
on ch.claim_id = ci.claim_id
join cd_hospital ho
on ho.hospital_id = ci.hospital_id
order by ch.claim_id;
CLAIM_ID CLAIM_STATUS HOSPITAL_ID HOS_CAT_ID SERVICE_TYPE_ID RECEIPT_ID
---------- ------------ ------------ ---------- ---------------- -----------
1 2 PRIV 1
1 1 PUBL 1
2 CLOSED 2 PRIV 123 1
2 CLOSED 1 PUBL 1
As I understand it, you want to report claim 1 (aggregated to one row) because none of its items have a service type. Claim 2 should not be shown, because one of its items has a service type.

Related

join equal or max

I want to join two tables by 'EmployeeId' and 'CalendarMonthId', but problem is that I want to connect record with the bigest CalednarMonthId if not exist equal. I use Azure Synapse SQL pool.
Example data and code
create table emp_contr (
EmployeeId int,
CalendarMonthId int,
IsDeleted int
--login varchar(20)
)
create table contr (
ContractId int,
EmployeeId int,
CalendarMonthId int,
value int
)
insert into emp_contr values (1, 202201, 0)
insert into emp_contr values (1, 202202, 0)
insert into emp_contr values (1, 202205, 0)
insert into emp_contr values (1, 202206, 0)
insert into emp_contr values (2, 202202, 0)
insert into emp_contr values (2, 202203, 0)
insert into contr values (1, 1, 202201, 5)
insert into contr values (2, 1, 202202, 2)
insert into contr values (40, 2, 202202, 2)
insert into contr values (50, 2, 202203, 0)
Base on this data I have problem with connect table: emp_contr, row: EmployeeId:1, CalendarMonthId:202205 with row from table contr from the same user and maximum CalendarMonthId:202202. I have query like this, but it doesn't work
select *
from emp_contr ec
join contr c
on ec.EmployeeId = c.EmployeeId
and (ec.CalendarMonthId = c.CalendarMonthId or ec.CalendarMonthId > max(c.CalendarMonthId))
order by ec.EmployeeId
I expect result like this:
EmployeeId
Emp_Contr_CalendarMonthId
Contr_CalendarMonthId
Value
ContractId
1
202206
202202
2
2
1
202205
202202
2
2
1
202202
202202
2
2
1
202201
202201
5
1
2
202203
202203
0
50
2
202202
202202
2
40
I don't know what I should do. Maybe should I add row in temporary table based on contr table with the same values like bigest one but different CalendarMonthId
TL;DR:
select ec.EmployeeId, ec.CalendarMonthId Emp_Contr_CalendarMonthId,
coalesce(c.CalendarMonthId,c2.CalendarMonthId) Contr_CalendarMonthId,
coalesce(c.value,c2.value) value,
coalesce(c.ContractId,c2.ContractId) ContractId,
-- additional columns for debugging
c.CalendarMonthId, c.value, c.ContractId,
c2.CalendarMonthId, c2.value, c2.ContractId
from emp_contr ec
left join contr c
on ec.EmployeeId = c.EmployeeId
and ec.CalendarMonthId = c.CalendarMonthId
join (
select *
from (
select EmployeeId,
CalendarMonthId,
value,
ContractId,
row_number() over (partition by EmployeeId order by CalendarMonthId desc) rn
from contr
) x
where rn = 1
) c2 on ec.EmployeeId = c2.EmployeeId
order by ec.EmployeeId, ec.CalendarMonthId desc
SQL Fiddle
The coalesce assumes all relevant columns are NOT NULL. If this is not the case you'll have to replace them with CASE-expressions in which you check if c.CalendarMonthId is null.
Explanation
The statement joins contr twice.
First using an outer join to obtain the matching results when present, without loosing rows without a match.
The second join is an inner join.
This assumes there is at least one contr row per emp_contr
The join does join an inline view which
uses the window function row_number() to filter only the last row per employee when ordered by CalendarMonthId
The last step is to combine the results, and pick the one from the first join if present and the one from the second join if not.

How can I check if Table B matches quantity of items that exists in Table A? SQL Server

I have 2 tables, table A and Table B, and I want to check if Table B matches quantity of items that exists on Table A, so, for example:
Table A
S_O
ITEM
QTY
1
ITA
1
3
ITB
2
4
ITC
3
6
ITD
0
Table B
S_O
ITEM
QTY
1
ITA
1
3
ITB
2
4
ITC
3
6
ITD
5
7
ITE
2
8
ITF
1
My first thought was to use an except between the two tables, but then I was asked to check if the quantity was OK or if it was shortage to generate a preview like:
Result from comparing the two tables
S_O
ITEM
STATUS
1
ITA
OK
3
ITB
OK
4
ITC
OK
6
ITD
SHORTAGE
And it needs to ignore items "ITE" and "ITF" because they don't exist in Table A
I'm pretty new with sql server queries, I think I could use a SELECT CASE but I don't know how to do it, I'd appreciate some help in this matter
In those tables my unique identifier is S_O, so it would need to match the S_O, item and quantity for both tables
Here's how you can "pre summarise" table a and b to make item unique, then join:
select
A.item,
A.qty as qtya,
B.qty as qtyb,
A.qty - B.qty as shortageamt,
case
when A.qty = B.qty then 'OK'
else 'Shortage'
end as status
from
(
select item, sum(qty) as qty
from tablea
group by item
) as A
inner join
(
select item, sum(qty) as qty
from tableb
group by item
) as B
on A.item = B.item
You'll only get an item listed in the result if it's in both tables - is that what you want? if ITG is in tablea but not tableb, do you want to see it (with qty 0)? That requires an outer join.
I think you should go with a LEFT JOIN. Also, the first answer does not JOIN on S_O, which you state in your question you need.
"In those tables my unique identifier is S_O, so it would need to match the S_O, item and quantity for both tables"
DECLARE #ta TABLE (S_O INT, ITEM VARCHAR(20), QTY INT)
INSERT INTO #ta
VALUES
(1, 'ITA', 1),
(3, 'ITB', 2),
(4, 'ITC', 3),
(6, 'ITD', 0)
DECLARE #tb TABLE (S_O INT, ITEM VARCHAR(20), QTY INT)
INSERT INTO #tb
VALUES
(1, 'ITA', 1),
(3, 'ITB', 2),
(4, 'ITC', 3),
(6, 'ITD', 5),
(7, 'ITE', 2),
(8, 'ITF', 1);
SELECT ta.S_O,
ta.ITEM,
CASE WHEN ta.ITEM = tb.ITEM AND SUM(ta.QTY) >= SUM(tb.QTY) THEN 'OK' ELSE 'SHORTAGE' END AS 'STATUS'
FROM #ta ta
LEFT JOIN #tb tb ON ta.S_O = tb.S_O
GROUP BY ta.S_O, ta.ITEM, tb.ITEM
S_O
ITEM
STATUS
1
ITA
OK
3
ITB
OK
4
ITC
OK
6
ITD
SHORTAGE

Query Results from Two Different Tables

I'm trying to write a query to produce a dataset from two or more tables, and I'm having trouble writing the query. I apologize in advanced my lack of knowledge in SQL.
Table 1 consists of basic customer account info and Table 2 consists of customer contract details where one customer account can have multiple contracts, both inactive and active
Table 1 and Table 2 can be joined with the values contained under a column named acct_id.
I would like the query to show only acct_ids where account status (acct_status) is "active" from Table 1, and that do not have an "active" contract from Table 2.
The problem is that in Table 2, there are more than one contract associated to an acct_id and are in different statuses.
If my where clause just focuses on the contract status values from table 2, my dataset won't be accurate. It will only return acct_ids that have contracts with those values.
for example:
acct_iD 123 has 6 contracts: 1 active contract, 4 cancelled contracts, 1 cancel in progress contract
acct_ID 456 has 3 contracts: 3 cancelled contracts
acct_ID 789 has 4 contracts: 2 active contracts, 2 cancelled contracts
acct_ID 012 has 1 contract: 1 cancelled contract
I would like my query result to show only acct_IDs: 456 and 012 as it truly represents that they do not have "active" contracts
I'm using SQL Management Studio 2008 R2.
select acct_id
from table1
where acct_status = "active" and
acct_id not in (select acct_id from table2 where contract_status = "active")
SELECT A.*
FROM Table1 A
WHERE A.acct_status = 'active'
AND NOT A.acct_id in (SELECT acct_id FROM Table2 WHERE contract_status = 'active')
Avoid the horror of IN and sub-selects by utilising LEFT OUTER JOINs like so:
SELECT A.*
FROM Table1 A
LEFT OUTER JOIN table2 B
ON b.acct_id = A.acct_id
AND B.status = 'active'
WHERE b.acct_id IS NULL
IF OBJECT_ID(N'tempdb..#Customer', N'U') is not null drop table #Customer
select
identity(int,1,1) as Customer_ID
, 'John Doe, the Ranger' as name
, '012' as Acct_ID
, 1 as Active
into #Customer
insert into #Customer (name, Acct_ID, Active) values ('Kermit the Frog', '789',1)
select * from #Customer
GO
IF OBJECT_ID(N'tempdb..#Contracts', N'U') is not null drop table #Contracts
select
identity(int,1,1) as Contract_ID
, 1 as Customer_ID
, '012' as Acct_ID
, 123.45 as amt
, 1 as Active
into #Contracts
insert into #Contracts (Customer_ID, Acct_ID, amt, active) values (1, '012', 234.56, 1)
insert into #Contracts (Customer_ID, Acct_ID, amt, active) values (2, '788', 9.56,1)
insert into #Contracts (Customer_ID, Acct_ID, amt, active) values (1, '789', 111.56, 0)
select * from #Contracts A
select *
from #Customer A
where a.Active=1
and (a.Acct_ID not in (select Acct_ID from #Contracts where Active=1))

How to return one row from group by multiple columns

I am trying to extract a list of unique customers from a database where some customers are listed more than once. The (almost) duplicate rows exist because customers have been moved from one division to another or because the customers have been registered with another address (or both).
So my challenge is in data that looks something like this:
ID Customer Division Address
-----------------------------------
1 A M X
1 A L X
2 B N Y
2 B N Z
3 C P W
3 C T S
I want my select statement to return one row for each customer (I dont care which one).
ID Customer Division Address
-----------------------------------
1 A M X
2 B N Y
3 C P W
I am using SQL Server 2008. I think I need to do a "GROUP BY" the last two columns but I don't know how to get just one row out of it.
I hope someone can help me!
(Yes, I know the problem should be solved at the source but unfortunately that is not possible within any reasonable time-frame...).
select ID, Customer,Division, Address from
(
SELECT
ID, Customer,Division, Address,
ROW_NUMBER() OVER (PARTITON BY Customer Order by Id) as RN
FROM T
) t1
WHERE RN=1
Try this one -
DECLARE #temp TABLE
(
ID INT
, Customer CHAR(1)
, Division CHAR(1)
, [Address] CHAR(1)
)
INSERT INTO #temp (ID, Customer, Division, [Address])
VALUES
(1, 'A', 'M', 'X'),
(1, 'A', 'L', 'X'),
(2, 'B', 'N', 'Y'),
(2, 'B', 'N', 'Z'),
(3, 'C', 'P', 'W'),
(3, 'C', 'T', 'S')
SELECT t.id
, t.Customer
, t.Division
, t.[Address]
FROM
(
SELECT *
, rn = ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY 1/0)
FROM #temp
) t
WHERE T.rn = 1
SELECT ID, Customer, Division = MAX(Division), [Address] = MAX([Address])
FROM #temp
GROUP BY ID, Customer
Output -
id Customer Division Address
----------- -------- -------- -------
1 A M X
2 B N Y
3 C P W
ID Customer Division Address
----------- -------- -------- -------
1 A M X
2 B N Z
3 C T W

Left join with complex join clause

I have two tables and want to left join them.
I want all entries from the account table, but only rows matching a criteria from the right table. If no criteria is matching, I only want the account.
The following does not work as expected:
SELECT * FROM Account a
LEFT JOIN
Entries ef ON ef.account_id = a.account_id AND
(ef.entry_period_end_date BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.forecast_period_end BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.entry_period_end_date IS NULL
OR
ef.forecast_period_end IS NULL
)
cause it also gives me the rows from the entries table, which are outside the requested period.
Example Data:
Account Table
AccountID | AccountName
1 Test
2 Foobar
3 Test1
4 Foobar2
Entries Table
id | AccountID | entry_period_end_date | forecast_period_end | amount
1 1 12/31/2009 12/31/2009 100
2 1 NULL 10/31/2009 150
3 2 NULL NULL 200
4 3 10/31/2009 NULL 250
5 4 10/31/2009 10/31/2009 300
So the query should return (when i set startDate = 12/01/2009, endDate 12/31/2009)
AccountID | id
1 1
2 NULL
3 NULL
4 NULL
Thx,
Martin
If either entry_period_end_date or forecast_period_end is NULL, the row will be returned, even if your other, non-NULL column is not within the period.
Probably you meant this:
SELECT *
FROM Account a
LEFT JOIN
Entries ef
ON ef.account_id = a.account_id
AND
(
entry_period_end_date BETWEEN …
OR forecast_period_end BETWEEN …
)
, which will return you all rows with either entry_period_end or forecast_period_end within the given period.
Update:
A test script:
CREATE TABLE account (AccountID INT NOT NULL, AccountName VARCHAR(100) NOT NULL);
INSERT
INTO account
VALUES
(1, 'Test'),
(2, 'Foobar'),
(3, 'Test1'),
(4, 'Foobar1');
CREATE TABLE Entries (id INT NOT NULL, AccountID INT NOT NULL, entry_period_end_date DATETIME, forecast_period_end DATETIME, amount FLOAT NOT NULL);
INSERT
INTO Entries
VALUES
(1, 1, '2009-12-31', '2009-12-31', 100),
(2, 1, NULL, '2009-10-31', 100),
(3, 2, NULL, NULL, 100),
(4, 3, '2009-10-31', NULL, 100),
(5, 4, '2009-10-31', '2009-10-31', 100);
SELECT a.*, ef.id
FROM Account a
LEFT JOIN
Entries ef
ON ef.accountID = a.accountID
AND
(
entry_period_end_date BETWEEN '2009-12-01' AND '2009-12-31'
OR forecast_period_end BETWEEN '2009-12-01' AND '2009-12-31'
);
returns following:
1, 'Test', 1
2, 'Foobar', NULL
3, 'Test1', NULL
4, 'Foobar1' NULL
Edited to fix logic so end date logic is grouped together, then forecast period logic...
Now it should check for a "good" end date (null or within range), then check for a "good" forecast date (null or within range)
Since all the logic is on the Entries table, narrow it down first, then join
SELECT a.*,temp.id FROM Account a
LEFT JOIN
(
SELECT id, account_id
FROM Entries ef
WHERE
((ef.entry_period_end_date BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.entry_period_end_date IS NULL
)
AND
(ef.forecast_period_end BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.forecast_period_end IS NULL
)
) temp
ON a.account_id = temp.account_id