Select Query Joined on Two Fields? - sql

I've got a few tables in an access database:
ID | LocationName
1 | Location1
2 | Location2
ID | LocationID | Date | NumProductsDelivered
1 | 1 | 12/10 | 3
2 | 1 | 01/11 | 2
3 | 1 | 02/11 | 2
4 | 2 | 11/10 | 1
5 | 2 | 12/10 | 1
ID | LocationID | Date | NumEmployees | EmployeeType
1 | 1 | 12/10 | 10 | 1 (=Permanent)
2 | 1 | 12/10 | 3 | 2 (=Temporary)
3 | 1 | 12/10 | 1 | 3 (=Support)
4 | 2 | 10/10 | 1 | 1
5 | 2 | 11/10 | 2 | 1
6 | 2 | 11/10 | 1 | 2
7 | 2 | 11/10 | 1 | 3
8 | 2 | 12/10 | 2 | 1
9 | 2 | 12/10 | 1 | 3
What I want to do is pass in the LocationID as a parameter and get back something like the following table. So, if I pass in 2 as my LocationID, I should get:
Date | NumProductsDelivered | NumPermanentEmployees | NumSupportEmployees
10/10 | | 1 |
11/10 | 1 | 2 | 1
12/10 | 1 | 2 | 1
It seems like this should be a pretty simple query. I really don't even need the first table except as a way to fill in the combo box on the form from which the user chooses which location they want a report for. Unfortunately, everything I've done has resulted in me getting a lot more data than I should be getting. My confusion is in how to set up the join (presumably that's what I'm looking for here) given that I want both the date and locationID to be the same for each row in the result set.
Any help would be much appreciated.
Thanks.
EDIT:
Ok - the answer below didn't quite work, but it did set me on the right track and I was able to use the following query:
SELECT t1.Date, t2.NumProductsDelivered,
(SELECT t1a.NumEmployees
FROM table3 t1a
WHERE t1a.EmployeeType=1 AND t1a.LocationID=t1.LocationID AND t1a.Date= t1.Date)
AS "PermEmps",
(SELECT t1b.NumEmployees
FROM table3 t1b
WHERE t1b.EmployeeType=3 AND t1b.LocationID=t1.LocationID AND t1b.Date=t1.Date)
AS "SupportEmps"
FROM table3 AS t1 LEFT JOIN table2 AS t2 ON (t2.Date=t1.Date)
AND (t2.LocationID=t1.LocationID)
WHERE t1.LocationID=2
GROUP BY t1.Date, t1.LocationID, t2.NumProductsDelivered;
This is getting me the results I was looking for. However, in a case where the location has a break between products being delivered, I don't see the correct results. It seems that the recordset stops as soon as there's an empty row and then never picks back up again. So, where I might expect to see this:
Date | NumProductsDelivered | NumPermanentEmployees | NumSupportEmployees
10/10 | | 1 |
11/10 | 1 | 2 | 1
12/10 | 1 | 2 | 1
01/10 | 2 | | 1
06/10 | 1 | |
I only see this:
Date | NumProductsDelivered | NumPermanentEmployees | NumSupportEmployees
10/10 | | 1 |
11/10 | 1 | 2 | 1
12/10 | 1 | 2 | 1
01/10 | 2 | | 1

I think this would work:
DECLARE #LocationId int
SET #LocationId=2
SELECT L2.LocationId, L2.Date, COUNT(DISTINCT NumProductsDelivered) as NumProductsDelivered,
SUM(case when L2.EmployeeType =1 then NumEmployees else 0 end) as NumPermanentEmployees,
SUM(case when L2.EmployeeType =3 then NumEmployees else 0 end) as NumSupportEmployees
FROM L1
RIGHT JOIN L2
ON L1.LocationID=L2.LocationID
AND L1.Date=L2.Date
WHERE L2.LocationId=#LocationId
GROUP BY L2.LocationId, L2.Date

Something like this should work:
[deleted original]
Try this instead (untested):
select t3.date, t2.numproductsdelivered,
(select sum(t3.numemployees)
from table3 t3a
where t3a.locationid = t3.locationid and t3a.date = t3.date and t3a.employeetype = 1
) as numpermanentemployees,
(select sum(t3.numemployees)
from table3 t3b
where t3b.locationid = t3.locationid and t3b.date = t3.date and t3b.employeetype = 3
) as numsupportemployees
from table3 as t3
left join table2 as t2 on t2.locationid = t3.locationid and t2.date = t3.date
where t3.locationid = 2
group by t3.date, t2.numproductsdelivered
If you didn't mind having separate rows for each employee type it could be simplified:
select t3.date, t2.numproductsdelivered, t3.employeetype, sum(t3.numemployees) as numemployees
from table3 as t3
left join table2 as t2 on t2.locationid = t3.locationid and t2.date = t3.date
where t3.locationid = 2 and t3.employeetype in (1, 3)
group by t3.date, t2.numproductsdelivered, t3.employeetype
Edit: Try this query:
SELECT t1.Date
FROM table3 AS t1
WHERE t1.LocationID=2
GROUP BY t1.Date
...and see if you get all the dates.
Then add the left join:
SELECT t1.Date, t2.NumProductsDelivered
FROM table3 AS t1 LEFT JOIN table2 AS t2 ON (t2.Date=t1.Date)
AND (t2.LocationID=t1.LocationID)
WHERE t1.LocationID=2
GROUP BY t1.Date, t1.LocationID, t2.NumProductsDelivered;
If it's doing a left INNER join, then it will remove rows from t1 that don't have a matching row in t2. Try explicitly setting a left OUTER join and see if that works. The RDBMS I've used the most defaults to outer, but maybe yours (Access) defaults to inner.
So I am thinking the following will work (add "OUTER" and remove "t1.LocationId"):
SELECT t1.Date, t2.NumProductsDelivered,
(SELECT t1a.NumEmployees
FROM table3 t1a
WHERE t1a.EmployeeType=1 AND t1a.LocationID=t1.LocationID AND t1a.Date= t1.Date)
AS "PermEmps",
(SELECT t1b.NumEmployees
FROM table3 t1b
WHERE t1b.EmployeeType=3 AND t1b.LocationID=t1.LocationID AND t1b.Date=t1.Date)
AS "SupportEmps"
FROM table3 AS t1 LEFT OUTER JOIN table2 AS t2 ON (t2.Date=t1.Date)
AND (t2.LocationID=t1.LocationID)
WHERE t1.LocationID=2
GROUP BY t1.Date, t2.NumProductsDelivered;

Related

Oracle SQL - How Can I Join Two Tables, One to Many?

I am looking to do a full outer join in order to get the following results. Mainly I am joining table 1 to table 2.
However in table 1, anything that has a 0 in Column A but has the same available value in Key 1 column (ABC100 Key 1 value in table 1), only use that record (record 1 in table 1), and ignore 0 record (record 2 in table 1)
When joined to table 2, specifically for ABC100, I am expecting to see output lines 1 and 2 in the expected table results.
Any help or ideas going about this?
Example:
Table 1
| Key 1 | Column A |
| ABC100 | 100 |
| ABC100 | 0 |
| ABC300 | 200 |
| ABC400 | 300 |
Table 2
| Key 2 | Column C |
| ABC100 | 100 |
| ABC200 | 50 |
| ABC300 | 200 |
Expected results:
| Key 1 | Column A | Key 2 | Column B | NVL(A,0) - NVL(B,0)
| ABC100 | 100 | ABC100 | 100 | 0
| ABC100 | NULL | NULL | NULL | NULL
| NULL | NULL | ABC200 | 50 | -50
| ABC300 | 200 | ABC300 | 200 | 0
| ABC400 | 300 | NULL | NULL | 300
Your result set suggests that you want something like this:
SELECT t1.key1,
(CASE WHEN t1.a <> 0 THEN t1.a END) as a,
(CASE WHEN t1.a <> 0 THEN t2.key2 END) as key2,
(CASE WHEN t1.a <> 0 THEN t2.c END) as ,
(CASE WHEN t1.a <> 0 THEN COALESCE(t1.A, 0) - COALESCE(t2.B, 0) END) as diff
FROM t1 FULL JOIN
t2
ON t1.Key1 = t2.Key2;
Your description suggests that you want:
SELECT t1.key1, t1.a, t2.key2, t2.c,
COALESCE(t1.A, 0) - COALESCE(t2.B, 0) as diff
FROM (SELECT t1.*
FROM
WHERE t1.A <> 0 OR
NOT EXISTS (SELECT 1 FROM t1 tt1 WHERE tt1.key1 = t1.key1 AND tt1.key1 <> 0)
) t1 FULL JOIN
t2
ON t1.Key1 = t2.Key2;
You could use:
SELECT t1.*, t2.*, NVL(t1.A,0) - NVL(t2.B,0)
FROM tab1 t1
FULL JOIN tab2 t2
ON t1.Key1=t2.Key2

SQL Not IN query is taking time with similar table

i have 2 tables suppose table_1 & table_2
table_1 has 56 columns and 1.2 million records
my query is like
table_1 like
RollNumber | Subject | G | Part | Status
------------------------------------------------
1 | 1 | 1 | 1 | 1
1 | 1 | 1 | 2 | 1
1 | 2 | 1 | 1 | 1
1 | 2 | 1 | 2 | 5
1 | 3 | 1 | 1 | 0
1 | 3 | 1 | 2 | 1
2 | 1 | 2 | 1 | 1
2 | 1 | 2 | 2 | 1
2 | 2 | 2 | 1 | 1
2 | 2 | 2 | 2 | 1
2 | 3 | 2 | 1 | 1
2 | 3 | 2 | 2 | 1
3 | 1 | 2 | 1 | 1
3 | 1 | 2 | 2 | 1
3 | 2 | 2 | 1 | 1
3 | 2 | 2 | 2 | 1
3 | 3 | 2 | 1 | 0
3 | 3 | 2 | 2 | 1
i want all RollNumber (group by with 2nd and third column) from table_1 where any status is 0 but don't want students who also have status = 5(or other than 1)
i have tried this
select * from table_1 as t1
inner join table_2 as t2
on t1.column2 = t2.column2 and t1.column3 = t2.column3 and t1.column4 = t2.column4
where t1.column1 not in
(select column1 from table_1 where status = 5)
This is the inner most query of my qhole query
i have also tried EXCEPT clause
Both queries take too long to execute
Starting with SQL Server 2008 you can use count() over() to count how many total rows in a given group have a certain value.
In this case you'll want to count the number of status <> 1 per group and to only select rows that belong to a group with a count of 0.
select * from (
select * ,
count(case when status <> 1 then 1 end) over(partition by RollNumber, G) c
from table_1
) t where c = 0
You can use EXISTS in place of NOT IN. This will be faster as there will be a boolean comparison instead of string comparison.
select * from table_1 as t1
inner join table_2 as t2
on t1.column1 = t2.column1 and t1.column2 = t2.column2 and t1.column3 = t2.column3
where not EXISTS
(select 1 from table_1 where status = 5 and t1.column3 = table_1.column3)
Try to use NOT EXISTS instead of NOT IN
SELECT *
FROM table_1 AS t1
INNER JOIN table_2 AS t2
ON t1.column1 = t2.column1 AND t1.column2 = t2.column2 AND t1.column3 = t2.column3
WHERE NOT EXISTS(
SELECT 1
FROM table_1
WHERE status=5 AND column3=t1.column3
)

How to mass-check for duplicates in MS-Access and log changes?

I want to mass-compare a few hundred fields in MS Access between 2 tables with identical column structures. If there are any differences between the column values, replaces the row in table1 with the new row in table2. If table2 does no longer holds a row that exists in table1, that row should be dropped from table1. All changes to table1 should be logged in tableLOGS.
Take for example:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 0 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 0 | 0 |
After running the desired SQL query, the result should be:
____table1___ _____table2____ __________tableLOGS__________
| pid | A | B | | pid | A | B | | id | pid | A | B | action |
| 1 | 0 | 0 | | 1 | 0 | 0 | | 1 | 1 | 0 | 0 | add |
| 2 | 0 | 1 | | 2 | 0 | 1 | | 2 | 2 | 0 | 0 | add |
| 3 | 2 | 0 | 1 | edit |
| 4 | 3 | 0 | 0 | delete |
I expect this would have to be broken down into 2 separate queries?
Mass-compare rows and update changes Log the changes to tableLOGS This seems like a fairly common task so perhaps MS Access has an easy way of accomplishing this? Thanks for all the help! :)
P.S. I am also open to simply deleting rows from table1 that do not match table2, and INSERT INTO table1 from table2.
This is from memory I didn't actually run this so it may need some fixing. My apologies
Start with the logging for updates
INSERT INTO TableLogs
SELECT A, B
FROM (
SELECT A, B
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL)
Then update table1 with the updated values
UPDATE table1
INNER JOIN(
SELECT *
FROM table2 t2
INNER JOIN table1 t1 ON t1.pid = t2.pid
AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL) t2
ON table1.pid = t2.pid
Log missing records in table 1
INSERT INTO tablelogs
SELECT A, B
FROM table2
INNER JOIN table1 t1 ON t1.pid = t2.pid AND (t1.A <> t2.a OR t1.B <> t2.B)
WHERE table1.A IS NOT NULL
Delete the missing rows from table1
DELETE table1
WHERE pid NOT IN
(SELECT pid FROM Table2)
You could also put a trigger on table1 to update table logs but its not really best practice.
Hope that helps, like I said I didn't run it yet.

Can't update main table with joined tables using DISTINCT - Access: This recordset is not updatable

gurus!
I'm using SQL Server linked tables in Access Forms. In MainTable I need to update and insert records, but Access won't let it, for update it says "This Recordset is not updateable". I know, it's couse DISTINCT, but it's necessary for TableType records - I need only one related name_ds from TableTypes (even first by npr) and in result just thees 7 MainTable records not 16 (without DISTINCT).
Any workarounds?
Simple structure -
MainTable: id, npr, name, type, datasource_fk.
TableDS: id, name_ds, something.
TableType: id, npr, name_type, something_type.
Data -
MainTable:
1;12;"Olie";"percentage";1
2;15;"Tol";"count";2
3;13;"Opp";"percentage";1
4;12;"Hypq";"count";3
5;14;"Gete";"count";1
6;;"Mour";"count";2
7;;"Ellt";"percentage";3
TableDS:
1;"City1";"q"
2;"City2";"a"
3;"State1";"z"
4;"State2";"x"
TableType:
1;12;"City1";"w"
2;15;"City1";"s"
3;13;"City1";"x"
4;14;"City2";"w"
5;14;"City1";"s"
6;13;"City3";"p"
7;12;"City1";"t"
8;12;"City1";"n"
9;12;"State1";"r"
10;15;"State1";"r"
SQL, result -
SELECT DISTINCT t3.npr AS npr_type, t1.npr, t1.id, t1.name, t2.name_ds, t1.datasource_fk, t1.types
FROM (MainTable AS t1 LEFT JOIN TableDS AS t2 ON t1.datasource_fk = t2.id) LEFT JOIN TableType AS t3 ON t1.npr = t3.npr;
---------------------------------------------------------------------------------------------------------------------------------------------
| npr_type | npr | id | name | name_ds | datasource_fk | types |
---------------------------------------------------------------------------------------------------------------------------------------------
| | | 6 | Mour | City2 | 2 | count |
---------------------------------------------------------------------------------------------------------------------------------------------
| | | 7 | Ellt | State1 | 3 | percentage |
---------------------------------------------------------------------------------------------------------------------------------------------
| 12 | 12 | 1 | Olie | City1 | 1 | percentage |
---------------------------------------------------------------------------------------------------------------------------------------------
| 12 | 12 | 4 | Hypq | State1 | 3 | count |
---------------------------------------------------------------------------------------------------------------------------------------------
| 13 | 13 | 3 | Opp | City1 | 1 | percentage |
---------------------------------------------------------------------------------------------------------------------------------------------
| 14 | 14 | 5 | Gete | City1 | 1 | count |
---------------------------------------------------------------------------------------------------------------------------------------------
| 15 | 15 | 2 | Tol | City2 | 2 | count |
---------------------------------------------------------------------------------------------------------------------------------------------
You are getting 16 matches on your joins because MainTable npr column matches multiple times with TableType npr column.
1;12;"Olie";"percentage";1
Matches to
7;12;"City1";"t"
8;12;"City1";"n"
9;12;"State1";"r"
1;12;"City1";"w"
Your best bet is to use a where clause for column TableType.somethingtype. You can try LEFT JOIN on TableDS and TableType using multiple columns but really, you may need to adjust your data. In other words, inactivate some rows. The following query will show you what you're up against:
SELECT t3.npr AS npr_type,
t1.npr,
t1.id,
t1.name,
t2.name_ds,
t1.datasource_fk,
t1.type,
t3.something_type
FROM #MainTable t1
LEFT JOIN #TableDS AS t2
ON t1.datasource_fk = t2.id
LEFT JOIN #TableType AS t3
ON t1.npr = t3.npr
ORDER BY t3.npr,
t1.npr,
t1.id,
t1.name,
t2.name_ds,
t1.datasource_fk,
t1.type,
t3.something_type
So, after you figure out your data. Then you may be able to do something like:
SELECT t3.npr AS npr_type,
t1.npr,
t1.id,
t1.name,
t2.name_ds,
t1.datasource_fk,
t1.type,
t3.something_type
FROM #MainTable t1
LEFT JOIN #TableDS AS t2
ON t1.datasource_fk = t2.id
LEFT JOIN #TableType AS t3
ON t1.npr = t3.npr
WHERE
(t1.npr = 12 AND t3.something_type = 'n')
OR
(t1.npr = 14 AND t3.something_type = 's')
OR
(t1.npr = 13 AND t3.something_type = 'p')
OR
(t1.npr = 15 AND t3.something_type = 's')
OR
(t1.npr IS NULL)

Is there a workaround to the Oracle Correlated Subquery Nesting Limit?

I have a situation where I'm trying to use a correlated subquery but am running into the nesting limit in Oracle. I might be missing another feature that Oracle has, so I thought I'd post this question here. Does anyone know how to rewrite the below SQL without running into this nesting limit, but also staying within the below constraints?
Constraints:
Only the SQL in the IN clause can be modified (Due to constraints beyond my control)
As shown, the filtering in the parent query needs to be applied to the aggregation subquery before the aggregation occurs.
Filter out 0 on an aggregation of colB after the parent filter is applied
The below code shows my try at this before running into the Oracle limit. Also, the Oracle version I'm on is 11.2.0.2. Any help would be appreciated. Thanks!
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN XXXX AND XXXX
AND t1.pk_id IN (
SELECT
t2.pk_id
FROM (
SELECT
t3.pk_id,
SUM(t3.amt) OVER (PARTITION BY t3.colB) amt
FROM table1 t3
WHERE t3.colA = t1.colA
) t2
WHERE
t2.amt <> 0
)
Here are some sample input/outputs of what I was looking for when running the above SQL:
Sample table1:
-----------------------------
| pk_id | colA | colB | amt |
-----------------------------
| 1 | 1 | A | 2 |
| 2 | 1 | A | -1 |
| 3 | 1 | B | 1 |
| 4 | 2 | B | 1 |
| 5 | 2 | A | -2 |
| 6 | 2 | A | 1 |
| 7 | 3 | A | 1 |
Results of SUM over t3.colB with t1.colA BETWEEN 1 And 2:
---------------
| pk_id | amt |
---------------
| 1 | 0 |
| 2 | 0 |
| 3 | 2 |
| 4 | 2 |
| 5 | 0 |
| 6 | 0 |
Results of subquery for IN clause with t1.colA BETWEEN 1 And 2:
---------
| pk_id |
---------
| 3 |
| 4 |
Result of top level query with t1.colA BETWEEN 1 And 2:
-----------------------------
| pk_id | colA | colB | amt |
-----------------------------
| 3 | 1 | B | 1 |
| 4 | 2 | B | 1 |
After working through some of the answers provided, I have a way of avoiding the nesting limit in Oracle with a simple CASE statement:
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN 1 AND 2
AND t1.pk_id IN (
SELECT
CASE
WHEN SUM(t2.amt) OVER (PARTITION BY t2.colB) <> 0 THEN t2.pk_id
ELSE NULL
END
FROM table1 t2
WHERE t2.colA = t1.colA
)
Unfortunately this surfaced the real problem. Because this is a subquery, I can only iterate through one value of the t1.colA range at a time. This appears to make it impossible execute the analytic sum within that range in the subquery. Because I can only modify the SQL within the IN clause, I don't see a solution to this problem. If anyone has any suggestions please let me know. Thanks.
If you know what the between values are and can use those in your subquery, then you can add that to your subquery instead:
SELECT
*
FROM
table1 t1
WHERE
t1.colA BETWEEN 1 AND 2
AND t1.pk_id IN (
SELECT
t2.pk_id
FROM
(
SELECT
t3.pk_id,
SUM(t3.amt) OVER (PARTITION BY t3.colB) amt
FROM table1 t3
WHERE t3.colA BETWEEN 1 AND 2
) t2
WHERE
t2.amt <> 0
)
SQL Fiddle Demo
You can rewrite your query like this:
SELECT *
FROM table1 t1
WHERE t1.colA BETWEEN XXXX AND XXXX and
t1.pk_id IN (
SELECT t2.pk_id
FROM (SELECT t3.pk_id, t3.ColA, SUM(t3.amt) as amt
FROM table1 t3
group by t3.pk_id, t3.ColA
having sum(t3.amt) > 0
) t2
WHERE t2.colA = t1.colA
)
From here, you can rewrite it as:
select t1.*
from table1 t1 join
(SELECT t3.pk_id, t3.ColA, SUM(t3.amt) as amt
FROM table1 t3
group by t3.pk_id, t3.ColA
having sum(t3.amt) > 0
) t2
on t1.pk_id = t2.pk_id and t1.ColA = t3.ColA
WHERE t1.colA BETWEEN XXXX AND XXXX