Left Join when > 0 rows but doesn't match where clause - sql

So I have a query where if there are no matching join in the table it show zero which is what I want. However, if there are matching rows but they don't match the rows in the where clause, I don't get a zero I get nothing.
SELECT *
FROM PUEvents PUE1
OUTER APPLY (
SELECT day
FROM Day_Map
WHERE Date = '2014-04-12'
) D1
OUTER APPLY (
SELECT date
FROM Day_Map
WHERE Day = D1.Day AND
Year = '2013' AND
MapID > 976
) D2
LEFT JOIN PUConsumerData PUC ON PUC.EventID = PUE1.EventID
WHERE PUE1.EventID > 4958 AND
PUE1.EventId < 5023 AND
PUE1.TourID = 353 AND
(
CONVERT(DATE, PUC.PictureTakenDate) = '2014-04-12' OR
PUC.PictureTakenDate IS NULL
) AND
(
PUC.FirstName <> 'ZOOM' OR
FirstName IS NULL
)
ORDER BY PUE1.Name
So for row 51, there are rows in the database with that eventid but not for the date range so I don't get the event data. I need the event data just the fact there's no matching consumer data.

aaron-betrand was right if I move the conditions in the WHERE clause up to the JOIN it worked fine. Simple

Related

How to find difference between table with multiple conditions

I have exact two tables but some value differences. So I would like to find those differences with condition that if the column value has a difference of more than 10.
For example, all 9 columns have the same values in both tables, but the difference between the values column is 11, so this record is different. If the value difference is 9 so records are the same.
So I wrote this query to get differences:
select *
from test.test m
inner join test.test1 t
on
m.month_date = t.month_date and
m.level_1 = t.level_1 and
m.level_2 = t.level_2 and
m.level_3 = t.level_3 and
m.level_4 = t.level_4 and
m.level_header = t.level_header and
m.unit = t.unit and
m.model_type_id = t.model_type_id and
m.model_version_desc = t.model_version_desc
where m.month_date = '2022-11-01' and abs(m.value - t.value) > 10)
so this returns me all records that all column values are matched but did not pass the value difference condition.
Second, i have full outer join to get all differences
select *
from test.test m
full outer join test.test1 t
on
m.month_date = t.month_date and
m.level_1 = t.level_1 and
m.level_2 = t.level_2 and
m.level_3 = t.level_3 and
m.level_4 = t.level_4 and
m.level_header = t.level_header and
m.unit = t.unit and
m.model_type_id = t.model_type_id and
m.model_version_desc = t.model_version_desc
where m.month_date is null or t.month_date is null and m.month_date = '2022-11-01'
How can I combine the results of these two queries without UNION? I want to have only one query (sub query is acceptable)
Assuming that for a given day, you need to find
rows that match between the tables but exceed the value difference threshold
AND
rows present in either left or right table, that don't have a corresponding row in the other table
select *
from test.test m
full outer join test.test1 t
using (
month_date,
level_1,
level_2,
level_3,
level_4,
level_header,
unit,
model_type_id,
model_version_desc )
where (m.month_date is null
or t.month_date is null
and m.month_date = '2022-11-01' )
or (m.month_date = '2022-11-01' and abs(m.value - t.value) > 10);
Online demo
Since the columns used to join the tables have the same names, you can shorten their list by swapping out the lengthy table1.column1=table2.column1 and... list of pairs for a single USING (month_date,level_1,level_2,level_3,...) (doc). As a bonus, it will avoid listing the matching columns twice in your output, once for the left table, once for the right table.
select *
from (select 1,2,3) as t1(a,b,c)
full outer join
(select 1,2,3) as t2(a,b,c)
on t1.a=t2.a
and t1.b=t2.b
and t1.c=t2.c;
-- a | b | c | a | b | c
-----+---+---+---+---+---
-- 1 | 2 | 3 | 1 | 2 | 3
select *
from (select 1,2,3) as t1(a,b,c)
full outer join
(select 1,2,3) as t2(a,b,c)
using(a,b,c);
-- a | b | c
-----+---+---
-- 1 | 2 | 3
In your first query, you can replace the null values for a specific number. Something like this:
where m.month_date = '2022-11-01' and abs(ISNULL(m.value,-99) - ISNULL(t.value,-99)) > 10)
The above will replace the nulls for -99 (choose an appropriate value for your data), so if you have that m.value is 10 and t.value is null, then should be returned in your first query.

SQL statement return matching records from join and null records from join

I have a db with records like the following structure in the table;
id,msg,date,fid
19 ,"start","2012-02",NULL
20 ,"end","2012-03",19
21 , "start",2012-04,NULL
I have and entire db set up like this where the fid in some cases matches the id of another record. Id like to return all records that have matching start and end dates and just the start date if it doesn't have a corresponding end date fid like below:
19 ,"start","2012-02","end","2012-03"
21 , "start,"2012-04,"",NULL
I think you want a left join:
select t.fid, t.date as start_date, tf.date as end_date
from t left join
t tf
on tf.id = t.fid
where t.msg = 'start'
This assumes that there are only two types of records and fid is always non-NULL for 'end' records.
You just need a LEFT OUTER JOIN to your table. You can use an alias to refer to each instance:
SELECT startdt.id, startdt.msg, startdt.date, enddt.msg, enddt.date
FROM yourtable as startdt
LEFT OUTER JOIN yourtable as enddt
ON startdt.fid = enddt.id
AND enddt.msg = 'end'
WHERE startdt.msg = 'start'

How to use RIGHT OUTER JOIN with GROUP BY in SQl Server 2017?

i want use ROJ in sql server with filter date but not work, i mean must return data table round null but not return how to fix problem ?
SQL Code
SELECT Lines.Target, COUNT(Round.ID) AS cnt
FROM Round RIGHT OUTER JOIN Line as Lines
on Round.Line = Lines.ID
WHERE Lines.Company = 20 AND
CAST(Round.System_Date AS DATE) BETWEEN
CAST('2019-03-01' AS DATE) AND CAST('2019-03-01' AS DATE)
GROUP BY Lines.Target
with out filter date code work
Must return =>
Target cnt
------ -----
7 0
9 0
15 0
Switch to LEFT JOIN. Move outer table condition from WHERE to ON to get true outer join result:
SELECT Lines.Target, COUNT(Round.ID) AS cnt
FROM Line as Lines
LEFT OUTER JOIN Round
on Round.Line = Lines.ID
AND CAST(Round.System_Date AS DATE) BETWEEN
CAST('2019-03-01' AS DATE) AND CAST('2019-03-01' AS DATE)
WHERE Lines.Company = 20
GROUP BY Lines.Target
Theres no BETWEEN required as its the same date you are searching for and make sure record exists for that specific date
Plus,
If you want the rounds table data being the master table then why you are using right outer join use Left Outer Join

Limit join to one row

I have the following query:
SELECT sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount, 'rma' as
"creditType", "Clients"."company" as "client", "Clients".id as "ClientId", "Rmas".*
FROM "Rmas" JOIN "EsnsRmas" on("EsnsRmas"."RmaId" = "Rmas"."id")
JOIN "Esns" on ("Esns".id = "EsnsRmas"."EsnId")
JOIN "EsnsSalesOrderItems" on("EsnsSalesOrderItems"."EsnId" = "Esns"."id" )
JOIN "SalesOrderItems" on("SalesOrderItems"."id" = "EsnsSalesOrderItems"."SalesOrderItemId")
JOIN "Clients" on("Clients"."id" = "Rmas"."ClientId" )
WHERE "Rmas"."credited"=false AND "Rmas"."verifyStatus" IS NOT null
GROUP BY "Clients".id, "Rmas".id;
The problem is that the table "EsnsSalesOrderItems" can have the same EsnId in different entries. I want to restrict the query to only pull the last entry in "EsnsSalesOrderItems" that has the same "EsnId".
By "last" entry I mean the following:
The one that appears last in the table "EsnsSalesOrderItems". So for example if "EsnsSalesOrderItems" has two entries with "EsnId" = 6 and "createdAt" = '2012-06-19' and '2012-07-19' respectively it should only give me the entry from '2012-07-19'.
SELECT (count(*) * sum(s."price")) AS amount
, 'rma' AS "creditType"
, c."company" AS "client"
, c.id AS "ClientId"
, r.*
FROM "Rmas" r
JOIN "EsnsRmas" er ON er."RmaId" = r."id"
JOIN "Esns" e ON e.id = er."EsnId"
JOIN (
SELECT DISTINCT ON ("EsnId") *
FROM "EsnsSalesOrderItems"
ORDER BY "EsnId", "createdAt" DESC
) es ON es."EsnId" = e."id"
JOIN "SalesOrderItems" s ON s."id" = es."SalesOrderItemId"
JOIN "Clients" c ON c."id" = r."ClientId"
WHERE r."credited" = FALSE
AND r."verifyStatus" IS NOT NULL
GROUP BY c.id, r.id;
Your query in the question has an illegal aggregate over another aggregate:
sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount
Simplified and converted to legal syntax:
(count(*) * sum(s."price")) AS amount
But do you really want to multiply with the count per group?
I retrieve the the single row per group in "EsnsSalesOrderItems" with DISTINCT ON. Detailed explanation:
Select first row in each GROUP BY group?
I also added table aliases and formatting to make the query easier to parse for human eyes. If you could avoid camel case you could get rid of all the double quotes clouding the view.
Something like:
join (
select "EsnId",
row_number() over (partition by "EsnId" order by "createdAt" desc) as rn
from "EsnsSalesOrderItems"
) t ON t."EsnId" = "Esns"."id" and rn = 1
this will select the latest "EsnId" from "EsnsSalesOrderItems" based on the column creation_date. As you didn't post the structure of your tables, I had to "invent" a column name. You can use any column that allows you to define an order on the rows that suits you.
But remember the concept of the "last row" is only valid if you specifiy an order or the rows. A table as such is not ordered, nor is the result of a query unless you specify an order by
Necromancing because the answers are outdated.
Take advantage of the LATERAL keyword introduced in PG 9.3
left | right | inner JOIN LATERAL
I'll explain with an example:
Assuming you have a table "Contacts".
Now contacts have organisational units.
They can have one OU at a point in time, but N OUs at N points in time.
Now, if you have to query contacts and OU in a time period (not a reporting date, but a date range), you could N-fold increase the record count if you just did a left join.
So, to display the OU, you need to just join the first OU for each contact (where what shall be first is an arbitrary criterion - when taking the last value, for example, that is just another way of saying the first value when sorted by descending date order).
In SQL-server, you would use cross-apply (or rather OUTER APPLY since we need a left join), which will invoke a table-valued function on each row it has to join.
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
-- CROSS APPLY -- = INNER JOIN
OUTER APPLY -- = LEFT JOIN
(
SELECT TOP 1
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(#in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(#in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
) AS FirstOE
In PostgreSQL, starting from version 9.3, you can do that, too - just use the LATERAL keyword to achieve the same:
SELECT * FROM T_Contacts
--LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1
--WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
LEFT JOIN LATERAL
(
SELECT
--MAP_CTCOU_UID
MAP_CTCOU_CT_UID
,MAP_CTCOU_COU_UID
,MAP_CTCOU_DateFrom
,MAP_CTCOU_DateTo
FROM T_MAP_Contacts_Ref_OrganisationalUnit
WHERE MAP_CTCOU_SoftDeleteStatus = 1
AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID
/*
AND
(
(__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo)
AND
(__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom)
)
*/
ORDER BY MAP_CTCOU_DateFrom
LIMIT 1
) AS FirstOE
Try using a subquery in your ON clause. An abstract example:
SELECT
*
FROM table1
JOIN table2 ON table2.id = (
SELECT id FROM table2 WHERE table2.table1_id = table1.id LIMIT 1
)
WHERE
...

Filtering SQL query by unique id and earliest dates that are in the future

I have this query that returns the correct data, but I would like to filter it.
SELECT TOP (100) PERCENT dbo.Reg_Master.id, dbo.Cart_Programs.cartid, dbo.Reg_Master.F_ID, dbo.BlockPeriod.profileid, dbo.Reg_Master.FirstName,
dbo.Reg_Master.LastName, dbo.BlockPeriod.startdate, dbo.Cart_Programs.blockid
FROM dbo.Cart_Programs LEFT OUTER JOIN
dbo.Reg_Master ON dbo.Cart_Programs.cartid = dbo.Reg_Master.cartid LEFT OUTER JOIN
dbo.BlockPeriod ON dbo.Cart_Programs.blockid = dbo.BlockPeriod.id
WHERE (dbo.BlockPeriod.profileid = xxx) AND (dbo.Reg_Master.F_ID = xxxx)
ORDER BY dbo.BlockPeriod.startdate
For each dbo.Reg_Master.id, I would like to return only the earliest dbo.BlockPeriod.startdate (that is today or later - in other words ignoring dates that have already passed) for each dbo.Reg_Master.id, I cannot seem to get it formatted correctly.
First of all, TOP 100 Percent does nothing, the optimizer will just ignore it.
Also, your left joins do not serve any purpose because your WHERE condition, so I have edited the SQL to use an inner join + cross apply vs an outer join + outer apply.
If I understand you correctly for each Reg_Master record, you want at most 1 record from BlockPeriod, where that 1 record is the closest date that is greater than today's date.
If so, then what you are looking for is an APPLY table operator combined with TOP (1) as shown below:
UPDATED:
SELECT Reg_Master.id,
Cart_Programs.cartid,
Reg_Master.F_ID,
T.profileid,
Reg_Master.FirstName,
Reg_Master.LastName,
T.startdate,
Cart_Programs.blockid
FROM Cart_Programs
JOIN Reg_Master ON Cart_Programs.cartid = Reg_Master.cartid
CROSS APPLY(
SELECT TOP 1 * FROM BlockPeriod
WHERE BlockPeriod.id = Cart_Programs.blockid
AND BlockPeriod.profileid = xxx AND Reg_Master.F_ID = xxxx
AND BlockPeriod.startdate >= GETDATE()
ORDER BY BlockPeriod.startdate ASC
) AS T