Obtaining certain value from child table - tsql

I have two tables:
Customer (Parent)
Agreement (Child)
I need to display a certain value in my query if one of the many agreement statusID's has a certain value (e.g. written off). The join between the two tables is CustomerID.
So if a customer has 3 agreements and 2 agreements have a statusID of 1 and one has 5, I need to display a certain value. I only want to return one row in this query rather than the 3 which would occur in a typical join
Any suggestions?

select
CustomerID,
max(case when StatusId = 1 then 1 else 0 end) as HasStatus1,
max(case when StatusId = 2 then 1 else 0 end) as HasStatus2
--etc.
from Customer
left join Agreement
group by Customer.CustomerID
This will return a single row per customer due to the group by, with flags indicating if they have any agreements in each status of concern - if you're looking this up for a single CustomerID you'd obviously throw a where statement in there, and you could remove the group by as well (you'd have to remove CustomerID from the result set though of course).
Taking into account your comment, you'd want something like:
;with grouped as (
select
CustomerID,
max(case when StatusId = 1 then 1 else 0 end) as HasStatus1,
max(case when StatusId = 2 then 1 else 0 end) as HasStatus2,
max(case when StatusId = 5 then 1 else 0 end) as HasStatus5
--etc.
from Customer
left join Agreement
group by Customer.CustomerID
)
select
CustomerID,
case
when HasStatus5 = 1 then 5
when (HasStatus1 = 1 OR HasStatus2) and <no other status>) then 1
--etc.
else <Can't return StatusId here because there might be more than one... so whatever your default actually is> END as Result
from grouped

Related

postgresql RIGHT Join: limit returned rows

I have the following schema:
expenses
id
name, varchar
cost, double
date, DATE
category_id, int f_key
user_id, int f_key
1
Pizza
22.9
22/08/2022
1
1
2
Pool
34.9
23/08/2022
2
1
categories
id
name, varchar
1
Food
2
Leisure
3
Medicine
4
Fancy food
users_categories(user_id int foreign key, category_id foreign key)
user_id int f_key
category_id int f_key
1
1
1
2
1
3
2
4
And two users with id 1 and 2.
Relation between user and category is many to many.
Problem:
I want to get statistics (total cost amount and count) for all categories. For categories where there are no expenses I want to return 0. Here is my query:
SELECT categories.name as name, count(expenses.name) as count, round(SUM(price)::numeric,2) as sum
FROM expenses
Right JOIN categories ON expenses.category_id = categories.id
and expenses.category_id in (
select users_categories.category_id from users_categories where users_categories.user_id = 1
)
and expenses.id in(
Select expenses.id from expenses
join users_categories on expenses.category_id = users_categories.category_id
and expenses.user_id = 1
AND (extract(year from date) = 2022 OR CAST(2022 AS int) is null)
AND (extract(month from date) = 8 OR CAST(8 AS int) is null)
)
GROUP BY categories.id ORDER BY categories.id
The response is:
name
count
sum
Food
1
22.9
Leisure
1
33.9
Medicine
0
null
Fancy food
0
null
How I should edit my query to eliminate the last row, because this category doesn't belong to the user 1.
In your query you used user_categories as subquery so it will not filter category ids,
Try this Query
SELECT categories.name as name,count(expenses.name) as count, coalesce(round(SUM(price)::numeric,2),0) as sum from
categories
left join users_categories on users_categories.category_id= categories.id
left join expenses ON expenses.category_id = categories.id
AND (extract(year from date) = 2022 OR CAST(2022 AS int) is null)
AND (extract(month from date) = 8 OR CAST(8 AS int) is null)
where users_categories.user_id='1'
GROUP BY categories.name,categories.id ORDER BY categories.id
OUTPUT :
name count sum
Food 1 22.90
Leisure 1 34.90
Medicine 0 0
You want to move expenses.category_id in ... out of the ON condition and into a WHERE clause.
When it is in the ON clause, that means rows which were removed by the in-test just get NULL-fabricated anyway. You want to remove those rows after the NULL-fabrication is done, so that they remain removed. But why do you use that in-test anyway? Seems like it would be much simpler written as another join.
What I understand is that you are trying to get the count and sum of expenses for all the categories related to the user_id 1 within the month of august 2022.
Please try out the following query.
WITH statistics
AS (SELECT e.category_id,
Count(e.*) AS count,
Round(Sum(e.cost), 2) AS sum
FROM expenses e
WHERE e.user_id = 1
AND ( e.date BETWEEN '01/08/2022' AND '31/08/2022' )
GROUP BY e.category_id),
user_category
AS (SELECT uc.category_id,
COALESCE(s.count, 0) AS count,
COALESCE(s.sum, 0) AS sum
FROM users_categories uc
LEFT JOIN statistics s
ON uc.category_id = s.id
WHERE uc.user_id = 1)
SELECT c.NAME,
u.count,
u.sum
FROM categories c
INNER JOIN user_category u
ON u.category_id = c.id;

PostgreSQL Query not returning the proper results

So this is my table structure
learning_paths
id
name
version
created_at
updated_at
learning_path_levels
id
name
learning_path_id
order
created_at
updated_at
learning_path_level_nodes
id
name
description
documentation_links
evaluation_methodology
learning_path_level_id
created_at
updated_at
learning_path_node_users
id
learning_path_level_node_id
user_id
evaluated_by
evaluated_at
is_successful
created_at
updated_at
I'm writing a query to retrieve the learning_path_name, count of the amount of levels each learning path has, the pending and completed nodes per level for the user, and the total amount of nodes per level.
I have the following query
select learning_paths."name",
sum(case when learning_path_node_users.is_successful and learning_path_node_users.user_id is not null then 1 else 0 end) as completed_nodes,
sum(case when learning_path_node_users.is_successful = false or learning_path_node_users.user_id is null then 1 else 0 end) as pending_nodes,
count(learning_path_levels.id) as total_levels,
count(*) as total_nodes
from learning_path_level_nodes
inner join learning_path_levels on learning_path_levels.id = learning_path_level_nodes.learning_path_level_id
inner join learning_paths on learning_paths.id = learning_path_levels.learning_path_id
left join learning_path_node_users on learning_path_node_users.learning_path_level_node_id = learning_path_level_nodes.id
group by learning_paths."name"
which returns:
name
completed_nodes
pending_nodes
total_levels
total_nodes
Devops
5
3
8
8
QA
0
1
1
1
Project manager
3
3
6
6
AI
0
5
5
5
Everything is correct, except for the levels count,
for example, for Devops,it should be 2, and it is returning 8
for Project Manager it should be 2, and it is returning 6
a pattern I see is that it returns the amount of nodes as the amount of levels,
How can I fix this?
I'd really appreciate any help or suggestions, as I've been struggling with this.
Thanks in advance
EDIT: As per your suggestion, I'm attaching a fiddle with the tables and data.
https://dbfiddle.uk/?rdbms=postgres_14&fiddle=f29676ff7051686a28de96928db1e3a6
While I don't get the exact results you want, I think you want to add a distinct to your count for the total levels:
select
lp.name,
sum(case when u.is_successful and u.user_id is not null then 1 else 0 end) as completed_nodes,
sum(case when u.is_successful = false or u.user_id is null then 1 else 0 end) as pending_nodes,
count(distinct lpl.id) as total_levels, -- added "distinct"
array_agg (lpl.id) as level_detail, -- debugging aid
count(*) as total_nodes
from
learning_path_level_nodes n
join learning_path_levels lpl on lpl.id = n.learning_path_level_id
join learning_paths lp on lp.id = lpl.learning_path_id
left join learning_path_node_users u on u.learning_path_level_node_id = n.id
group by
lp.name
To help expose the rationale, I added the field level_detail, which you can delete, to show why the results are what they are. You can obviously remove that once the results are what you want.
If it's not what you expect, perhaps you can explain or give by example what I might be missing.

How to normalize group by count results?

How can the results of a "group by" count be normalized by the count's sum?
For example, given:
User Rating (1-5)
----------------------
1 3
1 4
1 2
3 5
4 3
3 2
2 3
The result will be:
User Count Percentage
---------------------------
1 3 .42 (=3/7)
2 1 .14 (=1/7)
3 2 .28 (...)
4 1 .14
So for each user the number of ratings they provided is given as the percentage of the total ratings provided by everyone.
SELECT DISTINCT ON (user) user, count(*) OVER (PARTITION BY user) AS cnt,
count(*) OVER (PARTITION BY user) / count(*) OVER () AS percentage;
The count(*) OVER (PARTITION BY user) is a so-called window function. Window functions let you perform some operation over a "window" created by some "partition" which is here made over the user id. In plain and simple English: the partitioned count(*) is calculated for each distinct user value, so in effect it counts the number of rows for each user value.
Without using a windowing function or variables, you will need to cross join a grouped subquery on a second "maxed" subquery then select again to return a subset you can work with.
SELECT
B.UserID,
B.UserCount,
A.CountAll
FROM
(
SELECT
CountAll=SUM(UserCount)
FROM
(
SELECT
UserCount=COUNT(*)
FROM
MyTable
GROUP BY
UserID
) AS A
)AS C
CROSS JOIN(
SELECT
UserID,
UserCount=COUNT(*)
FROM
MyTable
GROUP BY
UserID
)AS B

Show Rows Data Into Column Wise

i wants to show rows data into columns. suppose if rows type increased then number of columns also increased. Information as follow:
Please see picture to get idea
You can use COUNT with CASE WHEN:
SELECT t.Name AS Type,
COUNT(*) AS NumberOfCase,
COUNT(CASE WHEN s.Name = 'Resolved' THEN 1 END) AS Resolved,
COUNT(CASE WHEN s.Name = 'Pending' THEN 1 END) AS Pending,
COUNT(CASE WHEN s.Name = 'Waiting' THEN 1 END) AS Waiting
FROM Type t
LEFT JOIN "Case" c
ON c.CaseType = t.TypeId
LEFT JOIN "Status" s
ON c.CaseStatus = s.StatusId
GROUP BY t.Name;
SqlFiddleDemo

Help with difficult 'group by' clause

need some your help with a query.
I have a table Managers (ManagerId, ManagerName)
I have a table Statuses (StatusId, StatusName)
(There's about 10 statuses in that table)
I have a table Clients (ClientId, ClientName, ManagerId, StatusId, WhenAdded)
(WhenAdded is a datetime type)
It's obvious that field 'ManagerId' refers to a table 'Managers' and field 'StatusId' refers to a table 'Statuses'.
User wants to get some statistics about Managers over a period of time (from startDate to endDate using field 'WhenAdded') in the following table.
Columns:
ManagerName, NumberOfClients, NumberOfClientsWithStatus1, NumberOfClientsWithStatus2, NumberOfClientsWithStatus3 and so on.
Number of columns with name NumberOfClientsWithStatusI where i is a number of statuses equal to number of rows in table 'Statuses'.
How can I do that?
t-sql, sql server 2008 r2 express edition.
SELECT
ManagerName,
COUNT(*) AS NumberOfClients,
COUNT(CASE WHEN S.StatusId = 1 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus1,
COUNT(CASE WHEN S.StatusId = 2 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus2,
COUNT(CASE WHEN S.StatusId = 3 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus3,
...
FROM
Clients C
JOIN
Managers M ON C.ManagerId = M.ManagerId
JOIN
Statuses S ON C.StatusId = S.StatusId
WHERE
M.WhenAdded BETWEEN #startDate AND #endDate
GROUP BY
M.ManagerName
Note: there is no clean way to add arbritrary numbers of status columns in SQL (not just SQL Server) because its a fixed column output. You'd have to change the query for status, unless you deal with this in the client
Edit, after comment
SELECT
ManagerName,
COUNT(*) AS NumberOfClients,
COUNT(CASE WHEN S.StatusId = 1 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus1,
COUNT(CASE WHEN S.StatusId = 2 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus2,
COUNT(CASE WHEN S.StatusId = 3 THEN 1 ELSE NULL END) AS NumberOfClientsWithStatus3,
...
FROM
Managers M ON C.ManagerId = M.ManagerId
LEFT JOIN
Clients C
LEFT JOIN
Statuses S ON C.StatusId = S.StatusId
WHERE
M.WhenAdded BETWEEN #startDate AND #endDate
GROUP BY
M.ManagerName
If you know that statuses table will always contain a limited number of statuses, you can do this:
SELECT M.ManagerName,
COUNT(C.ClientId) NumberOfClients,
SUM(CASE WHEN S.StatusId= 1 THEN 1 ELSE 0 END) NumberOfClientsWithStatus1,
SUM(CASE WHEN S.StatusId= 2 THEN 1 ELSE 0 END) NumberOfClientsWithStatus2,
...
FROM Clients C
JOIN Managers M on M.ManagerId = C.ManagerId
JOIN Statuses S on S.StatusId = C.StatusId
WHERE C.WhenAdded BETWEEN startDate AND endDate
GROUP BY ManagerName