Need Help Thinking Through Semi-Complex Query on Two Tables - mysql

I have two tables, and I want to create a SELECT pulling a single record from one table based on multiple records from another table.
Perhaps it will be clearer if I just give a sort of example.
Table: dates. Fields: dosID, personID, name, dateIn
1 | 10 | john smith | 2013-09-05
2 | 10 | john smith | 2013-01-25
Table: cards. Fields: cardID, personID, cardColor, cardDate
1 | 10 | red | 2013-09-05
2 | 10 | orange | 2013-09-05
3 | 10 | black | 2013-09-05
4 | 10 | green | 2013-01-25
5 | 10 | orange | 2013-01-25
So what I want is to only select a record from the dates table if a person did not receive a "red" card. The closest I have come is something like:
SELECT name, dateIn FROM dates, cards
WHERE dates.personID = cards.personID AND cardColor != 'red' AND dateIn = cardDate;
But for this query the 2013-09-05 date-of-service would still be pulled out because of the "orange" and "black" cards given on the same day.
I have tried searching but I am not even sure how to properly describe this issue and so my Google-fu has failed me. Any help or suggestions would be very much appreciated.

The easiest to understand version would filter using NOT EXISTS:
SELECT name, dateIn
FROM dates
WHERE NOT EXISTS(
SELECT *
FROM cards
WHERE cards.personID = dates.personID
AND cards.cardDate = dates.dateIn
AND cards.cardColor = 'red'
)
See it on sqlfiddle.

I'm a bit confused, you're getting the non-red 2013-09-05 rows back.
http://sqlfiddle.com/#!2/89260
I tried it using an inner join (as you wrote it), and an outer join. Same results.
EDIT:
Sorry, misunderstood your post.
eggyal's answer looks like a winner to me.

Related

Leaderboard position SQL

He there,
I've found a lot of questions like this one, but I cannot seem to work it out. So I'm gonna ask it and hope that someone can help me with this specific problem (or direct me to the question if it is a duplicate).
I have the following three tables in my database:
tbl_achievements
id | points
tbl_achievements_finished
id | user_id | achievement_id
tbl_users
id
What I would like to do, is select a specific user or list of users and see the positions/ranks. The idea is that all the achievements the user finishes, yield points. The perfect outcome would be something like:
rank | user_id | achievement_points
1 | 6 | 65
2 | 3 | 45
3 | 2 | 15
I can't seem to wrap my head around it. I hope there is someone out there that can help me out with (an idea for) a query.
Thanks a lot! If anything is unclear, please let me know. :)
Something like this should do it:
SELECT af.user_id, SUM(points) as 'achievement_points'
FROM tbl_users as u
LEFT JOIN tbl_achievements_finished AS af ON u.id = af.user_id
LEFT JOIN tbl_achievements AS a ON a.id = af.achievement_id
GROUP BY u.id
ORDER BY SUM(points) DESC
This is the sample output of the above query:
| user_id | achievement_points |
|---------|--------------------|
| 3 | 40 |
| 1 | 25 |
| 2 | 15 |
See it here: http://sqlfiddle.com/#!9/69775/9/0
I didn't manage to keep the rank in place because of the ordering, but you can always have it when you process the query.

Can't figure out a proper MySQL query

I have a table with the following structure:
id | workerID | materialID | date | materialGathered
Different workers contribute different amounts of different material per day. A single worker can only contribute once a day, but not necessarily every day.
What I need to do is to figure out which of them was the most productive and which of them was the least productive, while it is supposed to be measured as AVG() material gathered per day.
I honestly have no idea how to do that, so I'll appreciate any help.
EDIT1:
Some sample data
1 | 1 | 2013-01-20 | 25
2 | 1 | 2013-01-21 | 15
3 | 1 | 2013-01-22 | 17
4 | 1 | 2013-01-25 | 28
5 | 2 | 2013-01-20 | 23
6 | 2 | 2013-01-21 | 21
7 | 3 | 2013-01-22 | 17
8 | 3 | 2013-01-24 | 15
9 | 3 | 2013-01-25 | 19
Doesn't really matter how the output looks, to be honest. Maybe a simple table like that:
workerID | avgMaterialGatheredPerDay
And I didn't really attempt anything because I literally have no idea, haha.
EDIT2:
Any time period that is in the table (from earliest to latest date in the table) is considered.
Material doesn't matter at the moment. Only the arbitrary units in the materialGathered column matter.
As in your comments you say that we look at each worker and consider their avarage daily working skill, rather than checking which worked most in a given time, the answer is rather easy: Group by workerid to get a result record per worker, use AVG to get their avarage amount:
select workerid, avg(materialgathered) as avg_gathered
from work
group by workerid;
Now to the best and worst workers. These can be more than two. So you cannot just take the first or last record, but need to know the maximum and the minimum avg_gathered.
select max(avg_gathered) as max_avg_gathered, min(avg_gathered) as min_avg_gathered
from
(
select avg(materialgathered) as avg_gathered
from work
group by workerid
);
Now join the two queries to get all workers that worked the avarage minimum or maximum:
select work.*
from
(
select workerid, avg(materialgathered) as avg_gathered
from work
group by workerid
) as worker
inner join
(
select max(avg_gathered) as max_avg_gathered, min(avg_gathered) as min_avg_gathered
from
(
select avg(materialgathered) as avg_gathered
from work
group by workerid
)
) as worked on worker.avg_gathered in (worked.max_avg_gathered, worked.min_avg_gathered)
order by worker.avg_gathered;
There are other ways to do this. For example with HAVING avg(materialgathered) IN (select min(avg_gathered)...) OR avg(materialgathered) IN (select max(avg_gathered)...) instead of a join. The join is very effective though, because you need just one select for both min and max.

Select rows which do not have the first column matching any other rows

I need to select the rows that do not have the first column matching. For example, from the data below;
Person | Room
---------------------------------------
ben | 1
jake | 3
jake | 1
steven | 2
james | 1
james | 2
james | 3
The query would only return these rows:
Person | Room
---------------------------------------
ben | 1
jake | 3
steven | 2
james | 1
It doesn't matter what value the room column returns.
And it needs to be able to work with increasing room numbers and different names.
I've had no look searching for a answer and can't figure out how to do it, however it might be my current mindset and it might be really easy to do.
SELECT Person, MIN(Room) AS Room
FROM YourTable
GROUP BY Person
And if you can guarantee that the ONLY_FULL_GROUP_BY setting will always be turned off, the following is also possible in MySql:
SELECT Person, Room
FROM YourTable
GROUP BY Person
But I'd use the MIN() function just to be safe...

Pulling information from two tables using LIKE MySQL

I'm working in PHP and MySQL
I feel like punching a wall. I have no idea how to do this and it's making me angry. I've done plenty of things like it before and this isn't working. UGH. Please help!!!!
I'm doing a search. Customer will type in something like "88 brake pads". I want to return all the parts that say "brake" all the parts that say "pads" and all the parts that have the year 88. My issue here is, the years and the parts are in different tables.
Example:
Years Table
ID | part_id | year |
============================
1 | 15 | 1945 |
2 | 15 | 1946 |
3 | 16 | 1984 |
4 | 18 | 1987 |
Parts Table
ID | part_name |
=====================
15 | brakes |
16 | hose |
17 | crank |
18 | muffler |
This SQL Works, when pulling out of parts table
SELECT DISTINCT * FROM hj_parts WHERE part_name LIKE "%brakes"
I will get the output
ID | part_name |
=====================
15 | brakes |
So what I need is if someone enters "brakes 87" I want to get back all the info for part 15 (brakes) and part 18 (because it's from 1987).
I am so totally lost.
I've done something like this to pull out all the parts from a certain year, and I thought I could just tweak it, and it's not helping.
$query = "SELECT hj_parts.* FROM hj_parts JOIN(SELECT DISTINCT part_id FROM hj_years WHERE (year BETWEEN $starter AND $ender) OR (year='all')) hj_years ON hj_parts.id = hj_years.part_id";
Here's how I'd do it. First create a query that brings everything together, and we'll put it in a view for convenience sake
create view AllParts as
SELECT Parts.*,Years.* from Parts inner join Years on Years.part_id=Parts.ID
Then you do a search like this
Select * from AllParts where part_name like '%brake' or year=1987
If your database is big this might take a while. It's not necessarily a perfect solution, but it can work for you.
If you are interested in doing a keyword search, perhaps a nosql database would be better

MySQL - COUNT before INSERT in one query

Hey all, I am looking for a way to query my database table only once in order to add an item and also to check what last item count was so that i can use the next number.
strSQL = "SELECT * FROM productr"
After that code above, i add a few product values to a record like so:
ID | Product | Price | Description | Qty | DateSold | gcCode
--------------------------------------------------------------------------
5 | The Name 1 | 5.22 | Description 1 | 2 | 09/15/10 | na
6 | The Name 2 | 15.55 | Description 2 | 1 | 09/15/10 | 05648755
7 | The Name 3 | 1.10 | Description 3 | 1 | 09/15/10 | na
8 | The Name 4 | 0.24 | Description 4 | 21 | 09/15/10 | 658140
i need to count how many times it sees gcCode <> 'na' so that i can add a 1 so it will be unique. Currently i do not know how to do this without opening another database inside this one and doing something like this:
strSQL2 = "SELECT COUNT(gcCode) as gcCount FROM productr WHERE gcCode <> 'na'
But like i said above, i do not want to have to open another database query just to get a count.
Any help would be great! Thanks! :o)
There's no need to do everything in one query. If you're using InnoDB as a storage engine, you could wrap your COUNT query and your INSERT command in a single transaction to guarantee atomicity.
In addition, you should probably use NULL instead of na for fields with unknown or missing values.
They're two queries; one is a subset of the other which means getting what you want in a single query will be a hack I don't recommend:
SELECT p.*,
(SELECT COUNT(*)
FROM PRODUCTR
WHERE gccode != 'na') AS gcCount
FROM PRODUCTR p
This will return all the rows, as it did previously. But it will include an additional column, repeating the gcCount value for every row returned. It works, but it's redundant data...