Can't figure out a simple SQL query - sql

Might be very simple, but I've been digging fow a few days now... I just can't figure out how to make this SQL query in Access...
In reference to the tables below, i'm looking for the query that can extract all the ITEMS for a specific Shop (ie 1:Alpha) from a specific GROUP (ie 1:Tools), that are NOT in the report for 2014... in this case ITEMS.IDs 6, 8, 9 and 10!
Tables:
Years
ID | Year
-----------------------------------------------
1 | 2014
2 | 2015
Shops
ID | ShopName
-----------------------------------------------
1 | Alpha
2 | Bravo
Items
ID | StockNbr | Description | GroupID
-----------------------------------------------
1 | 00-1200 | Ratchet 1/4 | 1
2 | 00-1201 | Ratchet 1/2 | 1
3 | 00-1300 | Screwdriver Philips No1 | 1
4 | 01-5544 | Banana | 2
5 | 00-4457 | Apple | 2
6 | 21-8887 | Hammer | 1
7 | 21-6585 | Drill | 1
8 | 21-4499 | Multimeter | 1
9 | 21-5687 | Digital Caliper | 1
10 | 22-7319 | File Set | 1
...
Groups
ID | GroupName
-----------------------------------------------
1 | Tools
2 | Fruits
REPORTS
ID | YearID | ShopID | ItemID
-----------------------------------------------
1 | 1 | 1 | 1
2 | 1 | 1 | 2
3 | 1 | 1 | 3
4 | 1 | 1 | 4
5 | 1 | 1 | 7
6 | 1 | 2 | 5
7 | 1 | 2 | 8
8 | 1 | 2 | 10
I've tried this, but then I realize it doesn't take the shops into consideration, it'll list all items that are not listed in reports, so if reports has an item for shop 2, it won't list it either...
SELECT Items.ID, Items.StockNbr, Items.Description, Items.GroupID, Reports.YearID, Reports.ShopID
FROM Reports
RIGHT JOIN Items ON Reports.ItemID = Items.ID
WHERE (((Items.GroupID)=1) AND ((Reports.UnitID) Is Null))
ORDER BY Items.StockNbr;
Thank you!

I think you're looking for an anti-join. There are several ways to do this. Here's one using not in.
select i.* from items i
where i.GroupId = 1
and i.ID NOT IN (
select ItemID from reports r
where r.ShopID = 1
and r.YearID = 2014
)

If the table Reports does not reference Items.ID then there is no available relationship ShopID or YearID
select *
from items
left join reports on items.id = reports.itemid
where reports.itemid IS NULL

Related

Generate 'average' column from sub query and ROW_NUMBER window function in SQL SELECT

I have the following SQL Server tables (with sample data):
Questionnaire
id | coachNodeId | youngPersonNodeId | complete
1 | 12 | 678 | 1
2 | 12 | 52 | 1
3 | 30 | 99 | 1
4 | 12 | 678 | 1
5 | 12 | 678 | 1
6 | 30 | 99 | 1
7 | 12 | 52 | 1
8 | 30 | 102 | 1
Answer
id | questionnaireId | score
1 | 1 | 1
2 | 2 | 3
3 | 2 | 2
4 | 2 | 5
5 | 3 | 5
6 | 4 | 5
7 | 4 | 3
8 | 5 | 4
9 | 6 | 1
10 | 6 | 3
11 | 7 | 5
12 | 8 | 5
ContentNode
id | text
12 | Zak
30 | Phil
52 | Jane
99 | Ali
102 | Ed
678 | Chris
I have the following T-SQL query:
SELECT
Questionnaire.id AS questionnaireId,
coachNodeId AS coachNodeId,
coachNode.[text] AS coachName,
youngPersonNodeId AS youngPersonNodeId,
youngPersonNode.[text] AS youngPersonName,
ROW_NUMBER() OVER (PARTITION BY Questionnaire.coachNodeId, Questionnaire.youngPersonNodeId ORDER BY Questionnaire.id) AS questionnaireNumber,
score = (SELECT AVG(score) FROM Answer WHERE Answer.questionnaireId = Questionnaire.id)
FROM
Questionnaire
LEFT JOIN
ContentNode AS coachNode ON Questionnaire.coachNodeId = coachNode.id
LEFT JOIN
ContentNode AS youngPersonNode ON Questionnaire.youngPersonNodeId = youngPersonNode.id
WHERE
(complete = 1)
ORDER BY
coachNodeId, youngPersonNodeId
This query outputs the following example data:
questionnaireId | coachNodeId | coachName | youngPersonNodeId | youngPersonName | questionnaireNumber | score
1 | 12 | Zak | 678 | Chris | 1 | 1
2 | 12 | Zak | 52 | Jane | 1 | 3
3 | 30 | Phil | 99 | Ali | 1 | 5
4 | 12 | Zak | 678 | Chris | 2 | 4
5 | 12 | Zak | 678 | Chris | 3 | 4
6 | 30 | Phil | 99 | Ali | 2 | 2
7 | 12 | Zak | 52 | Jane | 2 | 5
8 | 30 | Phil | 102 | Ed | 1 | 5
To explain what's happening here… There are various coaches whose job is to undertake questionnaires with various young people, and log the scores. A coach might, at a later date, repeat the questionnaire with the same young person several times, hoping that they get a better score. The ultimate goal of what I'm trying to achieve is that the managers of the coaches want to see how well the coaches are performing, so they'd like to see whether the scores for the questionnaires tend to go up or not. The window function represents a way to establish how many times the questionnaire has been undertaken by the same coach/young person combo.
I need to be able to determine the average score based on the questionnaire number. So for example, the coach 'Zak' logged scores of '1' and '3' for his first questionnaires (where questionnaireNumber = 1) so the average would be 2. For his second questionnaires (where questionnaireNumber = 2) the scores were '3' and '5' so the average would be 4. So in analysing this data we know that over time Zak's questionnaire scores have improved from an average of '2' the first time to an average of '4' the second time.
I feel like the query needs to be grouped by the coachNodeId and questionnaireNumber values so it would output something like this (I've ommitted the questionnaireId, youngPersonNodeId, youngPersonName and score columns as they aren't crucial for the output — they're only used to derive the averageScore — and wouldn't be useful the way the results are grouped):
coachNodeId | coachName | questionnaireNumber | averageScore
12 | Zak | 1 | 2 (calculation: (1 + 3) / 2)
12 | Zak | 2 | 4 (calculation: (3 + 5) / 2)
12 | Zak | 3 | 4 (only one value: 4)
30 | Phil | 1 | 5 (calculation: (5 + 5) / 2)
30 | Phil | 2 | 2 (only one value: 2)
Could anyone suggest how I can modify my query to output the average scores based on the score from the sub-query and the ROW_NUMBER window function? I've hit the limits of my SQL skills!
Many thanks.
It is a bit hard to tell without sample data, but I think you are describing aggregation:
SELECT q.coachNodeId AS coachNodeId,
cn.[text] AS coachName,
q.youngPersonNodeId AS youngPersonNodeId,
ypn.[text] AS youngPersonName,
AVG(score)
FROM Questionnaire q JOIN
ContentNode cn
ON q.coachNodeId = cn.id JOIN
ContentNode ypn
ON q.youngPersonNodeId = ypn.id LEFT JOIN
Answer a
ON a.questionnaireId = q.id
WHERE complete = 1
GROUP BY q.coachNodeID, cn.[text] AS coachName,
q.youngPersonNodeId, ypn.[text]

Check if relation exists and return true or false

I have 3 tables, Category Step and CategoryStep, where CategoryStep relates the two other tables together. I want to return all categories with a true/false column whether or not the relation exists in CategoryStep based on a StepID.
The schema for the tables is simple,
Category:
CategoryID | CategoryName
Step:
StepID | StepName
CategoryStep:
CategoryStepID | CategoryID | StepID
When trying to get results based on StepID, I only get the relations that exist, and not ones that don't.
SELECT [CategoryID], [Category], CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep ON Category.CategoryID = CategoryStep.CategoryID
INNER JOIN Step ON CategoryStep.StepID = Step.StepID
WHERE Step.StepID = 2
Step Table:
|StepID | StepName
|-------|---------
| 1 | StepOne
| 2 | StepTwo
| 3 | StepThree
Category Table:
| CategoryID | CategoryName
|------------|-------------
| 1 | Holidays
| 2 | States
| 3 | Cities
| 4 | Animals
| 5 | Food
CategoryStep Table
| CategoryStepID | CategoryID | StepID
|----------------|------------|-------
| 1 | 1 | 1
| 2 | 1 | 2 <--
| 3 | 2 | 1
| 4 | 2 | 3
| 5 | 3 | 2 <--
| 6 | 4 | 1
| 7 | 4 | 2 <--
| 8 | 4 | 3
| 9 | 5 | 1
| 10 | 5 | 3
So, if I was looking for StepID = 2 the result table I am looking for is:
| CategoryID | Category | Related
|------------|----------|--------
| 1 | Holidays | 1
| 2 | States | 0
| 3 | Cities | 1
| 4 | Animals | 1
| 5 | Food | 0
Try replacing the INNER JOIN with a LEFT JOIN.
Update:
The fatal flaw with your original attempt was the WHERE clause. You were performing the correct LEFT JOIN, but the WHERE clause was filtering off category records which did not match. In the query below, I moved the check for step ID into the join condition, where it belongs.
SELECT [CategoryID], [Category],
CAST(CASE WHEN [CategoryStep].[CategoryStep] IS NULL THEN 0 ELSE 1 END AS BIT) AS related
FROM Category
LEFT JOIN CategoryStep
ON Category.CategoryID = CategoryStep.CategoryID AND
CategoryStep.StepCodeID = 2
LEFT JOIN Step
ON CategoryStep.StepID = Step.StepID

CREATE VIEW with multiple tables - must show 0 values

I have three tables:
// priorities // statuses // projects
+----+--------+ +----+-------------+ +----+------+--------+----------+
| ID | NAME | | ID | STATUS NAME | | ID | NAME | STATUS | PRIORITY |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 1 | Normal | | 1 | Pending | | 1 | a | 1 | 3 |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 2 | High | | 2 | In Progress | | 2 | b | 1 | 1 |
+----+--------+ +----+-------------+ +----+------+--------+----------+
| 3 | Urgent | | 3 | c | 2 | 1 |
+----+--------+ +----+------+--------+----------+
| 4 | d | 1 | 2 |
+----+------+--------+----------+
I need to create a view that shows how many projects hold a status of 1 and a priority of 1, how many hold a status of 1 and a priority of 2, how many hold a status of 1 and a priority of 3, and so on.
This should go through each status, then each priority, then count the projects that apply to the criteria.
The view should hold values something like this:
// VIEW (stats)
+--------+----------+-------+
| STATUS | PRIORITY | COUNT |
+--------+----------+-------+
| 1 | 1 | 1 |
+--------+----------+-------+
| 1 | 2 | 1 |
+--------+----------+-------+
| 1 | 3 | 1 |
+--------+----------+-------+
| 2 | 1 | 1 |
+--------+----------+-------+
| 2 | 2 | 0 |
+--------+----------+-------+
| 2 | 3 | 0 |
+--------+----------+-------+
This view is so that I can call, for example, how many projects have a status of 1 and a priority of 3, the answer given the data above should be 1.
Using the below select statement I've been able to produce a similar result but it does not explicitly show that 0 projects have a status of 2 and a priority of 3. I need this 0 value to be accessible the same way as any of the others with a COUNT >= 1.
// my current select statement
CREATE VIEW stats
AS
SELECT P.STATUS, P.PRIORITY, COUNT(*) AS hits
FROM projects P
GROUP BY P.STATUS, P.PRIORITY
// does not show rows where COUNT = 0
How could I create a VIEW that holds all of the priorities' ids, all of the statuses' ids, and 0 values for COUNT?
You need to generate all the rows and then get the count for each one. Here is a query that should work:
SELECT s.status, p.priority, COUNT(pr.status) AS hits
FROM (SELECT DISTINCT status FROM projects) s CROSS JOIN
(SELECT DISTINCT priority FROM projects) p LEFT JOIN
project pr
ON pr.status = s.status and pr.priority = p.priority
GROUP BY s.status, p.priority;

Oracle query using 3 tables, sql

I have a problem with a query for Oracle with this scenario:
Table People
ID | Name
1 | juan
2 | pedro
3 | luis
Table Properties
ID | nombre_inmueble | FK to Table People
1 | house | 1
2 | garden | 1
3 | terrace | 1
4 | moto | 2
5 | jet | 2
Table Accessories
ID | accessories | FK Table Properties
1 | windows | 1
2 | doors | 1
3 | scale | 2
4 | plants | 3
5 | motor | 4
What I want is only the people who have Properties and that have ALL Accessories, in this case the output would be
1 | juan
What would be the query?
Your query will look like this:
SELECT *
FROM People P
WHERE EXISTS(
SELECT 1
FROM Properties T
WHERE T.PEOPLE= P.ID
)
AND NOT EXISTS(
SELECT 1
FROM Properties T
WHERE T.PEOPLE= P.ID
AND NOT EXISTS(
SELECT 1
FROM Accessories A
WHERE A.Properties = T.ID
)
);

Selecting several max() from a table

I will first say that the table structure is (unfortunately) set.
My goal is to select several max() from a query. Lets say I have the following tables
jobReferenceTable jobList
jobID | jobName | jobDepartment | listID | jobID |
_______|__________|_______________| _______|_________|
1 | dishes | cleaning | 1 | 1 |
2 |vacumming | cleaning | 2 | 5 |
3 | mopping | cleaning | 3 | 2 |
4 |countMoney| admin | 4 | 4 |
5 | hirePpl | admin | 5 | 1 |
6 | 2 |
7 | 3 |
8 | 3 |
9 | 1 |
10 | 5 |
Somehow, I would like to have a query that selects the jobID's from cleaning, and then shows the most recent jobList ID's for each job. I started a query below, and below that are what I'm hoping to get as results
query
SELECT jrt.jobName, jrt.jobDepartment
FROM jobReferenceTable
WHERE jobDepartment = 'cleaning'
JOIN jobList jl ON jr.jobID = jl.jobID
results
jobName | jobDepartment | listID |
________|_______________|________|
1 | cleaning | 9 |
2 | cleaning | 6 |
3 | cleaning | 8 |
Try this;
SELECT jrt.jobName, jrt.jobDepartment, MAX(jl.listID)
FROM jobReferenceTable AS jrt INNER JOIN jobList AS jl ON jrt.jobID = jl.jobID
WHERE jrt.jobDepartment = 'cleaning'
GROUP BY jrt.jobName, jrt.jobDepartment
So far as I can see, you need only the one MAX() - the listID.
MAX() is an aggregate function, meaning that the rest of your result set must then be 'grouped'.