Postgres Group By with additional condition - database

I'm having problem with I would think simple query.
I'm trying to group users, by given name so in result I get something like this:
| name_1 | 0 |
| name_2 | 0 |
| name_3 | 2 |
| name_4 | 0 |
| name_5 | 0 |
And I achieve this with this query:
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
GROUP BY r.name
Now I want to add another condition, and another joined table. UserRole contains reference to Role and User, so my query looks like this:
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
LEFT JOIN users as u
ON ur.user_id = u.id
WHERE u.status != 'deleted'
GROUP BY r.name, u.status
But instead of getting table as on the beginning. I get something like this(according to if I look for deleted or active user)
| name_3 | 2 | <- for active
[] <- for deleted
And I want to achieve something like first table from the post. Any idea?
EDIT
Expected result would be:
| name_1 | 0 |
| name_2 | 0 |
| name_3 | 0 |
| name_4 | 0 |
| name_5 | 0 |

You have to move u.status in the join like this. If you don't, the left join is considered as an inner join
SELECT r.name, count(ur.user_id)
FROM public.user_roles as ur
RIGHT JOIN roles as r
ON ur.role_id = r.id and r.company_id = 1
LEFT JOIN users as u
ON ur.user_id = u.id and u.status != 'deleted'
GROUP BY r.name, u.status

Related

Filtering duplicates on join without effecting first table

Basically, I have the following data queried from three tables
{ _Document330 } { _Document224_VT3836 } { _Document224 }
_IDRef | _Number | _Document224_IDRRef | _LineNo | _IDRef | _Marked |
0x9468 | К0000000598 | 0x5972941689C3 | 1 | 0x5972941689C3 | 0x01 |
0x9468 | К0000000598 | 0x5A474968456C | 1 | 0x5A474968456C | 0x00 |
0x8543 | K0000000009 | NULL | NULL | NULL | NULL |
This is the only order I can join this tables
SELECT *
FROM dbo._Document330 T12
LEFT OUTER JOIN dbo._Document224_VT3836 T41
ON T41._Fld3852_RRRef = T12._IDRRef
LEFT OUTER JOIN dbo._Document224 T42
ON T42._IDRRef = T41._Document224_IDRRef
This is what I need in output
{ _Document330 } { _Document224_VT3836 } { _Document224 }
_IDRef | _Number | _Document224_IDRRef | _LineNo | _IDRef | _Marked |
0x9468 | К0000000598 | 0x5A474968456C | 1 | 0x5A474968456C | 0x00 |
0x8543 | K0000000009 | NULL | NULL | NULL | NULL |
If I select this data like this:
SELECT *
FROM dbo._Document330 T12
LEFT OUTER JOIN dbo._Document224_VT3836 T41
ON T41._Fld3852_RRRef = T12._IDRRef
RIGHT JOIN dbo._Document224 T42
ON T42._IDRRef = T41._Document224_IDRRef
AND T42._Marked = 0x00
Then I would not have a row with the number K0000000009
It looks like you're doing
SELECT *
FROM a
LEFT JOIN b
and your problem is that for each row in a you're getting more than one output row becuase jour JOIN condition matches more than one row from b to a row in a.
The solution in this situation is to use a sub-query to make from b a new table whose key is the join condition. So you get something that looks like
SELECT *
FROM a
LEFT JOIN (
SELECT foo, AGG(bar),...
FROM b
GROUP BY foo
) AS b
ON a.foo = b.foo
The trick then is to choose the aggregate functions AGG so that you pick appropriate values to represent the records from b.
Sometimes simple aggregates aren't appropriate, for example you want the first (or last -- reverse the sort order) record which you can obtain with something like
SELECT *
FROM a
LEFT JOIN (
SELECT *,
ROW_NUMBER() OVER (PARTITOIN BY foo ORDER BY bar) AS RowNumber
FROM b
) AS b
ON a.foo = b.foo AND b.RowNumber = 1

Combine two queries with different 'FROM' tables but similar 'JOIN' tables

I have two queries that I'm trying to combine into one result set.
Query 1:
SELECT t1.evalID, t2.[Order], COUNT(t2.StepID) AS 'Total Categories'
FROM Evals t1
JOIN Steps t2 ON t1.TemplateID = t2.TemplateID
JOIN GradingCats t3 ON t2.StepID = t3.StepID
GROUP BY t1.EvalID, t2.[Order]
ORDER BY t2.[Order]
Query 2:
SELECT t4.EvaluatorID, t6.StepID, t6.[Order], COUNT(t4.Grade) AS 'Grades Entered'
FROM Grading t4
JOIN GradingCats t5 ON t4.GradingCatID = t5.GradingCatID
JOIN Steps t6 ON t5.StepID = t6.StepID
GROUP BY t6.StepID, t4.EvaluatorID, t6.[Order]
My end goal is to locate which steps of an evaluation have missing grades.
edit (sample data):
Query #1
|---------------------|------------------|---------------------|
| evalID | Order | Total Categories |
|---------------------|------------------|---------------------|
| 81 | 01.00 | 17 |
|---------------------|------------------|---------------------|
| 81 | 02.00 | 17 |
|---------------------|------------------|---------------------|
| 81 | 03.00 | 17 |
|---------------------|------------------|---------------------|
Query #2
|---------------------|------------------|---------------------|------------------|
| evaluatorID | Step | Order | Grades Entered |
|---------------------|------------------|---------------------|------------------|
| 1178 | 609 | 01.00 | 2 |
|---------------------|------------------|---------------------|------------------|
| 1178 | 615 | 02.00 | 3 |
|---------------------|------------------|---------------------|------------------|
| 9441 | 609 | 01.00 | 17 |
|---------------------|------------------|---------------------|------------------|
| 9441 | 609 | 02.00 | 17 |
|---------------------|------------------|---------------------|------------------|
| 9441 | 609 | 03.00 | 17 |
|---------------------|------------------|---------------------|------------------|
Starting with the first query which shows all the steps associated with an EVAL, you can LEFT OUTER JOIN the second query, and the steps that are NULL on the right side of the query will be the ones that are missing grades.
In order to do this, there must be some way in your tables to link Grading to Evals. This column is not evident from the code you posted, but I will assume it is there. Maybe it's through GradingCats.
In shortened psuedo-code, just to show what I mean:
SELECT ...
FROM Evals e
INNER JOIN Steps s ON e.TemplateID = s.TemplateID
LEFT OUTER JOIN Grading g ON g.EvalID = e.EvalID --use whatever means you have to show which Eval a Grade is from
LEFT OUTER JOIN Steps gs ON {join to Grading through GradingCats as in your second query}
WHERE gs.StepID IS NULL
In analyzing the result of this query, all the Steps of every Eval will be in s.StepID, and when the same row has a NULL for gs.StepID, that means that step did not get a grade.
Note that you won't want to do any GROUP BY in this query, since you want a row-level analysis.
A coworker (with more knowledge of the data than I) slightly modified my query:
SELECT query1.stepID, Categories, Graded
FROM
(
SELECT rs.stepid, COUNT(c.category) AS 'Categories'
FROM Evals e
JOIN RunScriptSteps rs ON e.TemplateID = rs.TemplateID
JOIN GradingCats c ON rs.StepID = c.StepID
WHERE EvalID = *(someNumber)*
GROUP BY rs.stepid
)AS query1
LEFT JOIN
(
SELECT s.StepID, COUNT(Grade) AS 'Graded'
FROM Grading g
JOIN GradingCats c ON g.GradingCatID = c.GradingCatID
JOIN Steps s ON c.StepID = s.StepID
WHERE EvalID = *(someNumber)*
GROUP BY s.stepid
) AS query2
ON query1.stepid = query2.stepid
ORDER BY stepid ASC

Filter UNION results based on MAX() of column

I have two queries and their result sets are brought together with a UNION.
I'm trying to filter out the rows based on the MAX() of th FilterColumn, but I can't figure out how to do this.
+-----------------+----------+--------------+
| PropDescription | PropCode | FilterColumn |
+-----------------+----------+--------------+
| 476SADDR1 | Finland | 2 |
| 477SADDR1 | Sweden | 2 |
| 1SADDR1 | 2038 | 1 |
| 2SADDR1 | 2030 | 1 |
| 3SADDR1 | 15 | 1 |
| 4SADDR1 | 00000002 | 1 |
| 6SADDR1 | 500 | 1 |
| 8SADDR1 | 556 | 1 |
| 9SADDR1 | 821 | 1 |
+-----------------+----------+--------------+
SELECT
PropDescription
, PropCode
, FilterColumn
FROM
(SELECT DISTINCT
CASE
WHEN PL.Type = 11
THEN PL.ADDR
ELSE P.ADDR
END AS N'PropDescription'
,CASE
WHEN PL.TYPE = 3
THEN P.CODE
ELSE LPA.Value
END AS N'PropCode'
, 2 AS FilterColumn
FROM PROPERTY PL
INNER JOIN PROPLIST LP2 ON PL.ID = LP2.PropList
INNER JOIN PROPERTY P ON LP2.Property = P.ID
INNER JOIN PropAttribute LPA ON LPA.PropList = PL.ID
WHERE 1 = 1
AND PL.ID IN (SELECT Property from PROPLIST where PropList IN (47,46))
AND P.TYPE = 3
UNION ALL
SELECT DISTINCT
CASE
WHEN PL.Type = 11
THEN PL.ADDR
ELSE P.ADDR
END AS N'PropDescription'
,CASE
WHEN PL.TYPE = 3
THEN P.CODE
ELSE LPA.Value
END AS N'PropCode'
, 1 AS FilterColumn
FROM PROPERTY PL
INNER JOIN PROPLIST LP2 ON PL.ID = LP2.PropList
INNER JOIN PROPERTY P ON LP2.Property = P.ID
LEFT JOIN PropAttribute LPA ON LPA.PropList = PL.ID
WHERE 1 = 1
AND PL.ID IN (SELECT Property from PROPLIST where PropList IN (1,2,3,4))
AND PL.TYPE = 3
) AS ResultSet
I know this is quite simple, but I can't just figure it out right now.
Expected output is:
+-----------------+----------+--------------+
| PropDescription | PropCode | FilterColumn |
+-----------------+----------+--------------+
| 476SADDR1 | Finland | 2 |
| 477SADDR1 | Sweden | 2 |
+-----------------+----------+--------------+
Try this:
;with cte as
( --Your union),
filtered as
(*, rank() over (order by filtercolumn desc) rank from cte)
select * from filtered
where rank = 1

Select column based on whether a specific row in another table exists

Question is similar to this one How to write a MySQL query that returns a temporary column containing flags for whether or not an item related to that row exists in another table
Except that I need to be more specific about which rows exists
I have two tables: 'competitions' and 'competition_entries'
Competitions:
ID | NAME | TYPE
--------------------------------
1 | Example | example type
2 | Another | example type
Competition Entries
ID | USERID | COMPETITIONID
---------------------------------
1 | 100 | 1
2 | 110 | 1
3 | 110 | 2
4 | 120 | 1
I want to select the competitions but add an additional column which specifies whether the user has entered the competition or not. This is my current SELECT statement
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e
ON e.COMPETITIONID = c.ID
My desired result set from setting the #userid parameter to 110 is this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 1
2 | Another | example type | 1
But instead I get this
ID | NAME | TYPE | ENTERED
-------------------------------------
1 | Example | example type | 0
1 | Example | example type | 1
1 | Example | example type | 0
2 | Another | example type | 1
Because it's counting the entries for all user ids
Fixing your query
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
MAX(CASE
WHEN e.ID IS NOT NULL AND e.USERID = #userid THEN 1
ELSE 0
END
) AS 'ENTERED'
FROM competitions AS c
LEFT OUTER JOIN competition_entries AS e ON e.COMPETITIONID = c.ID
GROUP BY
c.[ID],
c.[NAME],
c.[TYPE]
An alternative is to rewrite it using EXISTS which is pretty much the same but may be easier to understand.
BTW, using single quotes on the column name is deprecated. Use square brackets.
SELECT
c.[ID],
c.[NAME],
c.[TYPE],
CASE WHEN EXISTS (
SELECT *
FROM competition_entries AS e
WHERE e.COMPETITIONID = c.ID
AND e.USERID = #userid) THEN 1 ELSE 0 END [ENTERED]
FROM competitions AS c

MySQL query: list of tag.names and lang which isn't already inserted in combination inside images_urls

I can't figure out any good way to get a list of tag.names and lang which isn't already inserted in combination inside images_urls.
My database looks something like this.
tags
name user_id
+-------+---------+
|hi | 1 |
|friend | 1 |
|friend | 2 |
|people | 2 |
+-------+---------+
users
id lang
+---+------+
| 1 | en |
| 2 | sv |
+---+------+
images_urls
name lang
+--------+------+
| hi | en |
| friend | sv |
+--------+------+
What I would like to have returned would be:
result
name lang
+-------+------+
|friend | en |
|people | sv |
+-------+------+
I have tried something like this:
SELECT tags.name, users.lang
FROM tags, users
WHERE tags.user_id = users.id
AND CONCAT(tags.name, ',', users.lang) NOT IN(
SELECT DISTINCT CONCAT(name, ',', lang) FROM images_urls
)
GROUP BY CONCAT(name, ',', lang)
ORDER BY SUM(tags.count) DESC
LIMIT 20;
I'm not sure why images_urls has a name instead of a user_id, but this should work:
SELECT t.name, u.lang
FROM tags t
JOIN users u ON ( u.id = t.user_id )
LEFT JOIN images_urls iu ON ( iu.name=t.name AND iu.lang=u.lang )
WHERE iu.name IS NULL
Using the LEFT JOIN returns NULL for rows that do not exist in images_urls, so I check for that.
SELECT tags.name, users.lang
FROM tags, users
WHERE tags.user_id = users.id
AND users.name NOT IN (SELECT name from images_urls);
I would suggest images_urls should contain user_id not name, but thats a different issue.
column NOT IN ('name`','name1','name2','name3')
is also valid for testing purposes.

Resources