Here DB Structure:
turns DB Table
+-----------+-------------+------------+------------+----------------+
| turnNumber| userId | locationId | status | itemsPurchased |
+-----------+-------------+------------+------------+----------------+
| 32 | 1 | 1 | 1 | 20 |
| 33 | 2 | 1 | 0 | 0 |
+-----------+-------------+------------+------------+----------------+
locations DB Table
+-----------+---------+---------+
| id | Address | ZIPCode |
+-----------+---------+---------+
| 1 | ... | 12345 |
| 2 | ... | 67890 |
+-----------+---------+---------+
Im trying to get every location data (Address, ZIPCode...) + the amount of turns pending (with status 0) per location + the sum of items purchased per location (for all turns even if their state is 1)
Here my Query:
SELECT
l.*,
COUNT(t.id) AS turns,
SUM(IF(t.itemsPurchased > 0, t.itemsPurchased, 0)) AS items
FROM turns t RIGHT OUTER JOIN locations l
ON t.locationId = l.id
WHERE t.status = 0 AND
l.ZIPCode = XXXX
GROUP BY l.id
The thing is when i put the t.status condition it doesnt get the location data when theres no turn with status 0 in turns table, also even if it would, i guess the count for items purchased would take in count only turns with status 0 and not all turns.
Im wondering if theres a way to get all data within the same query, please Help!
Edit:
The expected output is as following:
+-----------+-------------+------------+------------+----------------+
| id | Address | ZIPCode | turns | itemsPurchased |
+-----------+-------------+------------+------------+----------------+
| 1 | ... | 12345 | 1 | 20 |
+-----------+-------------+------------+------------+----------------+
The condition "t.status = 0" in the WHERE clause negates the "outerness" of the join; the same result we'd get with an INNER JOIN.
With the outer join, any rows in locations that don't have a matching row in turns will be returned with NULL values for the all of the t. columns. The unmatched rows from locations are going to get excluded by the condition in the WHERE clause.
Consider relocating that condition from the WHERE clause to ON clause of the outer join.
Or consider relocating that condition into an aggregate expression.
As an example:
SELECT l.id
, l.zipcode
, SUM(IF(t.status = 0, 1, 0)) AS turns
, SUM(IF(t.status = 0 AND t.itemspurchased > 0, t.itemspurchased, 0)) AS items
FROM locations l
LEFT
JOIN turns t
ON t.locationid = l.id
AND t.status = 0
WHERE l.zipcode = XXXX
GROUP
BY l.id
, l.zipcode
Using a LEFT JOIN and putting the criteria on different places.
To avoid that by using a criteria in the WHERE clause for the LEFT JOIN'd table, that it would give it the effect on as it were an INNER JOIN.
SELECT
loc.ZIPCode,
COUNT(DISTINCT CASE turns.status WHEN 0 THEN turns.id END) AS turns,
SUM(CASE
WHEN turns.status = 1 AND turns.itemsPurchased > 0
THEN turns.itemsPurchased
ELSE 0
END) AS items
FROM locations loc
LEFT JOIN turns ON turns.locationId = loc.id
WHERE loc.ZIPCode = 12345
GROUP BY loc.id, loc.ZIPCode
Related
Now I have that structure (very simplify):
promotion:
id | name | level1 | points1| level2 | points2 | client_id
1 | A | 10 | 12 | 20 | 15 | 1
client:
id | name | value
1 | john | 15
And that's how I calculate the level:
SELECT
name,
CASE
WHEN client.value >= promotion.level2 THEN promotion.points2
WHEN client.value >= promotion.level1 THEN promotion.points1
ELSE "None"
END as points
FROM promotion
JOIN client ON client.id = promotion.client_id
This is working well, but I would like to have such structure:
promotion:
id | name | client_id
1 | A | 1
level:
id | name | level | points | promotion_id
1 | level1 | 10 | 12 | 1
2 | level2 | 10 | 15 | 1
client:
id | name | value
1 | john | 15
But I don't have any idea how to use it in my query to get points...
SELECT
name,
CASE
???
END as points
FROM promotion
JOIN client ON client.id = promotion.client_id
LEFT JOIN level ON promotion.id= level.promotion_id
This query will give you the desired results. It uses a LEFT JOIN to find any rows in the level table which are lower than the client value, and then takes the MAX of those values, using COALESCE to set the value to None if there are no levels below the client's value:
SELECT p.name AS promotion,
c.name AS client,
COALESCE(MAX(l.points), 'None') AS points
FROM promotion p
JOIN client c ON c.id = p.client_id
LEFT JOIN level l ON l.promotion_id = p.id AND l.level < c.value
GROUP BY p.name, c.name
Output for your sample data:
promotion client points
A john 12
Demo on dbfiddle
I would use a correlated subquery:
select p.name,
(select l.points
from level l
where l.promotion_id = p.points and
l.level <= c.value
order by l.level desc
limit 1
) as points
from promotion p join
client c
on c.id = p.client_id;
The point of using a correlated subquery is to avoid an aggregation on the full data. With an index on level(promotion_id, level, points), this should have better performance.
I have tables Match and Reaction as following:
REACTION
+----------+----------+----------+----------+
| user_id | game_id | item_id | reaction |
+----------+----------+----------+----------+
| 1 | 1 | 1 | 1 |
| 1 | 1 | 2 | 1 |
| 2 | 1 | 1 | 1 |
| 2 | 1 | 2 | 0 |
+----------+----------+----------+----------+
MATCH:
+----------+----------+
| game_id | item_id |
+----------+----------+
| 1 | 1 |
| 1 | 2 |
+----------+----------+
Now I want (if possible without subqueries) to select ALL item_ids from MATCH table AND count of rows where field reaction in table Reaction is equal to 1 for user with id = 2. For example, for defined tables I want to get following results:
+----------+----------+
| item_id | count |
+----------+----------+
| 1 | 1 |
| 2 | 0 |
+----------+----------+
I've tried something like
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
WHERE reaction.reaction = 1 AND match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but it didn't work as expected. I cannot get count for particular user.
I think you are close. I think you just need to move conditions on the second table to the ON clause:
SELECT m.item_id, COUNT(r.user_id) as c
FROM match m LEFT JOIN
reaction r
ON r.item_id = m.item_id AND
r.reaction = 1 AND
r.user_id = 2
WHERE m.game_id = 2
GROUP BY m.item_id;
I'm not sure what the HAVING clause is for, because you seem to want counts of 0.
Note that this also introduces table aliases so the query is easier to write and to read.
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match JOIN reaction ON (reaction.item_id = match.item_id and reaction.reaction = 1 AND match.game_id = 2)
GROUP BY match.item_id
HAVING COUNT(reaction.user_id)
I think you need to filter 'before' join -> so use the 'on' clause.
Filters in where are applied after the join is made while filter applied on on clause are applied before the join is made
You have not game_id = 2 so this should return no value
and you should not use left joined table columns in where condition otherwise these wprk as inner join ... in these cases you shou move the related condition in ON clause
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
WHERE match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but try also
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
GROUP BY match.item_id
I have a table like this :
---------------------------------------
| Actions |
---------------------------------------
| action_id | user_id | action_active |
---------------------------------------
| 1 | 1 | 0 |
---------------------------------------
| 2 | 2 | 1 |
---------------------------------------
| 3 | 1 | 0 |
---------------------------------------
| 4 | 2 | 0 |
---------------------------------------
I want to retrieve all rows where a user has all of his rows as action_active = 0. If he has just one action_active as 1, don't retrieve it.
In this example, it should only retrieve the row 1 and 3, since the user 1 has all of his rows at action_active = 0.
I thought about something like this, but I'm not sure about how right it is :
SELECT *
FROM Actions AS a
WHERE action_active = ALL (SELECT action_active FROM actions as s WHERE action_active = 0 where a.idx_user = s.idx_user)
I'm not sure my query is right.
Thanks !
Calculate sum in a sub-query to find users with all zero values and join that with main select
SELECT a.*
FROM actions a
JOIN (SELECT user_id, SUM(action_active) AS sum
FROM actions
GROUP BY user_id) AS sum_a ON sum_a.user_id = a.user_id
WHERE sum = 0
Use NOT EXISTS:
SELECT a.*
FROM actions a
WHERE NOT EXISTS (SELECT 1
FROM actions a2
WHERE a2.user_id = a.user_id AND
a2.action_active <> 0
);
This should have better performance than a solution using group by -- and this makes direct use use of an index on actions(user_id, action_active).
You can also phrase this using a LEFT JOIN:
SELECT a.*
FROM actions a LEFT JOIN
actions a2
ON a2.user_id = a.user_id AND a2.action_active <> 0
WHERE a2.user_id IS NULL;
So I have a translations table which holds various representations of a place name. I join this table on the placeId, where this table serves as the right side (the left side contains info about the place).
However, joining on placeId results in possibly more translations, see table below. The contents of preferredName/shortName/historicName are all 0 or 1. There is no rule though that each translation should have at least 1 record with preferredName=1.
So what I end up with is: how can I select only 1 of these translations, in particular:
if record exists with preferredName=1: use this
else if record exists with shortName=1: use this
else if neither is true (so both are 0) then pick that record
.
+---------------+---------+------------------+---------------+-----------+--------------+
| translationId | placeId | alternateName | preferredName | shortName | historicName |
+---------------+---------+------------------+---------------+-----------+--------------+
| 4832 | 554 | 'New York' | 1 | 0 | 0 |
| 4833 | 554 | 'NY' | 0 | 1 | 0 |
| 4834 | 554 | 'New York City' | 0 | 0 | 0 |
+---------------+---------+------------------+---------------+-----------+--------------+
Any clue? It basically boils down to filtering multiple matches on the right table to 1 record on the left table.
You could join 3 times with different conditions and COALESCE the results:
select
a.*,
coalesce(name1.alternateName, name2.alternateName, name3.alternateName) as alternateName
from _table1_ as a
left join _table2_ as name1 on (a.placeId = name1.placeId and name1.preferredName=1)
left join _table2_ as name2 on (a.placeId = name2.placeId and name2.shortName=1)
left join _table2_ as name3 on (a.placeId = name3.placeId and name3.preferredName=0 and name3.shortName=0)
group by a.placeId
Following my previous comment, you can make your JOIN request on a subrequest.
try this :
SELECT * FROM mytable WHERE placeId = 554 ORDER BY preferredName DESC, shortName DESC;
it will order results exactly as you want, with your preferrence. Juste add a limit 1 ant you are right !
SELECT * FROM mytable WHERE placeId = 554 ORDER BY preferredName DESC, shortName DESC LIMIT 1;
so, just add you first part of request and make your join on this ;)
EDIT : the following is relative to a comment
LEFT OUTER JOIN
(SELECT * FROM alternatename
WHERE isoLanguage="NL" AND geonameid = a1.geonameid
ORDER BY isPreferredName DESC, isShortName DESC
LIMIT 1
)
AS alternate10
ON
alternate10.geonameid = a1.geonameid
I'm trying to use the SUM function to count rows from 3 tables, which is however, not working effectively since when the total_files and total_notes are returned, they both are the same when there is at least one file and then total_files will take the same value as total_notes which I don't understand why it's doing that.
It should count the number of rows which is relevant to each record that will get return as a record list with a count of total files, total notes and total contacts assigned to the record per record row (the data of files, notes and contacts do not get displayed only counted).
My query is shown below:
SELECT rec.street_number,
rec.street_name,
rec.city,
rec.state,
rec.country,
rec.latitude,
rec.longitude,
LEFT(rec.description, 250) AS description,
usr.username,
usr.full_name,
ppl.person_id,
ppl.first_name,
ppl.last_name,
SUM(IF(rlk.record_id = rec.record_id, 1, 0)) AS total_contacts,
SUM(IF(files.record_id = rec.record_id, 1, 0)) AS total_files,
SUM(IF(notes.record_id = rec.record_id, 1, 0)) AS total_notes,
(
SELECT COUNT(DISTINCT rec.record_id)
FROM records rec
WHERE rec.marked_delete = 0 AND rec.is_archive = 0
) AS total_records
FROM
(
records rec
INNER JOIN members usr ON rec.user_id = usr.user_id
LEFT OUTER JOIN record_links rlk ON rec.record_id = rlk.record_id
LEFT OUTER JOIN people ppl ON ppl.person_id = rlk.person_id AND rlk.record_id = rec.record_id
LEFT OUTER JOIN files files ON files.record_id = rec.record_id
LEFT OUTER JOIN notes notes ON notes.record_id = rec.record_id
)
WHERE rec.marked_delete = 0 AND rec.is_archive = 0
GROUP BY rec.record_id
ORDER BY rec.submit_date DESC
LIMIT 0, 25
Basically as you can see there is three SUM which will count relevant rows that comes from those tables, but I seriously don't understand how total_files would be taking the same value as total_notes is there something wrong I'm doing here?
It's because rec is joined to both notes and files.
Suppose record 1 has 2 notes and 1 file, record 2 has two note and two files, and record 3 has a note but no files.
Then the table rec LEFT OUTER JOIN files ... LEFT OUTER JOIN notes will look like this:
+-----------+---------+---------+
| record_id | file_id | note_id |
+-----------+---------+---------+
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 2 | 3 | 2 |
| 2 | 3 | 3 |
| 2 | 4 | 2 |
| 2 | 4 | 3 |
| 3 | NULL | 4 |
+-----------+---------+---------+
Note how every file_id gets joined to every note_id (within the same record_id). Also, since you have SUM(IF(files.record_id = rec.record_id,1,0)) and the join condition is files.record_id = rec.record_id, you are actually counting COUNT(files)*COUNT(notes) per record_id.
I'd recommend you instead COUNT(DISTINCT files.id) and COUNT(DISTINCT records.id). The column in the COUNT would be your primary key on files/notes, not files.record_id:
SELECT rec.record_id,
COUNT(DISTINCT files.id) AS total_files,
COUNT(DISTINCT notes.id) AS total_notes
FROM rec
-- note: LEFT OUTER JOIN is the same as LEFT JOIN in MySQL
LEFT JOIN files ON files.record_id=rec.record_id
LEFT JOIN notes ON notes.record_id=rec.record_id
GROUP BY record_id
+-----------+-------------+-------------+
| record_id | total_files | total_notes |
+-----------+-------------+-------------+
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 0 | 1 |
+-----------+-------------+-------------+
Of course, adjust to your query as necessary (add in those extra columns/joins).