Oracle single query with multiple lookups to same table - sql

I have a reference table containing a list of account numbers. For each account in that table, I need to query a table containing a list of activities; each activity can have zero to many associated notes. Each activity also has a product, call type, reason and outcome, which are stored as references to a category table containing these in a single field. And just for fun, I have to pull all of this data in a single query.
Here's where I am so far:
SELECT
ACTIVITY.ACTIVITYID,
ACTIVITY.ACCOUNTNO,
ACTIVITY.CREATEDATETIME,
C1.CATDESC AS PRODUCT,
C2.CATDESC AS CALLDRIVER,
C3.CATDESC AS REASON,
C4.CATDESC AS OUTCOME,
NOTE.NOTEID,
NOTE.NOTEDESC
FROM NOTE
RIGHT JOIN ACTIVITY
ON NOTE.ACTIVITYID = ACTIVITY.ACTIVITYID
RIGHT JOIN REFERENCE
ON ACTIVITY.ACCOUNTNO = REFERENCE.ACCOUNTNO
INNER JOIN CATEGORY C1
ON ACTIVITY.PRODUCTCODE = C1.CATCODE
INNER JOIN CATEGORY C2
ON ACTIVITY.CALLDRIVERCODE = C2.CATCODE
INNER JOIN CATEGORY C3
ON ACTIVITY.REASONCODE = C3.CATCODE
INNER JOIN CATEGORY C4
ON ACTIVITY.OUTCOMECODE = C4.SOURCECATCODE
WHERE ACTIVITY.CREATEDATETIME >= (SYSDATE -30)
ORDER BY ACTIVITYID, NOTEID
This almost does what I want it to do, except that it returns many, many more rows than it should (between 12 and 40 rows instead of 1-3). If I remove the joins to CATEGORY, like so:
SELECT
ACTIVITY.ACTIVITYID,
ACTIVITY.ACCOUNTNO,
ACTIVITY.CREATEDATETIME,
NOTE.NOTEID,
NOTE.NOTEDESC
FROM NOTE
RIGHT JOIN ACTIVITY
ON NOTE.ACTIVITYID = ACTIVITY.ACTIVITYID
RIGHT JOIN REFERENCE
ON ACTIVITY.ACCOUNTNO = REFERENCE.ACCOUNTNO
WHERE ACTIVITY.CREATEDATETIME >= (SYSDATE -30)
ORDER BY ACTIVITYID, NOTEID
then it works perfectly as expected, so I know the problem is with the repeated joins to the CATEGORY table.
The PRODUCTCODE, CALLDRIVERCODE, REASONCODE and OUTCOMECODE fields in ACTIVITY all map to the CATCODE (id) and CATDESC (string) fields in CATEGORY. What I'm looking for is an alternative way to look up these values on a row by row basis, while still containing all of this within a single query.

As an alternative you can put the category queries in the select part of the query.
select ...
,(select c1.catdesc from category c1
where c1.catcode=activity.productcode) as product
,(select c2.catdesc from category c2
where c2.catcode=activity.calldrivercode) as calldriver
...

Related

How to find missing data in table Sql

This is similar to How to find missing data rows using SQL? and How to find missing rows (dates) in a mysql table? but a bit more complex, so I'm hitting a wall.
I have a data table with the noted Primary key:
country_id (PK)
product_id (PK)
history_date (PK)
amount
I have a products table with all products, a countries table, and a calendar table with all valid dates.
I'd like to find all countries, dates and products for which there are missing products, with this wrinkle:
I only care about dates for which there are entries for a country for at least one product (i.e. if the country has NOTHING on that day, I don't need to find it) - so, by definition, there is an entry in the history table for every country and date I care about.
I know it's going to involve some joins maybe a cross join, but I'm hitting a real wall in finding missing data.
I tried this (pretty sure it wouldn't work):
SELECT h.history_date, h.product_id, h.country_id, h.amount
FROM products p
LEFT JOIN history h ON (p.product_id = h.product_id)
WHERE h.product_id IS NULL
No Joy.
I tried this too:
WITH allData AS (SELECT h1.country_id, p.product_id, h1.history_date
FROM products p
CROSS JOIN (SELECT DISTINCT country_id, history_date FROM history) h1)
SELECT f.history_date, f.product_id, f.country_id
FROM allData f
LEFT OUTER JOIN history h ON (f.country_id = h.country_id AND f.history_date = h.history_date AND f.product_id = h.product_id)
WHERE h.product_id IS NULL
AND h.country_id IS NOT NULL
AND h.history_date IS NOT null
also no luck. The CTE does get me every product on every date that there is also data, but the rest returns nothing.
I only care about dates for which there are entries for a country for
at least one product (i.e. if the country has NOTHING on that day, I
don't need to find it)
So we care about this combination:
from (select distinct country_id, history_date from history) country_date
cross join products p
Then it's just a matter of checking for existence:
select *
from (select distinct country_id, history_date from history) country_date
cross join products p
where not exists (select null
from history h
where country_date.country_id = h.country_id
and country_date.history_date = h.history_date
and p.product_id = h.product_id
)

SQL query. Joining column with itself, looks like should work but missing pairs

I've simplified the database to make it easier. Here's the diagram:
We have products. Products belong to categories and have manufacturers.
What I need is, for every pair of categories, to find the count of manufacturers that have manufactured products of both categories.
So the result set needs to look like this (this is not actual data):
This is what I've tried so far:
SELECT phC1.category_id as Cat1 , PhC2.category_id as Cat2, COUNT(PhM.manufacturer_id)
FROM
product_has_category phC1, product_has_category phC2, product_has_manufacturer PhM
WHERE
PhM.product_id = phC1.product_id AND PhM.product_id=PhC2.product_id AND PhC1.category_id < phC2.category_id
group by PhC1.category_id, PhC2.category_id
order by PhC1.category_id, PhC2.category_id
This looks to me like it should be right, but I'm getting less results than I should be. I'm missing some Category pairs. Am I approaching this wrong?
Thank you
I did some assumptions, which may be wrong.
I assumed, that there's a Categories table - as this makes things
easier.
I assumed, that a product can have multiple (or no) categories.
I assumed, that a category can be assigned to multiple (or no) products.
For manufacturers, I took the same assumptions, as for categories.
This is what I came up with:
SELECT c1.Category_id AS Cat1,
c2.Category_id AS Cat2,
COUNT( DISTINCT
CASE
WHEN c1m.Manufacturer_id = c2m.Manufacturer_id
THEN c1m.Manufacturer_id
END
) AS pCount
FROM Category AS c1
INNER JOIN Category AS c2
ON c1.Category_id < c2.Category_id
LEFT JOIN ProductHasCategory c1p
ON c1p.Category_id = c1.Category_id
LEFT JOIN ProductHasCategory c2p
ON c2p.Category_id = c2.Category_id
LEFT JOIN ProductHasManufacturer c1m
ON c1m.Product_id = c1p.Product_id
LEFT JOIN ProductHasManufacturer c2m
ON c2m.Product_id = c2p.Product_id
GROUP BY c1.Category_id, c2.Category_id
ORDER BY 1, 2
This is a fiddle for it, using MS SQL Server:
http://sqlfiddle.com/#!18/461cc/1

SQL many-to-many, how to check criteria on multiple rows

In many-to-many table, how to find ID where all criteria are matched, but maybe one row matches one criterion and another row matches another criterion?
For example, let's say I have a table that maps shopping carts to products, and another table where the products are defined.
How can I find a shopping cart that has at least one one match for every criterion?
Criteria could be, for example, product.category like '%fruit%', product.category like '%vegetable%', etc.
Ultimately I want to get back a shopping cart ID (could be all of them, but in my specific case I am happy to get any matching ID) that has at least one of each match in it.
I am assuming a table named cart_per_product with fields cart,product, and a table named product with fields product,category.
select cart from cart_per_product c
where exists
(
select 1 from product p1 where p1.product=c.product and p1.category like N'%fruit%'
)
and exists
(
select 1 from product p2 where p2.product=c.product and p2.category like N'%vegetable%'
)
You can use ANY and ALL operators combined with outer joins. A simple sample on a M:N relation:
select p.name
from products p
where id_product = ALL -- all operator
( select pc.id_product
from categories c
left outer join product_category pc on pc.id_product = p.id_product and
pc.id_category = c.id_category
)
I think you can figure out the column names
select c.id
from cart c
join product p
on c.pID = p.ID
group by c.id
having count(distinct p.catID) = (select count(distinct p.catID) from product)
Generic approach that possibly isn't the most efficient:
with data as (
select *,
count(case when <match condition> then 1 end)
over (partition by cartid) as matches
from <cart inner join products ...>
)
select * from data
where matches > 0;

SQL query not returning Null-value records

I am using SQL Server 2014 on a Windows 10 PC. I am sending SQL queries directly into Swiftpage’s Act! CRM system (via Topline Dash). I am trying to figure out how to get the query to give me records even when some of the records have certain Null values in the Opportunity_Name field.
I am using a series of Join statements in the query to connect 4 tables: History, Contacts, Opportunity, and Groups. History is positioned at the “center” of it all. They all have many-to-many relationships with each other, and are thus each linked by an intermediate table that sits “between” the main tables, like so:
History – Group_History – Group
History – Contact_History – Contact
History – Opportunity_History – Opportunity
The intermediate tables consist only of the PKs in each of the main tables. E.g. History_Group is only a listing of HistoryIDs and GroupIDs. Thus, any given History entry can have multiple Groups, and each Group has many Histories associated with it.
Here’s what the whole SQL statement looks like:
SELECT Group_Name, Opportunity_Name, Start_Date_Time, Contact.Contact, Contact.Company, History_Type, (SQRT(SQUARE(Duration))/60) AS Hours, Regarding, HistoryID
FROM HISTORY
JOIN Group_History
ON Group_History.HistoryID = History.HistoryID
JOIN "Group"
ON Group_History.GroupID = "Group".GroupID
JOIN Contact_History
ON Contact_History.HistoryID = History.HistoryID
JOIN Contact
ON Contact_History.ContactID = Contact.ContactID
JOIN Opportunity_History
ON Opportunity_History.HistoryID = History.HistoryID
JOIN Opportunity
ON Opportunity_History.OpportunityID = Opportunity.OpportunityID
WHERE
( Start_Date_Time >= ('2018/02/02') AND
Start_Date_Time <= ('2018/02/16') )
ORDER BY Group_NAME, START_DATE_TIME;
The problem is that when the Opportunity table is linked in, any record that has no Opportunity (i.e. a Null value) won’t show up. If you remove the Opportunity references in the Join statement, the listing will show all history events in the Date range just fine, the way I want it, whether or not they have an Opportunity associated with them.
I tried adding the following to the WHERE part of the statement, and it did not work.
AND ( ISNULL(Opportunity_Name, 'x') = 'x' OR
ISNULL(Opportunity_Name, 'x') <> 'x' )
I also tried changing the Opportunity_Name reference up in the SELECT part of the statement to read: ISNULL(Opportunity_Name, 'x') – this didn’t work either.
Can anyone suggest a way to get the listing to contain all records regardless of whether they have a Null value in the Opportunity Name or not? Many thanks!!!
I believe this is because a default JOIN statement discards unmatched rows from both tables. You can fix this by using LEFT JOIN.
Example:
CREATE TABLE dataframe (
A int,
B int
);
insert into dataframe (A,B) values
(1, null),
(null, 1)
select a.A from dataframe a
join dataframe b ON a.A = b.A
select a.A from dataframe a
left join dataframe b ON a.A = b.A
You can see that the first query returns only 1 record, while the second returns both.
SELECT Group_Name, Opportunity_Name, Start_Date_Time, Contact.Contact, Contact.Company, History_Type, (SQRT(SQUARE(Duration))/60) AS Hours, Regarding, HistoryID
FROM HISTORY
LEFT JOIN Group_History
ON Group_History.HistoryID = History.HistoryID
LEFT JOIN "Group"
ON Group_History.GroupID = "Group".GroupID
LEFT JOIN Contact_History
ON Contact_History.HistoryID = History.HistoryID
LEFT JOIN Contact
ON Contact_History.ContactID = Contact.ContactID
LEFT JOIN Opportunity_History
ON Opportunity_History.HistoryID = History.HistoryID
LEFT JOIN Opportunity
ON Opportunity_History.OpportunityID = Opportunity.OpportunityID
WHERE
( Start_Date_Time >= ('2018/02/02') AND
Start_Date_Time <= ('2018/02/16') )
ORDER BY Group_NAME, START_DATE_TIME;
You will want to make sure you are using a LEFT JOIN with the table Opportunity. This will keep records that do not relate to records in the Opportunity table.
Also, BE CAREFUL you do not filter records using the WHERE clause for the Opportunity table being LEFT JOINED. Include those filter conditions relating to Opportunity instead in the LEFT JOIN ... ON clause.

How to filter for multiple values with multiple JOINs

This is a vague title, so please correct it if you can think of a better one.
Consider these 4 tables:
products: id (int), name, msrp, etc...
subproducts: id (int), product_id (int), name (varchar)
subproducts_properties: id (int), subproduct_id (int), property_id (int)
subproducts_properties_values: subproducts_properties_id (int), value (varchar)
So the basic idea here is that a single product can have multiple subproducts (models), a single subproduct can have multiple properties (or specs), and a single property for a subproduct can have multiple values.
Now imagine that there is a product that has multiple subproducts which have multiple properties which have multiple values. In particular, this product has a subproduct that has these properties:
Property 1 - property_id: 1; value = '.17 HMR';
Property 2 - property_id: 22; value = 'Bolt';
Where property_id 1 has a name Caliber and property_id 2 has a name Action (think: guns).
What this product doesn't have is a subproduct containing a property with property_id=1 and value='5.56 mm NATO'.
The user has drop-down boxes where he can select multiple filter sets based on unique values. So if a user selects a Caliber of .17 HMR and an Action of Bolt, he should expect to see our product, but when he pulls back Bolt and a Caliber of, say, 5.56 mm NATO, he should see no products because our product doesn't match both filters.
So...given this information, I would like to pull back all products (one product per row) in the database and filter by multiple property values. My current attempt goes like this:
SELECT p.*, m.name as manufacturer_name, pt.name as product_type_name, COUNT(DISTINCT com.id) AS num_reviews, ROUND(AVG(com.rating), 1) as rating, pi.image_thumb
FROM products p
LEFT JOIN manufacturers m ON p.manufacturer_id=m.id
LEFT JOIN product_types pt ON p.product_type_id=pt.id
LEFT JOIN comments com ON p.id=com.object_id AND com.object_group = 'com_products' AND com.level=0
LEFT JOIN (
SELECT product_id, thumb_path as image_thumb
FROM products_images pi
ORDER BY ordering ASC
) AS pi ON p.id=pi.product_id
LEFT JOIN subproducts sp ON p.id=sp.product_id
LEFT JOIN subproducts_properties spp ON sp.id=spp.subproduct_id
LEFT JOIN subproducts_properties_values sppv ON spp.id=sppv.sp_id
WHERE p.deleted != 1 AND p.published=1
AND (
IF(spp.property_id=1, IF(sppv.value='5.56 mm NATO',1,0), 0) = 1
OR IF(spp.property_id=22, IF(sppv.value='Bolt',1,0), 0) = 1
)
GROUP BY p.id
ORDER BY p.created DESC
LIMIT 0, 12
The part to focus on is the last AND in the WHERE clause where I attempt to get the filters going. Notice, too, that I have a GROUP BY in order to be able to perform aggregate functions on other tables. This particular query will pull back our product because of the OR inside that last AND, but I would like to set it up so it doesn't pull it back in this case, but does pull it back if instead of sppv.value='5.56 mm NATO' there is sppv.value='.17 HMR' (which is a value for our subproduct).
I've tried putting an AND in there instead, but it doesn't return anything because each value has its own row in the sppv table.
Please help! I'm totally lost for what to do here.
Thanks in advance!
try a Having - Count - If instead of the where:
WHERE p.deleted != 1 AND p.published=1
GROUP BY p.id
HAVING COUNT(IF(spp.property_id = 1 AND sppv.value='5.56 mm NATO',1,NULL)) > 0
AND COUNT(IF(spp.property_id=22 AND sppv.value='Bolt',1,NULL)) > 0
ORDER BY p.created DESC