How to select data with group by and subquery calculations? - sql

I have two tables:
list_table:
id
name
1
a
2
b
3
c
vt_table:
id
list_id
amount
direction_id
1
1
20
1
2
1
12
2
3
1
15
1
4
2
23
1
5
1
20
1
6
1
20
2
7
1
18
1
I need this result:
amount (dir_id = 1 - dir_id = 2), list_id
amount
list_id
41
1
23
2
0
3
Amount is sum of all amount fields in table vt_table where direction_id = 1 minus sum of all amount fileds in table vt_table where direction_id = 2
And I need group this calculations by list_id, and if table have no rows with list_id 3, as example, amount must be 0.
I'm trying to do it with this query:
SELECT vt.list_id
, ((SELECT COALESCE(SUM(vt.amount), 0)
FROM table_name vt
WHERE vt.direction_id = 1)
-
(SELECT COALESCE(SUM(vt.amount), 0)
FROM table_name vt
WHERE direction_id = 2)) AS result
FROM table_name vt
GROUP BY vt.list_id
But I don't know how to group it correctly and make it so that if there were no entries for some list_id, then the amount was 0 for this list_id.
I use PostgreSQL 12.
Here the examples

You can try to use OUTER JOIN with condition aggregate function with COALESCE fucntion.
Query 1:
SELECT l.id,
SUM(COALESCE(CASE WHEN vt.direction_id = 1 THEN vt.amount END,0)) -
SUM(COALESCE(CASE WHEN vt.direction_id = 2 THEN vt.amount END,0)) AS result
FROM table_name vt
RIGHT JOIN list l ON vt.list_id = l.id
GROUP BY l.id
ORDER BY l.id
Results:
| id | result |
|----|--------|
| 1 | 41 |
| 2 | 23 |
| 3 | 0 |

Try something like this, as a start:
SELECT vt.list_id
, COALESCE(SUM(CASE WHEN direction_id = 1 THEN amount END), 0)
- COALESCE(SUM(CASE WHEN direction_id = 2 THEN amount END), 0) AS result
FROM table_name vt
GROUP BY vt.list_id
;
Result using your fiddle:
list_id
result
1
41
2
23
This just misses the cases where there are no vt rows for some list.
Use an outer join to address those cases.

SELECT SUM(CASE WHEN vt.direction_id = 1 THEN vt.amount ELSE 0 END) - SUM(CASE WHEN vt.direction_id = 2 THEN vt.amount ELSE 0 END) as amount,
lt.id as list_id
FROM list_table lt
LEFT OUTER JOIN vt_table vt
ON lt.id = vt.list_id
GROUP BY lt.id
ORDER BY lt.id

Related

Get the sum of (count(column1) + count(column2))

I have a table A:
entity_id name
------------------
1 Test1
2 Test2
3 Test3
4 Test4
5 Test5
6 Test6
I have a table B:
entity_id value1 value2
-----------------------------
1 10 20
1 15 30
2 10 25
1 9 45
3 null 1
2 45 50
3 20 null
I need to write a single query to select the entity_id and name from Table A and count the total occurrences for an entity_id of columns value1 and value2 from Table B and then the total of those column counts (null doesn't count).
So my output table would be:
entity_id name value1_count value2_count total_count
----------------------------------------------------------------------
1 Test1 3 3 6
2 Test2 1 2 3
3 Test3 1 1 2
4 Test4 0 0 0
5 Test5 0 0 0
6 Test6 0 0 0
I am having trouble summing the count of value1 and count of value2 and outputting that value in the total_count per unique entity_it.
This is the query I have so far:
SELECT DISTINCT a.entity_id, a.name
, count(b.value1) AS value1_count, count(b.value2) AS value2_count, sum(2) AS total_count
FROM a
LEFT JOIN b ON a.entity_id = b.entity_id
GROUP BY a.entity_id, a.name
I know that the sum(2) as total_count is incorrect and doesn't get me what I want.
SELECT entity_id, a.name
, COALESCE(b.v1_ct, 0) AS value1_count
, COALESCE(b.v2_ct, 0) AS value2_count
, COALESCE(b.v1_ct + b.v2_ct, 0) AS total_count
FROM a
LEFT JOIN (
SELECT entity_id, count(value1) AS v1_ct, count(value2) AS v2_ct
FROM b
GROUP BY 1
) b USING (entity_id);
db<>fiddle here
Aggregate first, join later. That's simpler and faster. See:
Query with LEFT JOIN not returning rows for count of 0
count() never produces NULL. Only the LEFT JOIN can introduce NULL values for counts in this query, so v1_ct and v2_ct are either both NULL or both NOT NULL. Hence COALESCE(v1_ct + v2_ct, 0) is ok. (Else, one NULL would nullify the other summand in the addition.)
try this :
WITH list AS
(
SELECT b.entity_id
, count(*) FILTER (WHERE b.value1 IS NOT NULL) OVER () AS value1_count
, count(*) FILTER (WHERE b.value2 IS NOT NULL) OVER () AS value2_count
FROM Table_B AS b
GROUP BY b.entity_id
)
SELECT a.entity_id, a.name
, COALESCE(l.value1_count, 0)
, COALESCE(l.value2_count,0)
, COALESCE(l.value1_count + l.value2_count, 0) AS total_count
FROM Table_A AS a
LEFT JOIN list AS l
ON a.entity_id = l.entity_id

Oracle count if column exists in other table with conditions

I have this table
COL_A FROM TO
------------------
D1 1 3
D2 3 7
And also this other table
COL_A VALUE
-------------
D1 0
D1 2
D1 5
D2 2
D2 5
D2 6
I want to obtain this. For each row in the first table, count the rows in the second table whose value is less than, between and greater than the FROM and TO columns.
COL_A FROM TO LESS_THAN_FROM BETWEEN_FROM_TO GREATER_THAN_TO
-------------------------------------------------------------------
D1 1 3 1 1 1
D2 3 7 1 2 0
Use join and conditional aggregation:
select t.col_a, t.from, t.to,
sum(case when o.value < t.from then 1 else 0 end) as less_than,
sum(case when o.value between t.from and t.to then 1 else 0 end) as in_between,
sum(case when o.value > t.to then 1 else 0 end) as greater_than
from this_table t join
other_table o
on t.col_a = o.col_a
group by t.col_a, t.from, t.to;

SQL query sum of total corresponding rows

I have two tables as below. Caseid from first table is referenced in second table along with accidents. What I am trying to get total different accidents for a case type. Below two tables I documented sample data and expected result.
Table case:
caseId CaseType
1 AB
2 AB
3 AB
4 CD
5 CD
6 DE
Table CaseAccidents:
AccidentId caseID AccidentRating
1 1 High
2 1 High
3 1 Medium
4 1 LOW
5 2 High
6 2 Medium
7 2 LOW
8 5 High
9 5 High
10 5 Medium
11 5 LOW
Result should look like:
CaseType TotalHIghrating TotalMediumRating TotalLOWRating
AB 3 2 2
CD 2 1 1
DE 0 0 0
To get the sum of every rating, you can Use a SUM(CASE WHEN) clause, adding 1 by every record that match the rating.
In your question, you have pointed out that you want to see all distinct CaseType, you can get it by using a RIGHT JOIN, this will include all records of case table.
select case.CaseType,
sum(case when caseAccidents.AccidentRating = 'High' then 1 else 0 end) as TotalHighRating,
sum(case when caseAccidents.AccidentRating = 'Medium' then 1 else 0 end) as TotalMediumRating,
sum(case when caseAccidents.AccidentRating = 'LOW' then 1 else 0 end) as TotalLowRating
from caseAccidents
right join case on case.caseId = caseAccidents.caseID
group by case.CaseType;
+----------+-----------------+-------------------+----------------+
| CaseType | TotalHighRating | TotalMediumRating | TotalLowRating |
+----------+-----------------+-------------------+----------------+
| AB | 3 | 2 | 2 |
+----------+-----------------+-------------------+----------------+
| CD | 2 | 1 | 1 |
+----------+-----------------+-------------------+----------------+
| DE | 0 | 0 | 0 |
+----------+-----------------+-------------------+----------------+
Check it: http://rextester.com/MCGJA9193
Have you use case in a select clause before?
select C.CaseType,
sum(case when CA.AccidentRating = 'High' then 1 else 0 end)
from Case C join CaseAccidents CA on C.CaseId = CA.CaseId
group by C.CaseType
Please see this. Sample query of the table and also that result
create table #case(caseid int,casetype varchar(5))
insert into #case (caseid,casetype)
select 1,'AB' union all
select 2,'AB' union all
select 3,'AB' union all
select 4,'CD' union all
select 5,'CD' union all
select 6,'DE'
create table #CaseAccidents(AccidentId int, CaseId int,AccidentRating varchar(10))
insert into #CaseAccidents(AccidentId, CaseId, AccidentRating)
select 1,1,'High' union all
select 2,1,'High' union all
select 3,1,'Medium' union all
select 4,1,'Low' union all
select 5,2,'High' union all
select 6,2,'Medium' union all
select 7,2,'Low' union all
select 8,5,'High' union all
select 9,5,'High' union all
select 10,5,'Medium' union all
select 11,5,'Low'
My script
select c.casetype,
sum(case when ca.AccidentRating='High' then 1 else 0 end) as TotalHighRating,
sum(case when ca.AccidentRating='Medium' then 1 else 0 end) as TotalMediumRating,
sum(case when ca.AccidentRating='Low' then 1 else 0 end) as TotalLowRating
from #case c
Left join #CaseAccidents ca
on c.Caseid=ca.Caseid
group by c.casetype
Hope This could help!
Another approach using Pivot operator
SELECT casetype,
[High],
[Medium],
[Low]
FROM (SELECT c.casetype,
AccidentRating
FROM case c
LEFT JOIN CaseAccidents ca
ON ca.CaseId = c.caseid)a
PIVOT (Count(AccidentRating)
FOR AccidentRating IN ([High],
[Medium],
[Low]) ) p
Try This code once.
select casetype,
sum(case when ca.AccidentRating='High' then 1 else 0 end ) as TotalHIghrating,
sum(case when ca.AccidentRating='Medium' then 1 else 0 end ) as TotalMediumRating ,
sum(case when ca.AccidentRating='Low' then 1 else 0 end ) as TotalLOWRating
from #case c
left join #CaseAccidents ca on c.caseid=ca.CaseId
group by casetype

Return only unique values

I'm trying to get counts of how many jobs were done, regardless of personnel working on them. What I need to do is filter out any duplicates, the catch is that the entire row isn't duplicate so DISTINCT won't work here. I want to filter out if there is any duplication based on JobCode, JobType TaskTime and day of week. So my table looks like the following:
JobCode JobType TaskTime EmployeeID M Tu W Th F Sa Su
==================================================================
1800 1 06:49 101 1 1 1 1 1 0 0
1800 1 06:49 102 1 0 0 0 0 0 0
1800 1 07:04 101 1 1 1 1 1 0 0
1800 1 07:26 101 1 1 1 1 1 0 0
1800 1 07:49 101 1 1 1 1 1 0 0
1800 2 15:55 101 1 1 1 1 1 0 0
1800 1 16:20 101 1 1 1 1 1 0 0
1800 1 16:50 101 1 1 1 1 1 0 0
1800 2 16:55 101 1 1 1 1 1 0 0
My SQL Query is like this
SELECT t1.JobCode, t1.JobType,
t1.M, t1.Tu, t1.W, t1.Th, t1.F, t1.Sa, t1.Su,
SUM(t1.M + t1.Tu + t1.W + t1.Th + t1.F + t1.Sa + t1.Su) as Totals
FROM Table1 AS t1
JOIN Table1 AS t1_overlap ON
t1_overlap.EmployeeID = t1.EmployeeID AND
t1_overlap.JobType = t1.JobType AND
t1_overlap.TaskTime = t1.TaskTime
AND
(
(t1.M = 1 AND t1_overlap.M = t1.M) OR
(t1.Tu = 1 AND t1_overlap.Tu = t1.Tu) OR
(t1.W = 1 AND t1_overlap.W = t1.W) OR
(t1.Th = 1 AND t1_overlap.Th = t1.Th) OR
(t1.F = 1 AND t1_overlap.F = t1.F) OR
(t1.Sa = 1 AND t1_overlap.Sa = t1.Sa) OR
(t1.Su = 1 AND t1_overlap.Su = t1.Su)
)
GROUP BY t1.JobCode, t1.JobType, t1.M, t1.Tu, t1.W, t1.Th, t1.F, t1.Sa, t1.Su
The data returned is like this
JobCode JobType M Tu W Th F Sa Su Totals
==================================================
1800 1 1 0 0 0 0 0 0 1
1800 1 1 1 1 1 1 0 0 30
1800 2 1 1 1 1 1 1 1 10
What I want to see is only unique values, so I don't want that first line that shows the job was worked on by employee 102 on only M because I'm already seeing that employee 101 worked on that same job on that same day and time. So what I want to see instead is the following:
JobCode JobType M Tu W Th F Sa Su Totals
==================================================
1800 1 1 1 1 1 1 0 0 30
1800 2 1 1 1 1 1 0 0 10
Really I don't need to see the days of the week, I'm just showing them here so I can see whats being returned. All I actually need to see for output is the JobCode, JobType and Totals like the following:
JobCode JobType Totals
======================
1800 1 30
1800 2 10
Help is greatly appreciated.
I think a quick subquery where you grab the max of each day, grouping by your key, then sum the results, would do the trick:
SELECT
jobcode,
jobtype,
sum(monday+tuesday+wednesday+thursday+friday+saturday+sunday) AS total
FROM
(
SELECT
jobcode,
jobtype,
tasktime,
max(m) as monday,
max(tu) as tuesday,
max(w) as wednesday,
max(th) as thursday,
max(f) as friday,
max(sa) as saturday,
max(su) as sunday
FROM Table1 T1
GROUP BY jobcode, jobtype, tasktime
) t2
GROUP BY jobcode, jobtype
There may be something more eloquent than that, but this should get the job done.
Try with the below query.
;With cte1
as
(SELECT ROW_NUMBER()OVER(PArtition by t1.JobCode,t1.JobType order by t1.JobCode,t1.JobType) RNO,t1.JobCode, t1.JobType,
SUM(t1.M + t1.Tu + t1.W + t1.Th + t1.F + t1.Sa + t1.Su) OVER(partition by t1.JobCode,t1.JobType ORDER BY t1.JobCode,t1.JobType ) as Totals
FROM Table1 AS t1
JOIN Table1 AS t1_overlap ON
t1_overlap.EmployeeID = t1.EmployeeID AND
t1_overlap.JobType = t1.JobType AND
t1_overlap.TaskTime = t1.TaskTime
AND
(
(t1.M = 1 AND t1_overlap.M = t1.M) OR
(t1.Tu = 1 AND t1_overlap.Tu = t1.Tu) OR
(t1.W = 1 AND t1_overlap.W = t1.W) OR
(t1.Th = 1 AND t1_overlap.Th = t1.Th) OR
(t1.F = 1 AND t1_overlap.F = t1.F) OR
(t1.Sa = 1 AND t1_overlap.Sa = t1.Sa) OR
(t1.Su = 1 AND t1_overlap.Su = t1.Su)
))
SELECT t1.JobCode, t1.JobType,Totals
FROM cte1
WHERE RNO=1
Basically, you can use the super-awesome ROW_NUMBER function and wrap the query so that you effectively 1) define a grouping, and 2) take ONLY the first row of each group. Look closely at the over (partition by ... order by ...) clause to understand how you can control the grouping and the "ranking" (which ones you want to make it through to the results).
select JobCode, JobType, Totals
from (
SELECT t1.JobCode, t1.JobType, SUM(t1.M + t1.Tu + t1.W + t1.Th + t1.F + t1.Sa + t1.Su) as Totals
,row_number() over (partition by t1.JobCode order by t1.JobType) as rseq
FROM Table1 AS t1
JOIN Table1 AS t1_overlap ON
t1_overlap.EmployeeID = t1.EmployeeID AND
t1_overlap.JobType = t1.JobType AND
t1_overlap.TaskTime = t1.TaskTime
AND
(
(t1.M = 1 AND t1_overlap.M = t1.M) OR
(t1.Tu = 1 AND t1_overlap.Tu = t1.Tu) OR
(t1.W = 1 AND t1_overlap.W = t1.W) OR
(t1.Th = 1 AND t1_overlap.Th = t1.Th) OR
(t1.F = 1 AND t1_overlap.F = t1.F) OR
(t1.Sa = 1 AND t1_overlap.Sa = t1.Sa) OR
(t1.Su = 1 AND t1_overlap.Su = t1.Su)
)
GROUP BY t1.JobCode, t1.JobType, t1.M, t1.Tu, t1.W, t1.Th, t1.F, t1.Sa, t1.Su
) x
where rseq = 1 --filter to keep only the "first" row (JobType) for each JobCode
Note that the comments about "how do you decide which one to keep" are valid, and this example assumes you want to see "one row per JobCode", and keep the "first JobType for that JobCode". This can be adjusted to fit, if you'll explain the logic you want to use a little more clearly.

SQL: Get multiple line entries linked to one item?

I have a table:
ID | ITEMID | STATUS | TYPE
1 | 123 | 5 | 1
2 | 123 | 4 | 2
3 | 123 | 5 | 3
4 | 125 | 3 | 1
5 | 125 | 5 | 3
Any item can have 0 to many entries in this table. I need a query that will tell me if an ITEM has all it's entries in either a state of 5 or 4. For example, in the above example, I would like to end up with the result:
ITEMID | REQUIREMENTS_MET
123 | TRUE --> true because all statuses are either 5 or 4
125 | FALSE --> false because it has a status of 3 and a status of 5.
If the 3 was a 4 or 5, then this would be true
What would be even better is something like this:
ITEMID | MET_REQUIREMENTS | NOT_MET_REQUIREMENTS
123 | 3 | 0
125 | 1 | 1
Any idea how to write a query for that?
Fast, short, simple:
SELECT itemid
,count(status = 4 OR status = 5 OR NULL) AS met_requirements
,count(status < 4 OR status > 5 OR NULL) AS not_met_requirements
FROM tbl
GROUP BY itemid
ORDER BY itemid;
Assuming all columns to be integer NOT NULL.
Builds on basic boolean logic:
TRUE OR NULL yields TRUE
FALSE OR NULL yields NULL
And NULL is not counted by count().
->SQLfiddle demo.
SELECT a.ID FROM (SELECT ID, MIN(STATUS) AS MINSTATUS, MAX(STATUS) AS MAXSTATUS FROM TABLE_NAME AS a GROUP BY ID)
WHERE a.MINSTATUS >= 4 AND a.MAXSTATUS <= 5
One way of doing this would be
SELECT t1.itemid, NOT EXISTS(SELECT 1
FROM mytable t2
WHERE itemid=t1.itemid
AND status NOT IN (4, 5)) AS requirements_met
FROM mytable t1
GROUP BY t1.itemid
UPDATE: for your updated requirement, you can have something like:
SELECT itemid,
sum(CASE WHEN status IN (4, 5) THEN 1 ELSE 0 END) as met_requirements,
sum(CASE WHEN status IN (4, 5) THEN 0 ELSE 1 END) as not_met_requirements
FROM mytable
GROUP BY itemid
simple one:
select
"ITEMID",
case
when min("STATUS") in (4, 5) and max("STATUS") in (4, 5) then 'True'
else 'False'
end as requirements_met
from table1
group by "ITEMID"
better one:
select
"ITEMID",
sum(case when "STATUS" in (4, 5) then 1 else 0 end) as MET_REQUIREMENTS,
sum(case when "STATUS" in (4, 5) then 0 else 1 end) as NOT_MET_REQUIREMENTS
from table1
group by "ITEMID";
sql fiddle demo
WITH dom AS (
SELECT DISTINCT item_id FROM items
)
, yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id
)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id
)
SELECT d.item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM dom d
LEFT JOIN yes y ON y.item_id = d.item_id
LEFT JOIN no n ON n.item_id = d.item_id
;
Can be done with an outer join, too:
WITH yes AS ( SELECT item_id, COUNT(*) AS good_count FROM items WHERE status IN (4,5) GROUP BY item_id)
, no AS ( SELECT item_id, COUNT(*) AS bad_count FROM items WHERE status NOT IN (4,5) GROUP BY item_id)
SELECT COALESCE(y.item_id, n.item_id) AS item_id
, COALESCE(y.good_count,0) AS good_count
, COALESCE(n.bad_count,0) AS bad_count
FROM yes y
FULL JOIN no n ON n.item_id = y.item_id
;
Nevermind, it was actually easy to do:
select ITEM_ID ,
sum (case when STATUS >= 3 then 1 else 0 end ) as met_requirements,
sum (case when STATUS < 3 then 1 else 0 end ) as not_met_requirements
from TABLE as d
group by ITEM_ID