Select From Multi-Value List with Where Clause - sql

I'm trying to select all Projects which have Employees who are AtWork.
Projects:
ProjName | EmpOnProj
--------------------------
Alpha | 1, 2, 3
Beta | 1, 3
Employees:
EmpID | EmpName | AtWork
-------------------------------------
1 | John | TRUE
2 | Mark | FALSE
3 | Mary | TRUE
I need to output all projects which could currently be worked on; ie, I need to show Beta because the employees working on Beta are at work.
Currently I cannot say "ALL EMPLOYEES MUST BE AT WORK" only the following:
SELECT ProjName FROM Projects INNER JOIN
Employees ON EmpOnProj.Value = EmpID
WHERE AtWork = true
GROUP BY ProjName
which returns both, as it sees one correct employee and displays the project.

I think I solved this one. Basically I'm saying 'show all projects except those where somebody is NOT at work'
http://sqlfiddle.com/#!3/36c48/2
SELECT DISTINCT
p_global.ProjName
FROM
Projects AS p_global
WHERE
p_global.ProjName NOT IN
(SELECT DISTINCT
p1.ProjName
FROM
Projects p1 INNER JOIN Employees AS e ON p1.EmpOnProj = e.EmpID
WHERE
e.AtWork = 0)
There may be a simpler solution but this works (or it looks like it anyway) :)
Edit: Modified to remove GROUP BYs as suggested in comments.

If can't really answer your question but this stuff can guide you through simplification and can help you solve you question. Currently your table is not in good format. Instead of having comma separated values, why not do it in rows instead? Like this,
Projects:
ProjName | EmpOnProj
--------------------------
Alpha | 1
Alpha | 2
Alpha | 3
Beta | 1
Beta | 3
In this way you can easily join both tables. Example
SELECT a.EmpID, a.EmpName,
iif (ISNULL(b.EmpOnProj), 'False', 'True') AtWork
FROM Employees a
LEFT JOIN Projects b
ON a.EmpID = b.EmpOnProj
WHERE b.ProjName = 'Beta'

Let's assume you move beyond the EmpOnProj column, which, as implemented in your example, violates first normal form, and replace it with an associative entity called ProjEmp, whose primary key is (ProjName, EmpID)
SELECT p.ProjName FROM Projects p
LEFT OUTER JOIN
(SELECT eop.ProjName FROM ProjEmp eop
INNER JOIN JOIN Employees e
ON e.EmpId = eop.EmpId
AND e.AtWork = FALSE
) AS empNotHere
ON empNotHere.ProjName = p.ProjName
WHERE empNotHere.ProjName IS NULL
;

I'm assuming I misunderstood your question since your SQL seems to contradict the schema you provided. But if your table is formatted the way you listed, you have to jump through hoops. Here's a solution that involves making a UDF, to avoid extremely complicated SQL.
Add this to a module:
Function WhoIsAtWork() As String
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("Select * from Employees where AtWork = true Order by EmpID")
Do While Not rs.EOF
WhoIsAtWork = WhoIsAtWork & rs!EmpID & ", "
rs.MoveNext
Loop
If Len(WhoIsAtWork) <> 0 Then
WhoIsAtWork = Left(WhoIsAtWork, Len(WhoIsAtWork) - 2)
End If
rs.Close
Set rs = Nothing
End Function
Then your SQL would be this:
SELECT ProjName
FROM Projects
WHERE Projects.EmpOnProj=WhoIsAtWork();

Related

SQL Query to Show When Golfer Not Attached to an Event/Year

I am working on a school assignment that has downright stumped me for days. The task is to, using a view (VAvailableGolfers), populate a list box with Golfers who are not tied to a given event/year selected from a combo box. Here is the data in the tables:
The expected output on the form, then, would be:
2015 shows Goldstein available
2016 shows no one available
2017 shows both Goldstein and Everett available
so, in other words, where there isn't a record in TGolferEventYears for a golfer for a particular year
I have tried left joins, full outer joins, exists, not in, not exists, etc and I cannot seem to nail down the SQL to make it happen.
Here is the VB Form and the SQL backing it. I cannot figure out what to code in the view:
"SELECT intGolferID, strLastName FROM vAvailableGolfers WHERE intEventYearID = " & cboEvents.SelectedValue.ToString
Here is the view, which I know isn't giving correct output:
select tg.intGolferID, strLastName, intEventYearID
from TGolferEventYears TGEY, TGolfers TG
Where tgey.intGolferID = tg.intGolferID
and intEventYearID not IN
(select intEventYearID
from TEventYears
where intEventYearID not in
(select intEventYearID
from TGolferEventYears))
Appreciate any help
I usually approach this type of question by using a cross join to generate all possibly combination and then a left join/where to filter out the ones that already exist:
select g.intGolferID, g.strLastName, ey.intEventYearID
from TEventYears ey cross join
TGolfers g left join
TGolferEventYears gey
on gey.intGolferID = g.intGolferID and
gey.intEventYearID = ey.intEventYearID
where gey.intGolferID is null;
Try this query:
SELECT tg.intGolferID, strLastName, tey.intEventYearID, tey.intEventYear
FROM TGolfers tg, TEventYears tey
WHERE tg.intGolferID NOT IN (
SELECT DISTINCT tgey.intGolferID
FROM TGolferEventYears tgey
WHERE tgey.intEventYearID = tey.intEventYearID
)
Explanation
Since you are trying to get combinations of data that is not in TGolferEventYears, you cannot use it in your outer-most SELECT as any of its columns would be NULL. Therefore, you need to SELECT FROM the tables that are the sources of that data, and going through each joined record, filter out the combinations that are in TGolferEventYears.
Main query
Select the data you need:
SELECT tg.intGolferID, strLastName, tey.intEventYearID, tey.intEventYear
...from TGolfers, cross join with TEventYears:
FROM TGolfers tg, TEventYears tey
...where the golfer ID does not exist in the following collection:
WHERE tg.intGolferID NOT IN ( ... )
Subquery
Select unique golfer IDs:
SELECT DISTINCT tgey.intGolferID
...from TGolferEventYears:
FROM TGolferEventYears tgey
...where the year is the current year of the outer query:
WHERE tgey.intEventYearID = tey.intEventYearID
Result
+-------------+-------------+----------------+--------------+
| intGolferID | strLastName | intEventYearID | intEventYear |
+-------------+-------------+----------------+--------------+
| 1 | Goldstein | 1 | 2015 |
| 1 | Goldstein | 3 | 2017 |
| 2 | Everett | 3 | 2017 |
+-------------+-------------+----------------+--------------+

SQL Query - Join the same column twice

I'm having trouble to achieve the result I want trying join a column from a table twice.
My first table is "dbo.Sessions", which contains basic session info like the user ID, the project ID, login/logout date and times, etc.
I need to join to that the user names and project names. However, these are found in another table, but in the same column (dbo.tblObjects.Name).
Example:
+------+---------------+
| k_Id | Name |
+------+---------------+
| 1 | AgentName1 |
| 2 | ProjectNameX |
| 3 | ProjectNameY |
| 4 | AgentName2 |
| 5 | ProjectNameZ |
| 6 | AgentName3 |
+------+---------------+
To try and achieve my goal, I used two "LEFT JOIN". However, I get duplicate results in both. I'll either get both columns to display either the project names or the user names (depending on which "LEFT JOIN" is first).
This is what I have at this point:
SELECT SysDB.dbo.Sessions.*, SysDB.dbo.tblObjects.Name AS AgentName, SysDB.dbo.tblObjects.Name AS ProjectName
FROM SysDB.dbo.Sessions
LEFT JOIN SysDB.dbo.tblObjects ON SysDB.dbo.Sessions.userId = SysDB.dbo.Objects.k_Id
LEFT JOIN SysDB.dbo.tblObjects ON SysDB.dbo.Sessions.projectId = SysDB.dbo.Objects.k_Id
WHERE (SysDB.dbo.Sessions.loginDate BETWEEN 'm/d/yyyy' AND 'm/d/yyyy')
Note: SysDB is the name of the database that I identify every time because this query is to be run externally. I also don't use "USE SysDB" before my selection because it doesn't work from the VBA macro this will run from.
Note 2: I have found a thread on this site that addresses this exact issue, but I can't understand what is being done, and it dates back in 2012. Something about aliases. The solution offers to add "ls." and "lt." before the table names, but that doesn't work for me. Says the table doesn't exist.
SQL Query Join Same Column Twice
Note 3: I have tried many different things, such as:
LEFT JOIN SysDB.dbo.tblObjects AS AgentName ON SysDB.dbo.Sessions.userId = SysDB.dbo.tblObjects.k_Id
LEFT JOIN SysDB.dbo.tblObjects AS ProjectName ON SysDB.dbo.Sessions.projectId = SysDB.dbo.tblObjects.k_Id
Any insights would be greatly appreciated. Thanks!
You may find it much easier to see what you are doing by giving each table an alias (session, agent, project below)
SELECT session.*, agent.Name AS AgentName, project.Name AS ProjectName
FROM SysDB.dbo.Sessions session
LEFT JOIN SysDB.dbo.tblObjects agent
ON session.userId = agent.k_Id
LEFT JOIN SysDB.dbo.tblObjects project
ON project.projectId = session.k_Id
WHERE (session.loginDate BETWEEN 'm/d/yyyy' AND 'm/d/yyyy')

Left Outer Joining Tables with Multiple Property Keys

Long time lurker, first time poster.
I have two tables 'case' and 'case_char'.
case
case_id | status | date
1 | closed | 01/01/2014
2 | open | 02/01/2014
case_char
case_id | property_key | value
1 | email | xx#xx.com
1 | phone | 1234567
2 | email | x2#xx.com
2 | phone | 987654
2 | issue | Unhappy
Say I want to return the 'issue' for each case. Not all cases have issues so I will need to do a left outer join. Unfortunately it is not working for me, it is returning only cases with the 'issue' characteristic. I need it to return all cases regardless of whether the 'issue' characteristic exists for a case in the case_char table.
Below is an example of the way I have written the code ( bearing in mind I am using an Oracle DB).
Could any of you whizzes help a brother out?
SELECT c.case_id, char.value
FROM case c, case_char char
WHERE c.case_id = char.case_id (+)
AND char.property_key = 'issue'
Just add a Join(+) to your property key as below:
SELECT C.CASE_ID, CHAR.VALUE
FROM CASE C, CASE_CHAR CHAR
WHERE
C.CASE_ID = CHAR.CASE_ID (+)
AND
CHAR.PROPERTY_KEY(+) = 'ISSUE';
^
|
You shoud use an explicit join, and put the property_key in the ON clause.
SELECT c.case_id, char.value
FROM case AS c
LEFT JOIN case_char AS char ON c.case_id = char.case_id AND char.property_key = 'issue'
I'm not very familiar with the syntax for implicit out joins. My guess is you need to put (+) after char.property_key = 'issue' to keep it from filtering out the null rows.
I assume you want 1 row per case regardless of whether or not it has an issue, but all the issues for each case?
If that's what you want, something close to this should work (I'm a SQL Server guy, so I'm not totally sure that this will work with Oracle).
SELECT
c.case_id
,char.value
FROM case AS c
LEFT JOIN case_char AS char
ON
c.case_id = char.case_id
AND char.property_key = 'issue'
Basically, we've moved the filter logic to the join condition, otherwise the WHERE clause will filter out anything that's not an 'issue'.
Does that answer your question?

SQL Views - Modify Returned Result

I'm a little stuck here. I'm trying to modify a returned View based on a condition. I'm fairly green on SQL and am having a bit of difficultly with the returned result. Heres a partial component of the view I wrote:
WITH A AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY fkidContract,fkidTemplateItem ORDER BY bStdActive DESC, dtdateplanned ASC) AS RANK,
tblWorkItems.fkidContract AS ContractNo,
....
FROM tblWorkItems
WHERE fkidTemplateItem IN
(2895,2905,2915,2907,2908,
2909,3047,2930,2923,2969,
2968,2919,2935,2936,2927,
2970,2979)
AND ...
)
SELECT * FROM A WHERE RANK = 1
The return result is similar to the following:
ContractNo| ItemNumber | Planned | Complete
001 | 100 | 01/01/1900 | 02/01/1900
001 | 101 | 03/04/1900 | 02/01/1901
001 | 102 | 03/06/1901 | 02/08/1900
002 | 100 | 01/03/1911 | 02/08/1913
This gives me the results I expect, but due a nightmare crystal report I need to alter this view slightly. I want to take this returned result set and modify an existing column with a value pulled from the same table and the same Contract relationship, something like the following:
UPDATE A
SET A.Completed = ( SELECT R.Completed
FROM myTable R
INNER JOIN A
ON A.ContractNo = R.ContractNo
WHERE A.ItemNumber = 100 AND R.ItemNumber = 101
)
What I'm trying to do is modify the "Completed Date" of one task and make it the complete date of another task if they both share the same ContractNo field value.
I'm not sure about the ItemNumber relationships between A and R (perhaps it was just for testing...), but it seems like you don't really want to UPDATE anything, but you want to use a different value under some circumstances. So, maybe you just want to change the non-cte part of your query to something like:
SELECT A.ContractNo, A.ItemNumber, A.Planned,
COALESCE(R.Completed,A.Completed) as Completed
FROM A
LEFT OUTER JOIN myTable R
ON A.ContractNo = R.ContractNo
AND A.ItemNumber = 100 AND R.ItemNumber = 101 -- I'm not sure about this part
WHERE A.Rank = 1
So it turns out that actually reading the vendor documentation helps :)
SELECT
column1,
column2 =
case
when date > 1999 then 'some value'
when date < 1999 then 'other value'
else 'back to the future'
end
FROM ....
For reference, the total query did a triple inner join over ~5 million records and this case statement was surprisingly performant.
I suggest that this gets closed as a duplicate.

Access join on first record

I have two tables in an Access database, tblProducts and tblProductGroups.
I am trying to run a query that joins both of these tables, and brings back a single record for each product. The problem is that the current design allows for a product to be listed in the tblProductGroups table more than 1 - i.e. a product can be a member of more than one group (i didnt design this!)
The query is this:
select tblProducts.intID, tblProducts.strTitle, tblProductGroups.intGroup
from tblProducts
inner join tblProductGroups on tblProducts.intID = tblProductGroups.intProduct
where tblProductGroups.intGroup = 56
and tblProducts.blnActive
order by tblProducts.intSort asc, tblProducts.curPrice asc
At the moment this returns results such as:
intID | strTitle | intGroup
1 | Product 1 | 1
1 | Product 1 | 2
2 | Product 2 | 1
2 | Product 2 | 2
Whereas I only want the join to be based on the first matching record, so that would return:
intID | strTitle | intGroup
1 | Product 1 | 1
2 | Product 2 | 1
Is this possible in Access?
Thanks in advance
Al
This option runs a subquery to find the minimum intGoup for each tblProducts.intID.
SELECT tblProducts.intID
, tblProducts.strTitle
, (SELECT TOP 1 intGroup
FROM tblProductGroups
WHERE intProduct=tblProducts.intID
ORDER BY intGroup ASC) AS intGroup
FROM tblProducts
WHERE tblProducts.blnActive
ORDER BY tblProducts.intSort ASC, tblProducts.curPrice ASC
This works for me. Maybe this helps someone:
SELECT
a.Lagerort_ID,
FIRST(a.Regal) AS frstRegal,
FIRST(a.Fachboden) AS frstFachboden,
FIRST(a.xOffset) AS frstxOffset,
FIRST(a.yOffset) AS frstyOffset,
FIRST(a.xSize) AS frstxSize,
FIRST(a.ySize) AS frstySize,
FIRST(a.Platzgr) AS frstyPlatzgr,
FIRST(b.Artikel_ID) AS frstArtikel_ID,
FIRST(b.Menge) AS frstMenge,
FIRST(c.Breite) AS frstBreite,
FIRST(c.Tiefe) AS frstTiefe,
FIRST(a.Fachboden_ID) AS frstFachboden_ID,
FIRST(b.BewegungsDatum) AS frstBewegungsDatum,
FIRST(b.ErzeugungsDatum) AS frstErzeugungsDatum
FROM ((Lagerort AS a)
LEFT JOIN LO_zu_ART AS b ON a.Lagerort_ID = b.Lagerort_ID)
LEFT JOIN Regal AS c ON a.Regal = c.Regal
GROUP BY a.Lagerort_ID
ORDER BY FIRST(a.Regal), FIRST(a.Fachboden), FIRST(a.xOffset), FIRST(a.yOffset);
I have non unique entries for Lagerort_ID on the table LO_zu_ART. My goal was to only use the first found entry from LO_zu_ART to match into Lagerort.
The trick is to use FIRST() an any column but the grouped one. This may also work with MIN() or MAX(), but I have not tested it.
Also make sure to call the Fields with the "AS" statement different than the original field. I used frstFIELDNAME. This is important, otherwise I got errors.
Create a new query, qryFirstGroupPerProduct:
SELECT intProduct, Min(intGroup) AS lowest_group
FROM tblProductGroups
GROUP BY intProduct;
Then JOIN qryFirstGroupPerProduct (instead of tblProductsGroups) to tblProducts.
Or you could do it as a subquery instead of a separate saved query, if you prefer.
It's not very optimal, but if you're bringing in a few thousand records this will work:
Create a query that gets the max of tblProducts.intID from one table and call it qry_Temp.
Create another query and join qry_temp to the table you are trying to join against, and you should get your results.