4

Here's what I'm trying to accomplish:

There are four tables I have to reference to get the data I am looking for. list, part, listitem, and labelassign.

  • The list table has records representing each list
  • The listitem table references list.id and represents all of the items on each list
  • The part table holds all parts and each part has a unique id
  • The labelassign table has friendly labels (like tags) where a partId is referenced in order to label parts.

Visual:

List

id  name
--  ----
1   part1
2   part2
3   part3
4   part4

Listitem

id  partId  listId
--  ----    ------
1   10      1
2   11      1
3   12      1
4   13      2
5   14      2

Part

id  name
--  ----
1   part1
2   part2
3   part3
4   part4
10  part10
11  part11
12  part12
13  part13
14  part14

Labelassign

id  label          partId
--  -----          ----
1   StandardParts  1
2   StandardParts  2
3   SmallParts     3
4   LargeParts     4
5   HugeParts      5
6   MediumParts    10
7   MediumParts    11
8   MediumParts    12
9   SmallParts     13
10  MediumParts    14

To get all lists with a particular label:

SELECT list.name
FROM list
INNER JOIN part ON list.name = part.name
INNER JOIN labelassign ON part.id = labelassign.partId AND labelassign.label LIKE '%StandardParts%'
# Using LIKE for '%StandardParts%' because there are variations in the real data but I want them all

If the list.name records do not have the StandardParts label, they should be excluded from results.

The second part is more tricky and where I have trouble.

Each list record has a number of listitem records that represent the items on that list. All list records which have the StandardParts label should have at least one listitem record with a SmallParts label, but not all do and the ones that don't are what I want to find.

listitem records are known as belonging to a particular list by their listitem.listId field. So, for each list I want to check if any of its listitems have SmallParts as a label (by way of checking the matching part, and that part's label as shown above), and if none of those listitems do I want the parent list record kept in the result set. Otherwise, the list.name should be excluded from the results.

Based on the initial answer from @Eric Brandt, this is what I have so far:

SELECT DISTINCT
    l.id,
    l.num
FROM
    list AS l
INNER JOIN
    part AS p1
        ON b.num = p1.num
# Without this up here I didn't get the filtration, lists without the StandardParts label were included in results
INNER JOIN
    labelassign AS la
        ON p1.id = la.partId
        AND la.label LIKE '%StandardParts%'
INNER JOIN
    listitem AS li
        ON l.id = li.listId
INNER JOIN
    part AS p
        ON li.partId = p.id
WHERE
    EXISTS
        (
            SELECT 1
            FROM
                labelassign AS la1
            WHERE
                la1.partId = p.id AND
                la1.label LIKE '%StandardParts%'
        )
    AND NOT EXISTS
        (
            SELECT 1
            FROM
                labelassign AS la2
            WHERE
                la2.partId = p.id AND
                la2.label LIKE '%SmallParts%'
        );

The above still returns list records which have listitem records which have the proper label of SmallParts. Again, the goal is to filter those out because I needn't fix those. I'm looking for all list records which don't have a listitem record with a label of SmallParts.

Expected Results

id  name
--  ----
1   part1

Only list.id 1 should be returned because it has a label of StandardParts and none of its listitems have a label of small parts. list.id 2 does have a label of StandardParts, but one of its listitems has a label of SmallParts so it should be excluded.

This question touches upon my issue but is mostly Java oriented and the answers do not answer my question.

This question again brushes by my issue but is really a different problem, that of a non-existent value in another table rather than exclusion based on the presence of a value.

I'm happy to provide more information if I left something out.

Dusty Vargas
  • 863
  • 6
  • 17
  • When I first looked at this, I thought that the relationship from `list` to `part` was many-to-may, with `listItem` as a bridge table. Now, though, I see that there are `partId`s in the `list` table, and I don't understand the table relationships at all. I think this really needs identified match and no-match sample data, along with expected results in order to be clear. – Eric Brandt Dec 27 '18 at 03:40
  • Ok, I've edited the question to hopefully clarify the desired results and what I think should be the way to get it. The tables are much more complex than what I've shown here but I am including the relevant parts and so I removed the list.partId field and reworked my inital query to use list.name instead of list.partId to get the matching part's label. list.name should match part.name which should have a part.id matching labelassign.partId where I get the label for that list from. – Dusty Vargas Dec 27 '18 at 18:05
  • Your sample data seems very irrelevant with only one list with related data in labelassign and you have not included any expected result – Joakim Danielson Dec 27 '18 at 18:24
  • 1
    You say that all lists that has the label `StandardParts` should have the label `SmallParts` and all lists without the `SmallParts` should be included but what about lists without both `StandardParts` and `SmallParts`? Should they also be included? – Joakim Danielson Dec 27 '18 at 18:31
  • I see, I have contradictory sample data. I will edit that. – Dusty Vargas Dec 27 '18 at 18:38
  • @Joakim Danielson Ok, I've corrected the sample data and posted expected results. – Dusty Vargas Dec 27 '18 at 18:54
  • Both list 1 and 2 has label `SmallParts` and none has `StandardParts` so either both or none of them should be included, see my question above. Anyway your expected results doesn't match your sample data. – Joakim Danielson Dec 27 '18 at 19:08
  • list 1 and 2 have a label of `StandardParts`, their list**items** have different labels. list 1's list items all have the label `MediumParts` and one of list 2's list items has a label of `SmallParts`, so list 2 should be excluded while list 1 should be included. – Dusty Vargas Dec 27 '18 at 19:23
  • Yes, I misread somewhat so you are right about SmallParts but neither list 1 or to has a label StandardParts (that label exist for partId 1 and 2 but neither of those are present in listitem) – Joakim Danielson Dec 27 '18 at 20:39
  • And how is labelId relevant here, it's part of your query but it is never explained? – Joakim Danielson Dec 27 '18 at 20:43
  • Both lists and listitems utilize labels for independent reasons, a record does not need to be in listitems to be able to have a label, the table that uses listitems is the part table, and `list` and `listitem` reference the part table, so get labels indirectly. Second, you make a good point about labelId, it is used more widely in my real dataset and I do need to filter on it but here it isn't very relevant. I will remove it. – Dusty Vargas Dec 27 '18 at 21:03
  • The table that uses _labelassign_ is the part table, sorry for the typo in above comment – Dusty Vargas Dec 27 '18 at 21:16
  • “list and listitem reference the part table”, you mean the `list` table reference the `part` table via the `listitem` table because I see no direct link? – Joakim Danielson Dec 28 '18 at 12:16
  • `list.name` matches `part.name`, and `part.id` (for the record with the same `part.name`) matches `labelassign.partId` which is how a list record is associated with a label. A `list` record can be associated with a `part` record, and a `listitem` record can also be associated with a `part` record. The `part` records are what get labels assigned in the `labelassign` table. – Dusty Vargas Dec 28 '18 at 17:01

2 Answers2

0

You want a « LEFT JOIN » on items having « SmallParts », with a WHERE clause that filters out matching rows. You can use « SELECT DISTINCT » to avoid duplicates. Finally, I suspect that you do not need to JOIN on table « part », I removed it.

SELECT DISTINCT 
    list.id, 
    list.name
FROM 
    list
    INNER JOIN listitem ON list.id = listitem.listId
    INNER JOIN labelassign l1 ON listitem.partId = l1.partId AND l1.labelId =  AND l1.label LIKE '%StandardParts%'
    LEFT JOIN labelassign l2 ON l2.partId = listitem.partId AND l2.label LIKE '%SmallParts%';
WHERE l2.partId IS NULL
GMB
  • 216,147
  • 25
  • 84
  • 135
  • This isn't filtering out list records without a part label like '%StandardParts%' and also is returning list records that have a listitem record tied to a part with a 'SmallParts' label. I don't think my inner join usage is filtering correctly. – Dusty Vargas Dec 26 '18 at 22:32
  • @DustyVargas this should work already... I simplified the query (unnecessary JOIN on « part »). However I see a glitch in your test data, where most « partId »s from table listitem do not exist in tables « part » and « labelassign » – GMB Dec 26 '18 at 23:07
  • That's correct, I am not posting complete data (in the sense that I won't include the entire table, it's very large) but I've added the referenced parts for clarity'. I am still getting extra records though. I feel like the second INNER JOIN should keep me from getting list records where the label isn't like '%StandardParts%' but that isn't the case. I have a ton more. – Dusty Vargas Dec 26 '18 at 23:26
  • Maybe I'm misunderstanding the reason for the last statement but labelassign.partId is never null – Dusty Vargas Dec 26 '18 at 23:28
0

I would try an EXISTS clause to find list.ids that have a StandardParts label, and then a NOT EXISTS clause to limit those results to the lists where no parts have the SmallParts label.

SELECT 
  l.id, 
  l.name
FROM 
  list AS l
INNER JOIN 
  listitem AS li
    ON l.id = li.listId
INNER JOIN 
  part AS p
    ON li.partId = p.id
WHERE 
  EXISTS
    (
      SELECT 1
      FROM 
        labelAssign AS la1 
      WHERE 
        la1.partId = p.id AND
        la1.label LIKE '%StandardParts%'
    )
  AND NOT EXISTS
    (
      SELECT 1
      FROM 
        labelAssign AS la1 
      WHERE 
        la1.partId = p.id AND 
        la1.label LIKE '%SmallParts%'
    );
Dusty Vargas
  • 863
  • 6
  • 17
Eric Brandt
  • 7,886
  • 3
  • 18
  • 35
  • I believe you're missing an AND after la1.labelId = 20 (I won't edit because I'm new to mysql and unsure if I'm correct). Same as the other answer though, I get results for lists who's label is not 'StandardParts' and lists which have listitems whose label is 'Small Parts'. – Dusty Vargas Dec 26 '18 at 22:52
  • Also, can you explain what SELECT 1 is doing in this case? – Dusty Vargas Dec 26 '18 at 22:52
  • In a correlated sub-query (which is what you call the things in the `[NOT] EXISTS` clauses, you're only checking to see if any record meets the criteria. Since you're not returning any results, you just need to `SELECT` _something_ and `1` is lightweight. As to not getting the expected results, if time allows, I'll take another pass with the updated sample data you've posted. – Eric Brandt Dec 26 '18 at 23:30