NOT LIKE search on Link Table - sql

I have a Model table with an ID and A Text Column:
ID | Description
=======================
1 | Model A
2 | Model B
3 | Model C
I have an Items table with an ID and lots of other columns. These two tables are linked via an intermediary table call ItemModels with the following data:
ID | ItemID | ModelID
==================================
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
4 | 2 | 2
5 | 2 | 3
6 | 3 | 2
I want to search using the standard "Contains, Does Not Contain, Starts With, Ends With" methods.
If I do a "Contains", "Starts With" or "Ends With" search using the LIKE operator this works fine and I always get the correct results, however I have a problem when using the NOT LIKE operator:
If I want to return all items where the model description does not contain "C" (case insensitive) I thought simply of doing the following:
SELECT ItemID FROM ItemModels INNER JOIN Model ON ItemModels.ModelID = Model.ID WHERE Description NOT LIKE '%C%'
I want this query to return Items 1 and 3 as neither of them have any models that contain 'C' however this query will also return item 2 as it will hit the record with ItemModel.ID = 3 and say "That does not contain C so we want to return that!" which of course is undesired behaviour.
So my question is:
How can I do a NOT LIKE search that encompasses all records in a Link table?
ps. I hope I have made this clear as it took me hours to track this issue down and work out why it was happening. And even more hours trying to work out how the hell to fix it!

You don't want any of the items to match your condition. Think in terms of aggregation and a having clause:
SELECT im.ItemID
FROM im.ItemModels im INNER JOIN
Model m
ON im.ModelID = m.ID
GROUP BY im.ItemId
HAVING SUM(CASE WHEN Description LIKE '%C%' THEN 1 ELSE 0 END) = 0;
This query counts the number of models that match the item. The = 0 says that there are none. I like this approach because it is quite flexible. Using AND and OR you can put together complicated conditions, such as like '%a%' and '%b%' but not like '%c%'.

Related

SQL Select based on condition from another table

I have two tables composing with the following definitions:
A
ID | Name | Description
1 | Bag | It's a bag
2 | Leather | Leather makes the bag
3 | Website | It's a website
4 | Images | Just normal images
5 | Text | Some descriptive text
B
parentID | childID | Quantity
1 | 2 | 5
3 | 4 | 2
3 | 5 | 1
I'm trying to find all items in table A which are of level 0 (meaning they have no parent) and then use that to try and find the items in the following levels.
So far I've gotten to this, which I thought would work, but clearly not...
SELECT *
FROM A
WHERE A.ID in (
SELECT B.parentID
FROM B
WHERE (B.childID is NULL)
);
If anyone could shed some light on this I'd really appreciate it. Also if there's some must-reads for SQL querying please let me know, I'd be glad to learn more about this, as it's something I struggle with.
Thanks in advance.
EDIT:
I've edited the data to hopefully illustrate a little better what I'm trying to achieve. There's several items, some are parents and some are children.
From table B you can tell that to make 1 bag, you need 5 leather (bag is parent of leather with quantity 5). Or that the website is composed of 2 Image and 1 Text (website is parent to both Image quantity 2 and Text quantity 1).
I want to query for the bag and website only, meaning the items in table A which have no parents in table B.
Found it myself, basically what I was looking for was this:
SELECT *
FROM A
WHERE A.ID not in (
SELECT B.childID
FROM B
);
This selects all items from A which have no parents (meaning they are not children of any other item).

Converting a number value in a field to a word

I have a SQL query that I'm building and need to convert a number to a word.
The field is titled Type and the values are 1 or 2. I need to convert the 1 to display as Problem and the 2 to display as Resolution.
How would i go about doing this. I built this as an expression in SQL data tools, but we are going in a different direction and need to add it to the query instead and display the report another way.
Thanks!
I'd recommend you have a second table with your ID/Name combination as a lookup and do a JOIN.
That way, as new types come in, you only have to change the name, and not the code.
Although, the syntax would be
CASE WHEN Type = 1 THEN 'Problem'
WHEN Type = 2 THEN 'Resolution' END
You've essentially built the first portion of a normalized database, you've just haven't completed the second portion. The numbers: 1 and 2, can be foreign keys to a second data table that links to the second table's unique auto-incremented ID field:
+-------+ +----+-------------+
| Type | | ID | Description |
+-------+ +----+-------------+
| 1 | | 1 | Problem |
| 2 | | 2 | Resolution |
| 2 | +----+-------------+
| 1 |
+-------+
Which this schema, you can then query the data like such:
SELECT `table2`.`description`
FROM `table2`
INNER JOIN `table1`
ON `table1`.`type` = `table2`.`id`
WHERE `table1`.`type` = 1
Fiddle: Live Demo
What this does is it allows you to add more IDs and Descriptions to your second data table without having to rewrite a bunch of code.
Try below option, it will work with 2 types only (as in question):
Declare #Type int = 1
select
case #Type when 1 then 'Problem'
else 'Resolution'
end as Result
set #Type = 2
select
case #Type when 1 then 'Problem'
else 'Resolution'
end as Result

SQL - Representing SUM's after using CASE to transform STR -> INT

I am sorry for what may be a long post in advance.
Background:
I am using Rational Team Concert (RTC) which stores work item data in conjunction with Jazz Reporting Service to create reports. Using the Report Builder tool, it allows you to write your own queries to pull data as a table, and has its own interface to represent the table as a graph.
There is not much options for of graphing; the chart type defaults as a count, unless you specify it to show a sum. In order to graph by sum, the data must be a number rather than a string. By default, the Report Builder assumes all variables in the SELECT statement are strings.
The data which I will be using are a bunch of work items. Each work item is associated to a team (A, B) and has a work estimation number (count1, count2).
Item # | Team | Work |
------------------------
123 | A | count1 |
------------------------
124 | A | count2 |
------------------------
125 | B | count2 |
------------------------
....
Problem:
Since the work estimation is entered as a Tag, the first step was to use a CATCH WHEN block when using SELECT to transform count1 -> 1, and count2 -> 2 (the string tag to an actual number which can be summed). This resulted in a table with numbers 1 and 2 in place of the typed tag (good so far).
Item # | Team | Work |
------------------------
123 | A | 1 |
------------------------
124 | A | 2 |
------------------------
125 | B | 2 |
------------------------
....
The problem is that I am trying to graph by sum, which means getting the tool to identify the variables in the SELECT statement as numbers, except for some reason any variable I declare in a SELECT statement is always viewed as a string (The tool has a table of the current columns i.e. variables in the SELECT, along with that the tool identifies as its variable type).
Attempted Solutions:
The first query I did was to return a table of each work item with its team name and work estimate
SELECT T1.NAME,
(CASE WHEN T1.TAGs='count1' THEN 1 ELSE 2 END) AS WORK
FROM RIDW.VW_REQUEST T1
WHERE T1.PROJECT_ID = 73
Which resulted in
Team | Work |
----------------
A | 1 |
----------------
A | 2 |
----------------
B | 2 |
----------------
....
but the tool still sees the numbers as strings. I then tried explicitly casting the CASE to an integer, but resulted in the same issue
...
CAST(CASE WHEN T1.TAGs='count1' THEN 1 ELSE 2 END AS Integer) AS WORK
...
Which again the tool still represents as a string.
Current Goal:
As I cannot confirm if the tool has an underlying problem, compatibility issues with queries, etc. What I believe will work now would be to return a table with 2 rows: The sum of the work for each team
|Sum of 1's and 2's |
-----------------------------
Team A | SUM(1) + SUM(2) |
-----------------------------
Team B | SUM(1) + SUM(2) |
-----------------------------
What I am having trouble with is using sub queries to use SUM to sum the data. When I try
SUM(CASE WHEN ... END) AS TIME2 I get an error that "Column modifiers AVG and SUM apply only to number attributes". This has me thinking that I need to have a sub query which returns the column after the CASE, and then SUM that, but I am sailing into uncharted waters and can't seem to get the syntax to work.
I understand that a post like this would be better off on the product help forum. I have tried asking around but cannot get any help. The solution I am proposing of returning the 2 row/column table should bypass any issues the software may have, but I need help sub-querying the SUM when using a case.
I appreciate your time and help!
EDIT 1:
Below is the full query code which preforms the CASE correctly, but still causes with the interpreted type by the tool:
SELECT
T1.Name,
CAST(CASE WHEN T1.TAGS='|release_points_1|' THEN 1 ELSE (CASE WHEN T1.TAGS='|release_points_2|' THEN 2 ELSE 0 END) END AS Integer) AS TAG,
FROM RIDW.VW_REQUEST T1
WHERE T1.PROJECT_ID = 73
AND
(T1.ISSOFTDELETED = 0) AND
(T1.REQUEST_ID <> -1 AND T1.REQUEST_ID IS NOT NULL
This small adjustment to your current query should work:
SELECT
T1.Name,
SUM(CAST(CASE WHEN T1.TAGS='|release_points_1|' THEN 1 ELSE (CASE WHEN T1.TAGS='|release_points_2|' THEN 2 ELSE 0 END) END AS Integer)) AS TAG,
FROM RIDW.VW_REQUEST T1
WHERE T1.PROJECT_ID = 73
AND
(T1.ISSOFTDELETED = 0) AND
(T1.REQUEST_ID <> -1 AND T1.REQUEST_ID IS NOT NULL
GROUP BY T1.Name

Access SQL Max-Function

I have a question concerning MS Access queries involving these tables:
tblMIDProcessMain ={ Process_ID,Process_Title,...}
tblMIDProcessVersion = { ProcessVersion_ID, ProcessVersion_FK_Process, ProcessVersion_VersionNo, ProcessVersion_FK_Status, ...}
tblMIDProcessVersionStatus = { ProcessVersionStatus_ID,ProcessVersionStatus_Value }
The tables store different versions of a process description. The "ProcessVersion_VersionNo" field contains an integer providing the version number. Now I would like to get for each process the highest version number thus the current version. If I do the following it kind of works:
SELECT tblMIDProcessMain.Process_Titel
, Max(tblMIDProcessVersion.ProcessVersion_VersionNo) AS CurrentVersion
FROM tblMIDProcessMain
INNER JOIN tblMIDProcessVersion
ON tblMIDProcessMain.Process_ID = tblMIDProcessVersion.ProcessVersion_FK_Process
GROUP BY tblMIDProcessMain.Process_Titel;
The query returns a recordset with each existing process_title and the respective max number of the version field. But as soon as I add other fields like "ProcessVersion_FK_Status" in the Select statement the query stops working.
Any help would be appreciated. Thanks in advance.
Jon
Edit:
To clarify things a little I added a simplified example
Parent-Table:
Process_ID | Process_Title
----------------------------------
1 | "MyProcess"
2 | "YourProcess"
Child-Table:
Version_ID | Version_FK_ProcessID | Version_No | Version_Status
---------------------------------------------------------------------------
1 | 1 | 1 | "New"
2 | 2 | 1 | "Discarded"
3 | 2 | 2 | "Reviewed"
4 | 2 | 3 | "Released"
Intended Result:
Title | Max_Version_No | Status
--------------------------------------------------------
MyProcess | 1 | "New"
YourProcess | 3 | "Released"
Given the example tables you updated your post with, this should work:
select process_title as Title
, max_version.max_version_no
, c.version_status as status
from (parenttable p
inner join (select max(version_id) as max_version_no, version_fk_process_id from childtable group by version_fk_process_id) max_version
on p.process_id = max_version.version_fk_process_id)
inner join childtable c
on max_version.max_version_no = c.version_id and max_version.version_fk_process_id = c.version_fk_process_id
I assume you are adding the new field to the 'Group By" clause? If not, then you either must include in the 'Group By', or you must use one of the operators like "Max" or "First" etc.

How can I do access control via an SQL table?

I'm trying to create an access control system.
Here's a stripped down example of what the table I'm trying to control access to looks like:
things table:
id group_id name
1 1 thing 1
2 1 thing 2
3 1 thing 3
4 1 thing 4
5 2 thing 5
And the access control table looks like this:
access table:
user_id type object_id access
1 group 1 50
1 thing 1 10
1 thing 2 100
Access can be granted either by specifying the id of the 'thing' directly, or granted for an entire group of things by specifying a group id. In the above example, user 1 has been granted an access level of 50 to group 1, which should apply unless there are any other rules granting more specific access to an individual thing.
I need a query that returns a list of things (ids only is okay) along with the access level for a specific user. So using the example above I'd want something like this for user id 1:
desired result:
thing_id access
1 10
2 100
3 50 (things 3 and 4 have no specific access rule,
4 50 so this '50' is from the group rule)
5 (thing 5 has no rules at all, so although I
still want it in the output, there's no access
level for it)
The closest I can come up with is this:
SELECT *
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
But that returns multiple rows, when I only want one for each row in the 'things' table. I'm not sure how to get down to a single row for each 'thing', or how to prioritise 'thing' rules over 'group' rules.
If it helps, the database I'm using is PostgreSQL.
Please feel free to leave a comment if there's any information I've missed out.
Thanks in advance!
I don't know the Postgres SQL dialect, but maybe something like:
select thing.*, coalesce ( ( select access
from access
where userid = 1
and type = 'thing'
and object_id = thing.id
),
( select access
from access
where userid = 1
and type = 'group'
and object_id = thing.group_id
)
)
from things
Incidentally, I don't like the design. I would prefer the access table to be split into two:
thing_access (user_id, thing_id, access)
group_access (user_id, group_id, access)
My query then becomes:
select thing.*, coalesce ( ( select access
from thing_access
where userid = 1
and thing_id = thing.id
),
( select access
from group_access
where userid = 1
and group_id = thing.group_id
)
)
from things
I prefer this because foreign keys can now be used in the access tables.
I just read a paper last night on this. It has some ideas on how to do this. If you can't use the link on the title try using Google Scholar on Limiting Disclosure in Hippocratic Databases.
While there are several good answers, the most efficient would probably be something like this:
SELECT things.id, things.group_id, things.name, max(access)
FROM things
LEFT JOIN access ON
user_id = 1
AND (
(access.type = 'group' AND access.object_id = things.group_id)
OR (access.type = 'thing' AND access.object_id = things.id)
)
group by things.id, things.group_id, things.name
Which simply uses summarization added to you query to get what you're looking for.
Tony:
Not a bad solution, I like it, seems to work. Here's your query after minor tweaking:
SELECT
things.*,
coalesce (
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'thing'
AND object_id = things.id
),
( SELECT access
FROM access
WHERE user_id = 1
AND type = 'group'
AND object_id = things.group_id
)
) AS access
FROM things;
And the results look correct:
id | group_id | name | access
----+----------+---------+--------
1 | 1 | thing 1 | 10
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
I do completely take the point about it not being an ideal schema. However, I am stuck with it to some extent.
Josef:
Your solution is very similar to the stuff I was playing with, and my instincts (such as they are) tell me that it should be possible to do it that way. Unfortunately it doesn't produce completely correct results:
id | group_id | name | max
----+----------+---------+-----
1 | 1 | thing 1 | 50
2 | 1 | thing 2 | 100
3 | 1 | thing 3 | 50
4 | 1 | thing 4 | 50
5 | 2 | thing 5 |
The access level for 'thing 1' has taken the higher 'group' access value, rather than the more specific 'thing' access value of 10, which is what I'm after. I don't think there's a way to fix that within a GROUP BY, but if anyone has any suggestions I'm more than happy to be proven incorrect on that point.