SQL Exclude Row if Specific Value Exists Within Joined Field - sql

I am trying to write an SQL query that will allow me to exclude a record from TableA if it has at least one match against TableB.
I have written some code, as below, that almost gets me what I need -
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND NOT EXISTS (SELECT *
FROM TableB
WHERE A_ROW_WID = a.ROW_WID
AND EXTRACT(YEAR FROM b.CREATED) >= '2017')
;
Table A
ROW_WID | ID | OPEN_DT
---------------------------------
1 | A | 2013-01-01
2 | B | 2014-01-01
3 | C | 2017-01-01
Table B
RULE_ID | A_ROW_WID | LINKCREATED
---------------------------------
1 | A | 2014-01-01
2 | A | 2017-01-01
3 | B | 2017-01-01
The query above would return 1 row for ROW_WID = 1, 1 row for ROW_WID = 2 and nothing for ROW_WID = 3.
I would like my query to exclude ROW_WID=1 altogether because there is one row in TableB that has the year 2017.
I hope this question is clear, but let me know if not.
-EDIT-
Expected result would look like this -
ID | OPEN_DT | LINKCREATED | RULE_ID
C | 2017-01-01 | NULL | NULL
As ID 'C' from TableA has no link in TableB.
If there were an entry in A that had any links in B prior to 2017, they would be returned. Just not any with a TableB entry >= 2017.

Your issue is that you aren't checking for the max created date in the NOT EXISTS:
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND NOT EXISTS (SELECT 'NE'
FROM TableB B2
WHERE A_ROW_WID = a.ROW_WID
AND B2.LINKCREATED= (SELECT MAX(BE.LINKCREATED) FROM TableB BE WHERE B2.A_ROW_WID=BE.A_ROW_WID)
AND EXTRACT(YEAR FROM b2.CREATED) >= '2017')

Try using not in:
SELECT a.ID,
a.OPEN_DT,
b.LINKCREATED,
b.RULE__ID
FROM TableA a
LEFT JOIN TableB b
ON a.ROW_WID = b.A_ROW_WID
WHERE EXTRACT(YEAR FROM a.OPEN_DT) >= '2013'
AND b.rule_id not in (select rule_id from TableB where A_ROW_WID in (SELECT
A_ROW_WID
FROM TableB
WHERE EXTRACT(YEAR FROM b.CREATED) >= '2017')a)b

Related

Joining Table A and B to get elements of both

I have two tables:
Table 'bookings':
id | date | hours
--------------------------
1 | 06/01/2016 | 2
1 | 06/02/2016 | 1
2 | 06/03/2016 | 2
3 | 06/03/2016 | 4
Table 'lookupCalendar':
date
-----
06/01/2016
06/02/2016
06/03/2016
I want to join them together so that I have a date for each booking so that the results look like this:
Table 'results':
id | date | hours
--------------------------
1 | 06/01/2016 | 2
1 | 06/02/2016 | 1
1 | 06/03/2016 | 0 <-- Added by query
2 | 06/01/2016 | 0 <-- Added by query
2 | 06/02/2016 | 0 <-- Added by query
2 | 06/03/2016 | 2
3 | 06/01/2016 | 0 <-- Added by query
3 | 06/02/2016 | 0 <-- Added by query
3 | 06/03/2016 | 4
I have tried doing a cross-apply, but that doesn't get me there, neither does a full join. The FULL JOIN just gives me nulls in the id column and the cross-apply gives me too much data.
Is there a query that can give me the results table above?
More Information
It might be beneficial to note that I am doing this so that I can calculate an average hours booked over a period of time, not just the number of records in the table.
Ideally, I'd be able to do
SELECT AVG(hours) AS my_average, id
FROM bookings
GROUP BY id
But since that would just give me a count of the records instead of the count of the days I want to cross apply it with the dates. Then I think I can just do the query above with the results table.
select i.id, c.date, coalesce(b.hours, 0) as hours
from lookupCalendar c
cross join (select distinct id from bookings) i
left join bookings b
on b.id = i.id
and b.date = c.date
order by i.id, c.date
Try this:
select c.date, b.id, isnull(b.hours, 0)
from lookupCalendar c
left join bookings b on b.date = c.date
LookupCalendar is your main table because you want the bookings against each date, irrespective of whether there was a booking on that date or not, so a left join is required.
I am not sure if you need to include b.id to solve your actual problem though. Wouldn't you just want to get the total number of hours booked against each date like this, to then calculate the average?:
select c.date, sum(isnull(b.hours, 0))
from lookupCalendar c
left join bookings b on b.date = c.date
group by c.date
You can try joining all the combinations of IDs and dates and left joining the data;
WITH Booking AS (SELECT *
FROM (VALUES
( 1 , '06/01/2016', 2 )
, ( 1 , '06/02/2016', 1 )
, ( 2 , '06/03/2016', 2 )
, ( 3 , '06/03/2016', 4 )
) x (id, date, hours)
)
, lookupid AS (
SELECT DISTINCT id FROM Booking
)
, lookupCalender AS (
SELECT DISTINCT date FROM Booking
)
SELECT ID.id, Cal.Date, ISNULL(B.Hours,0) AS hours
FROM lookupid id
INNER JOIN lookupCalender Cal
ON 1 = 1
LEFT JOIN Booking B
ON id.id = B.id
AND Cal.date = B.Date
ORDER BY ID.id, Cal.Date

left join not giving correct output

I am having 2 table
tableA
Accountid
-----------
10
11
12
tableB
Accountid | Date |
--------------------------------------------
10 | 2016-02-02 |
11 | 2016-02-02 |
11 | 2016-02-02 |
15 | 2016-02-03 |
I am expecting the output like
Accountid | ID |
------------------------------------
10 | 10 |
11 | 11 |
12 | NULL |
I am running this query
select A.accountid,b.accountid as ID
from tableA as A
left join tableB as B on a.accountid=b.accountid
where b.date between '2016-02-02' and '2016-02-02'
but it is giving me the output as, I am not sure where am I going wrong
Accountid | ID |
-----------------------------------
10 | 10 |
11 | 11 |
I am using MSSQL database.
When any field of the right table of a left join is occurred in WHERE clause then this join will behave like INNER JOIN
To get expected result your query should be like this
select A.accountid,b.accountid as ID
from tableA as A
left join tableB as B on a.accountid=b.accountid and
b.date between '2016-02-02' and '2016-02-02'
Try this:
select A.accountid,b.accountid as ID
from tableA as A left join tableB as B on a.accountid=b.accountid
where b.date is null or b.date between '2016-02-02' and '2016-02-02'
The reason is, for AccountID 12 b.date is effectively null (because there's no row in tableB). Therefore you'll only get a result for that row if you allow date to be null in the query.
If b does not exist (id = 12) your where clause return false .
if you want to see the row with id=12 you must include your test (b.date between '2016-02-02' and '2016-02-02') with your "ON" clause :
select A.accountid,b.accountid as ID from tableA as A left join tableB as B
on a.accountid=b.accountid and b.date between '2016-02-02' and '2016-02-02'
the reason is because the WHERE clause of your select is executed after the LEFT JOIN
so, first sql server will extract the data as you expect, with the row 12-NULL,
and then it will be filtered out and removed from output by your WHERE clause
you can move the date filter on JOIN condition as suggested by #JaydipJ and #RémyBaron
or filter the tableB before the JOIN this way:
select A.accountid,b.accountid as ID
from tableA as A
left join (
select *
from tableB
where b.date between '2016-02-02' and '2016-02-02'
) as B on a.accountid=b.accountid

SQL Server 2005: How to join table rows only once

I think I've seen answers for similar questions for MySQL, but I'm struggling to find an answer applicable to SQL Server 2005.
So I have a table like this:
| ID | RelationalID | Year
----------------------------
| 1 | A | 2014
| 2 | A | 2014
| 3 | B | 2014
| 4 | A | 2015
| 5 | B | 2015
And I'd like a result like this when I join the same table where RelationID matches but the year is different:
| 2014_ID | 2015_ID | RelationalID |
------------------------------------
| 1 | 4 | A |
| 2 | NULL | A |
| 3 | 5 | B |
But a standard JOIN ends up getting duplicate matches:
| 2014_ID | 2015_ID | RelationalID |
------------------------------------
| 1 | 4 | A |
| 2 | 4 | A |
| 3 | 5 | B |
Is there a way to join two tables where the matches from the right table are joined only once in SQL Server 2005?
I tried this query with no success:
SELECT * FROM myTable
LEFT JOIN (SELECT * FROM myTable) AS t ON t.RelationalID = myTable.RelationalID
WHERE myTable.Year = 2014 and t.Year = 2015
You can get the result based on ROW_NUMBERs, but you need a rule how to assign them, I assumed it's based on the Id.
;WITH cte AS
(SELECT Id,
RelationalId,
year,
row_number()
over (partition by RelationalId, year
order by Id) as rn
FROM [YourTable]
)
select t1.id as Id_2014,t2.id as Id_2015, t1.RelationalId
from cte as t1 left join cte as t2
on t1.RelationalId = t2.RelationalId
and t1.rn = t2.rn
and t2.year = 2015
where t1.Year = 2014
This is based on TMNT2014's fiddle
Below Sql would give you the result you are looking for but as I said before complexity would depend on the original set of data you have in your table. Here is the SQL Fiddle - http://sqlfiddle.com/#!3/d6300/24 - Good Luck!
;WITH CTE_Union AS
(SELECT
a.Id AS Id2014,
NULL AS Id2015,
a.RelationalId
FROM [YourTable] a
WHERE a.Year = 2014
UNION
SELECT
NULL AS Id2014,
b.Id AS Id2015,
b.RelationalId
FROM [YourTable] b
WHERE b.Year = 2015)
SELECT Distinct CASE WHEN Id2014 IS NULL THEN (SELECT MIN(Id2014) FROM CTE_Union C WHERE C.RelationalId =M.RelationalId) ELSE Id2014 END AS ID2014 ,
CASE WHEN Id2015 IS NULL AND Id2014 = (SELECT MIN(Id2014) FROM CTE_Union C2 WHERE C2.RelationalId =M.RelationalId) THEN (SELECT MIN(Id2015) FROM CTE_Union C WHERE C.RelationalId =M.RelationalId) ELSE Id2015 END
,RelationalID
FROM CTE_Union M
DECLARE #MyTable TABLE
(
ID INT,
RelationalID VARCHAR(10),
[Year] INT
)
INSERT INTO #MyTable
VALUES
( 1 ,'A', 2014),
( 2 ,'A', 2014),
( 3 ,'B', 2014),
( 4 ,'A', 2015),
( 5 ,'B', 2015)
;WITH TEST AS
(
SELECT
a.Id AS Id2014,
NULL AS Id2015,
a.RelationalId,
RANK() OVER (PARTITION BY RelationalId ORDER BY ID) Ranked
FROM #MyTable a
WHERE a.Year = 2014
UNION
SELECT
NULL AS Id2014,
b.Id AS Id2015,
b.RelationalId,
RANK() OVER (PARTITION BY RelationalId ORDER BY ID) Ranked
FROM #MyTable b
WHERE b.Year = 2015
)
SELECT
t1.Id2014,
t2.Id2015,
t1.RelationalID
FROM TEST t1
LEFT JOIN TEST t2
ON t1.Ranked = t2.Ranked
AND t1.RelationalID = t2.RelationalID
AND t2.Id2015 IS NOT NULL
WHERE t1.Id2014 IS NOT NULL
ORDER BY t1.Id2014
I used a union and then ranked each side by relational id and left joined them.
Here is the output:
Id2014 Id2015 RelationalID
1 4 A
2 NULL A
3 5 B
There are probably a few ways to solve this but below shows an example of utilizing "Derived Tables" in a query.
SELECT
q1.Id AS [2014_Id],
q2.Id AS [2015_Id],
q1.RelationalId
FROM (SELECT
MAX(a.Id) AS Id,
a.RelationalId
FROM [table] a
WHERE a.Year = 2014
GROUP BY
a.RelationalId) q1
INNER JOIN (SELECT
MAX(b.Id) AS Id,
b.RelationalId
FROM [table] b
WHERE b.Year = 2015
GROUP BY
b.RelationalId) q2
ON q2.RelationalId = q1.RelationalId

IF ( Count(*) > 1)

I am trying to look through two tables TableA and TableB get print out the TableA.ID of any that show more than 1 count. TableA looks like this:
ID | Code
------------
1 | A
2 | B
3 | C
Table B Looks like
ID | AID | EffectiveDate | ExpirationDate
------------------------------------------------
1 | 1 | 2012-01-01 | 2012-12-31
2 | 1 | 2012-01-01 | 2012-12-31
3 | 2 | 2012-01-01 | 2012-12-31
4 | 3 | 2012-01-01 | 2012-12-31
The Query I am using looks like this:
DECLARE #MoreThanOne varchar(250)
SET #MoreThanOne = ''
IF((SELECT COUNT(*) FROM TableA
WHERE EXISTS(
SELECT TableB.ID
,TableB.EffectiveDate
,TableB.ExpirationDate
FROM TableB
WHERE TableB.AID = TableA.ID
and GETDATE() Between TableB.EffectiveDate and TableB.ExpirationDate
)
GROUP BY TableA.Code) > 1)
BEGIN
--SET #MoreThanOne = #MoreThanOne + TableA.Code + CHAR(10)
END
PRINT #MoreThanOne
I know that my nested Query works when reworked it will print the counts for all in the unique codes in TableA.
I know that I can not use what I commented out because i don't have access to TableA.Code.
My question is there another way to do this or how can I get access to TableA.Code for the Message MoreThanOne.
Thanks For the help!
This query will get you the codes for all AIDs that are duplicated in table B:
SELECT Code
FROM TableA
WHERE AID IN
(
SELECT AID
FROM TableB
GROUP BY AID
HAVING COUNT(*) > 1
)
You may also wish to add the WHERE condition that have in your stored procedure to the inner select.
Try this
SELECT TableA.ID, TableA.Code, Count(*) As Cnt
FROM TableB, TableA
WHERE TableB.AID = TableA.ID
and GETDATE() Between TableB.EffectiveDate and TableB.ExpirationDate
GROUP BY TableA.ID, TableA.Code
HAVING COUNT(*) > 1
You can do this very simply, with no join:
SELECT tableb.AID
FROM TableB
WHERE GETDATE() Between TableB.EffectiveDate and TableB.ExpirationDate
group by tableb.AID
having count(*) > 1
This simply aggregates tableB by AID, returning the values with more than one record. You only need to join to TableA if you want the code as well.
This should do you:
select Code = a.Code ,
Frequency = count(*)
from table_a a
join table_b b on b.aid = a.id
and current_timestamp between b.EffectiveDate and b.ExpirationDate
group by a.Code
having count(*) > 1
order by 2 desc -- order in descending sequence by frequency
1 -- then ascending sequence by code

How to create view that combine multiple row from 2 tables?

I want to create view that combine data from two tables, sample data in each table is like below.
SELECT Command for TableA
SELECT [ID], [Date], [SUM]
FROM TableA
Result
ID | Date | SUM
1 | 1/1/2010 | 2
1 | 1/2/2010 | 4
3 | 1/3/2010 | 6
SELECT Command for TableB
SELECT [ID], [Date], [SUM]
FROM TableB
Result
ID | Date | SUM
1 | 1/1/2010 | 5
1 | 2/1/2010 | 3
1 | 31/1/2010 | 2
2 | 1/2/2010 | 20
I want output like below
ID | Date | SUMA | SUMB
1 | 1/1/2010 | 2 | 10
1 | 1/2/2010 | 4 | 0
2 | 1/2/2010 | 0 | 20
3 | 1/3/2010 | 6 | 0
How can I do that on SQL Server 2005?
Date information be vary, as modify in table.
Try this...
SELECT
ISNULL(TableA.ID, TableB.ID) ID,
ISNULL(TableA.Date, TableB.Date),
ISNULL(TableA.Sum,0) SUMA,
ISNULL(TableB.Sum, 0) SUMB
FROM
TableA FULL OUTER JOIN TableB
ON TableA.ID = TableB.ID AND TableA.Date = TableB.Date
ORDER BY
ID
A full outer join is what you need because you want to include results from both tables regardless of whether there is a match or not.
I usually union the two queries together and then group them like so:
SELECT ID, [Date], SUM(SUMA) As SUMA, SUM(SUMB) AS SUMB
FROM (
SELECT ID, [Date], SUMA, 0 AS SUMB
FROM TableA
UNION ALL
SELECT ID, [Date], 0 As SUMA, SUMB
FROM TableB
)
GROUP BY ID, [Date]
SELECT
ISNULL(a.ID, b.ID) AS ID,
ISNULL(a.Date, b.Date) AS Date,
ISNULL(a.SUM, 0) AS SUMA,
ISNULL(b.SUM, 0) AS SUMB,
FROM
TableA AS a
FULL JOIN
TableB AS b
ON a.ID = b.ID
AND a.Date = b.Date;
It's not obvious how you want to combine the two tables. I think this is what you're after, but can you confirm please?
TableA.Date is the most important field; if a given date occurs in TableA then it will be included in the view, but not if it only occurs in TableB.
If a date has records in TableA and TableB and the records have a matching ID, they are combined into one row in the view with SUMA being taken from TableA.Sum and SUMB being TableA.Sum * TableB.Sum (e.g. Date: 01/01/2010, ID: 1) (e.g. Date: 01/03/2010 ID: 3).
If a date has records in TableA and TableB with different IDs, the view include these records separately without multiplying the Sum values at all (e.g. Date 02/01/2010, ID: 1 and ID: 2)