Count missing values - sql

I have a following table called Test:
Id | SomeId | Value
-----------------------------------------------------
019D9E52-41D1-45DF-81B6-C7CC484115A7 | 1 | 1
262640CA-65C2-4E30-8654-E187ACA1EEF4 | 1 | 1
53710AFC-4E19-4B1C-B68B-CDB713EC3D62 | 1 | 2
8FF7E77C-D04C-4961-82D9-87C2E5A1A096 | 1 | 2
-----------------------------------------------------
119D9E52-41D1-45DF-81B6-C7CC484115A7 | 2 | 1
762640CA-65C2-4E30-8654-E187ACA1EEF4 | 2 | 1
93710AFC-4E19-4B1C-B68B-CDB713EC3D62 | 2 | 2
4FF7E77C-D04C-4961-82D9-87C2E5A1A096 | 2 | 2
And there is a view called TestView:
SomeId | Value | Description
----------------------------
1 | 1 | 'One'
1 | 2 | 'Two'
1 | 3 | 'Three'
----------------------------
2 | 1 | 'One'
2 | 2 | 'Two'
These are just pseudo code examples.
I want to count all the values from the Test table (for a specific [SomeId]), and if value from the TestView (with a specific [SomeId]) is not in the Test table I just want to display 0 as count.
If I wanted to count values WHERE [Test].[SomeId] = 1, here's the expected result:
Value | Count
-----------------
One | 2
Two | 2
Three | 0
This is my query so far:
SELECT
tv.[Description] AS [Value],
COUNT(t.[Id]) - COUNT(tv.[Value]) AS [Count]
FROM [TestView] AS tv
LEFT JOIN [Test] AS t ON
t.[SomeId] = tv.[SomeId]
AND t.[Value] = tv.[Value]
WHERE
t.[SomeId] = 1
GROUP BY
tv.[Description]
But this gives me bad result... Anyways, here's the SQL Fiddle
EDIT:
This is just an addition to a Test table. What is Test table has one more foreign key Id, let's call it OtherId. Now when I use the query from the answer I won't get the result I wanted. Here's the modified query:
SELECT
t1.Description AS Value,
COUNT(t2.Value) AS Count
FROM TestView t1
LEFT JOIN test t2
ON t1.Value = t2.Value AND t1.SomeId = t2.SomeId
WHERE t1.SomeId = 1
AND t2.[OtherId] = *something* -- this is the addition
GROUP BY t1.Value, t1.Description
ORDER BY t1.Value;

Try this:
SELECT
t1.Description AS Value,
COUNT(t2.Value) AS Count
FROM TestView t1
LEFT JOIN test t2
ON t1.Value = t2.Value AND t1.SomeId = t2.SomeId
WHERE t1.SomeId = 1
GROUP BY t1.Value, t1.Description
ORDER BY t1.Value;
Demo

Below is your Solution
SELECT
tv.[Description] AS [Value],
COUNT(t.[Id]) AS [Count]
FROM [TestView] AS tv
LEFT OUTER JOIN [Test] AS t ON tv.SomeId = t.SomeId
AND t.Value = tv.value
AND t.[SomeId] = 1
GROUP BY
tv.[Description]

Related

Tie-breaking mutliple matches on MAX() in SQL

I have a table that looks like this:
| client_id | program_id | provider_id | date_of_service | data_entry_date | data_entry_time |
| --------- | ---------- | ----------- | --------------- | --------------- | --------------- |
| 2 | 5 | 6 | 02/02/2022 | 02/02/2022 | 0945 |
| 2 | 5 | 6 | 02/02/2022 | 02/07/2022 | 0900 |
| 2 | 5 | 6 | 02/04/2022 | 02/04/2022 | 1000 |
| 2 | 5 | 6 | 02/04/2022 | 02/04/2022 | 1700 |
| 2 | 5 | 6 | 02/04/2022 | 02/05/2022 | 0800 |
| 2 | 5 | 6 | 02/04/2022 | 02/05/2022 | 0900 |
I need to get the most recent date_of_service entered. From the table above, the desired result/row is:
date_of_service = 02/04/2022, data_entry_date = 02/05/2022, data_entry_time = 0900
This resulting date_of_service will be left joined to the master table.
This query mostly works:
SELECT t1.client_id, t1.program_id, t1.provider_id, t2.date_of_service
FROM table1 as t1
WHERE provider_id = '6'
LEFT JOIN
(SELECT client_id, program_id, provider_id, date_of_service
FROM table2) as t2
ON t2.client_id = t1.client_id
AND t2.program_id = t1.program_id
AND t2.provider_id = t1.provider_id
AND t2.date_of_service =
(SELECT MAX(date_of_service)
FROM t2 as t3
WHERE t3.client_id = t1.client_id
AND t3.program_id = t1.program_id
AND t3.provider_id = t1.provider_id
)
)
But it also returns multiple rows whenever there is more than one match on the max(date_of_service).
To solve this, I need to use the max data_entry_date to break any ties whenever there is more than one row that matches the max(date_of_service). Likewise, I also need to use the max data_entry_time to break any ties whenever there is more than one row that also matches the max data_entry_date.
I tried the following:
SELECT t1.client_id, t1.program_id, t1.provider_id, t2.date_of_service
FROM table1 as t1
WHERE provider_id = '6'
LEFT JOIN
(SELECT TOP(1) client_id, program_id, provider_id, date_of_service, data_entry_date, data_entry_time
FROM table2
ORDER BY date_of_service DESC, data_entry_date DESC, data_entry_time DESC
) as t2
ON t2.client_id = t1.client_id
AND t2.program_id = t1.program_id
AND t2.provider_id = t1.provider_id
But I can only get it to return null values for the date_of_service.
Likewise, this:
SELECT t1.client_id, t1.program_id, t1.provider_id, t2.date_of_service
FROM table1 as t1
WHERE provider_id = '6'
LEFT JOIN
(
SELECT TOP(1) client_id AS client_id2, program_id AS program_id2, provider_id AS provider_id2, date_of_service, data_entry_date, data_entry_time
FROM table2 AS t3
JOIN
(SELECT
MAX(date_of_service) AS max_date_of_service
,MAX(data_entry_date) AS max_data_entry_date
FROM table1
WHERE date_of_service = (SELECT MAX(date_of_service) FROM table2)
) AS t4
ON t3.date_of_service = t4.max_date_of_service
AND t3.data_entry_date = t4.max_data_entry_date
ORDER BY data_entry_time
) AS t2
ON t2.client_id2 = t1.client_id
AND t2.program_id2 = t1.program_id
AND t2.provider_id2 = t1.provider_id
... works (meaning it doesn't throw any errors), but it only seems to return null values for me.
I've tried various combinations of MAX, ORDER BY, and multiple variations of JOIN's, but haven't found one that works yet.
I don't know what version my SQL database is, but it doesn't appear to handle window functions like OVER and PARTITION or other things like COALESCE. I've been using DBeaver 22.2.0 to test the SQL scripts.
Based on your what you've provided, looks like you can simply query table2:
SELECT client_id, program_id, provider_id, MAX(date_of_service), MAX(data_entry_date), MAX(data_entry_time)
FROM table2
GROUP BY client_id, program_id, provider_id
If you need to join this result set to table1, just JOIN to the statement above on client_id, program_id, provider_id
Try using below query. This is using just joins and sub query.
SELECT TOP 1 * FROM table1 t1
JOIN (
SELECT
MAX(date_of_Service) AS Max_date_of_Service
,MAX(data_entry_date) AS Max_data_entry_date
FROM table1
WHERE date_of_Service = (SELECT MAX(date_of_Service) FROM table1)
)t2
ON t1.date_of_Service = t2.Max_date_of_Service
AND t1.data_entry_date = t2.Max_data_entry_date
ORDER BY data_entry_time

How to populate a table based on a value from a different table

I have two tables of data which I can join using a left join linked on the ID in both tables. Where the course and the person are the same, I need to populate the RegNumber as the same as the RegNumber which is already there for 1 row:
How it is currently: if I join table 1 and table 2 with a left join.
Table 1
ID | Course| Person
67705 | A | 1
68521 | A | 1
85742 | A | 1
89625 | A | 1
67857 | B | 2
86694 | B | 2
88075 | B | 2
88710 | C | 3
47924 | C | 3
66981 | C | 3
12311 | B | 1
12312 | B | 1
12313 | B | 1
Table 2
ID | RegNumber
67705 | N712316
NULL | NULL
NULL | NULL
NULL | NULL
67857 | N712338
NULL | NULL
NULL | NULL
NULL | NULL
47924 | M481035
NULL | NULL
12311 | N645525
NULL | NULL
NULL | NULL
I need table 2 to look like this:
ID | RegNumber
67705 | N712316
68521 | N712316
85742 | N712316
89625 | N712316
67857 | N712338
86694 | N712338
88075 | N712338
88710 | N712338
47924 | M481035
66981 | M481035
12311 | N645525
12312 | N645525
12313 | N645525
That is, I need to insert new rows into Table 2
Can anyone help me please? This is Totally beyond my capability!
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from table2 r2 join table1 r1
on r1.Id = r2.Id
and r1.Course = t1.Course
and r1.Person = t1.person
order by id) reg
where not exists (select 1 from table2 t2 where t1.ID = t2.ID)
you can improve performance a little bit by loading data into temp table first :
select t1.ID , Course,Person,regNumber
into #LoadedData
from table1 t1
join table2 t2 on t1.Id = t2.ID
insert into table2 (ID,RegNumber)
select t1.ID,reg.regNumber
from table1 t1
cross join (select top 1 regNumber from #LoadedData l
where l.Course = t1.Course
and l.Person = t1.person
order by id) reg
where not exists (select 1 from #LoadedData l where t1.ID = l.ID)
in either case having an index on (ID, Course, Person) will help with performance
Assuming:
You are missing items in table 2 that inherit data from other records in table 1.
What makes two different IDs share the same Regnumber is to have BOTH course and person number in common.
You really need to join table 1 to itself to create the mapping that associates ID 67705 with ID 68521, then you can join in table 2 to pick up the Regnumber.
Try this:
Insert into table2 (ID,RegNumber)
Select right1.ID, left2.RegNumber
From (
(table2 left2 INNER JOIN
table1 left1 On (left1.ID=left2.ID)
INNER JOIN table1 right1 On (left1.Course=right1.Course AND left1.Person=right1.Person)
) LEFT OUTER JOIN table2 right2 On (right1.ID=right2.ID)
WHERE right2.ID Is Null
The 4th table join (alias right2) is purely defensive, to handle two records in table2 having identical Person & Course in table1.
I have solved this myself.
I concatenated the person and course columns and then joined them using that new concatenated field
insert into table 2 (ID,RegNumber)
select X1.ID,X2.Regnumber
from (select concat(course,person) as X,ID from table1) X1
join (select concat(t1.course,t1.person) as X, t2.RegNumber
from table1 t1
join table2 t2 on t1.ID = t2.ID) X2
on X1.X = X2.X
where X1.ID not in (select ID from table2)

How to do an outer join with full result between two tables

I have two tables:
TABLE1
id_attr
-------
1
2
3
TABLE2
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
As a result I want a table that show:
RESULT
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
So I want the row with id=10 and id_attr=3 also when id_Attr=3 is missing in TABLE2 (and I know that because I have a NULL value (or something else) in the val column of RESULT.
NB: I could have others ids in table2. For example, after insert this row on table2: {11,1,A}, as RESULT I want:
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
11 | 1 | A
11 | 2 | NULL
11 | 3 | NULL
So, for every id, I want always the match with all id_attr.
Your specific example only has one id, so you can use the following:
select t2.id, t2.id_attr, t2.val
from table2 t2
union all
select 10, t1.id_attr, NULL
from table1 t1
where not exists (select 1 from table2 t2 where t2.id_attr = t1.id_attr);
EDIT:
You can get all combinations of attributes and ids in the following way. Use a cross join to create all the rows you want and then a left join to bring in the data you want:
select i.id, t1.id_attr, t2.val
from (select distinct id from table2) i cross join
table1 t1 left join
table2 t2
on t2.id = i.id and t2.id_attr = t1.id_attr;
It sounds like you want to do just an outer join on id_attr instead of id.
select * from table2 t2
left outer join table1 t1 on t2.id_attr = t1.id_attr;

GROUP BY Create group if at least one value in group meets condition

How to create group if at least one value in group meets condition?
Here is an example of DB table test:
| ID | TYPE | COLOR |
|====|======|=======|
| 1 | 1 | R |
| 2 | 1 | B |
| 3 | 1 | G |
| 4 | 2 | B |
| 5 | 2 | G |
| 6 | 3 | G |
I need to select all TYPE values that have more than one row, and at least one of COLORs is G for that TYPE.
So pseudo-select would look like this:
select TYPE
from test
group by TYPE
having count(*) > 1
and count(COLOR = 'G') > 0
With the OP's modified requirement:
select type
from test
group by type
having count(*) > 1 and count(case when color = 'G' then 0 end) > 0
;
You could use a inner join on th count(color)
select t1.type
from test t1
inner join (
select type, count(color)
from test
where type in (select type from test where color='G' )
group by type
) t2 on t1.type = t2.type
group by t1.type
having count(*) > 1
or in a simplest way
select t1.type
from test t1
inner join test t2 on t1.type = t2.type and t2.color = 'G'
group by t1.type
having count(*) > 1
count only counts non-null values. A neat trick to wrap both required conditions into one is to count the distinct number of a case expression that returns something for G and something else for any other value:
SELECT type
FROM test
GROUP BY type
HAVING COUNT(DISTINCT CASE color WHEN 'G' THEN 1 ELSE 2 END) = 2

T-sql using values from one table and displaying them in different columns

My database Table looks like:
ID | INDEX | Value |
1 | 0 | 3 |
1 | 1 | 5 |
1 | 2 | 7 |
2 | 0 | 4 |
2 | 1 | 6 |
2 | 2 | 2 |
What I want my output to look like is the difference of the values column based on their index
i.e. value(id=2,index = i) - value(id = 1, index = i) so the output table will look like
INDEX | Delta Value |
0 | 1 |
1 | 1 |
2 | -5 |
My attempt at solving this problem is as follows:
SELECT Top 6
col1.value column1,
col2.value column2,
col2.value - col1.value
FROM My_Table col1
INNER JOIN My_Table col2
ON col1.index = col2.index
WHERE col1.id = 1
OR col2.id = 2
I know there are problems with this query. But I just haven't been able to produce the output that I want. Any help is appreciated.
You can do this by join
select
t1.ind, t1.value - t2.value as delta
from My_Table as t1
inner join My_Table as t2 on t2.id = 2 and t2.ind = t1.ind
where t1.id = 1
Or by simple aggregate:
select
ind, sum(case id when 1 then 1 when 2 then -1 end * value) as delta
from My_Table
group by ind
Do you want something like this?
select
col1.value column1,
col2.value column2,
col2.value - col1.value AS delta
From My_Table col1
INNER JOIN My_Table col2
ON col1.index = col2.index
AND col2.id = 2
where col1.id = 1
have a look at mine
select t1.[index],
t2.value-t1.value
from my_table t1 inner join my_table t2
on t1.[index] = t2.[index]
where t1.id = 1 and t2.id = 2
order by t1.[index]