Select Customers where not have value in other table with join - sql

I need to select all Customers from the table Customer where Value in table Customer_Value is not 4.
Customers:
+------------+-------+
| Customer | ... |
+------------+-------+
| 312 | ... |
| 345 | ... |
| 678 | ... |
+------------+-------+
Customer_Value:
+------------+-------+
| Customer | Value |
+------------+-------+
| 312 | 1 |
| 312 | 2 |
| 345 | 1 |
| 345 | 2 |
| 345 | 3 |
| 678 | 1 |
| 678 | 2 |
| 678 | 4 |
+------------+-------+
To get my result I've used the following query:
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer)
WHERE C.Customer NOT IN (SELECT Customer FROM [Customer_Value] WHERE Value = '4')
GROUP BY C.Customer
So my question is:
Is that a fast and good query? Or are there some other better solutions to get all the Customer Ids?

You can avoid Negative condition using Left Join and IS NULL Filter in where Condition.
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer) and V.Value = '4'
WHERE V.Value is null
GROUP BY C.Customer

Your method is overkill; the JOIN is not necessary. I would use not exists:
select c.Customer
from Customer c
where not exists (select 1
from customer_value cv
where c.Customer = v.Customer and
cv.value = 4
);
You can also use aggregation, if you assume that all customers have at least one row in customer_value:
select cv.customer
from customer_value cv
group by cv.customer
having sum(case when cv.value = 4 then 1 else 0 end) = 0;

I would do
select * from customer c
join (
select distinct customer from customer_value where value!=4) v on c.customer = v.customer

Related

Getting an empty result with 'AND' operation and wrong result with 'OR' operation in SQL

I have two tables and I want to find out the customer_id and customer_name of all customers who bought product A and B both.
Customers table:
+-------------+---------------+
| customer_id | customer_name |
+-------------+---------------+
| 1 | Daniel |
| 2 | Diana |
| 3 | Elizabeth |
| 4 | Jhon |
+-------------+---------------+
Orders table:
+------------+--------------+---------------+
| order_id | customer_id | product_name |
+------------+--------------+---------------+
| 10 | 1 | A |
| 20 | 1 | B |
| 30 | 1 | D |
| 40 | 1 | C |
| 50 | 2 | A |
| 60 | 3 | A |
| 70 | 3 | B |
| 80 | 3 | D |
| 90 | 4 | C |
+------------+--------------+---------------+
In this example only the customers with id 1 and 3 have bought both the product A and B.
To find that i wrote this code -
SELECT distinct c.customer_id,
c.customer_name
from customers c inner join orders o
on c.customer_id = o.customer_id
where o.product_name = 'A' and o.product_name = 'B'
When I am doing this I am getting an empty result.
So tried to use OR -
SELECT distinct c.customer_id,
c.customer_name
from customers c inner join orders o
on c.customer_id = o.customer_id
where o.product_name = 'A' or o.product_name = 'B'
output -
customer_name customer_id
Daniel 1
Diana 2
Elizabeth 3
Based on OR it is working right but I am still not getting the result I am trying to find. Because customer with id 2 only bought A and not Product B. And Using AND bringing me an empty result.
I always feel confused with AND and OR operations. can someone help?
If you want both use aggregation:
select c.customer_id, c.customer_name
from customers c inner join
orders o
on c.customer_id = o.customer_id
where o.product_name in ('A', 'B')
group by c.customer_id, c.customer_name
having count(distinct product_name) = 2;
Note: This assumes that the data could have multiple rows for a customer and product. If that is not possible, just use count(*) = 2 for performance reasons.

Postgres group by columns and within group select other columns by max aggregate

This is probably a standard problem, and I've keyed off some other greatest-n-per-group answers, but so far been unable to resolve my current problem.
A B C
+----+-------+ +----+------+ +----+------+-------+
| id | start | | id | a_id | | id | b_id | name |
+----+-------+ +----+------+ +----+------+-------+
| 1 | 1 | | 1 | 1 | | 1 | 1 | aname |
| 2 | 2 | | 2 | 1 | | 2 | 2 | aname |
+----+-------+ | 3 | 2 | | 3 | 3 | aname |
+----+------+ | 4 | 3 | bname |
+----+------+-------+
In English what I'd like to accomplish is:
For each c.name, select its newest entry based on the start time in a.start
The SQL I've tried is the following:
SELECT a.id, a.start, c.id, c.name
FROM a
INNER JOIN (
SELECT id, MAX(start) as start
FROM a
GROUP BY id
) a2 ON a.id = a2.id AND a.start = a2.start
JOIN b
ON a.id = b.a_id
JOIN c
on b.id = c.b_id
GROUP BY c.name;
It fails with errors such as:
ERROR: column "a.id" must appear in the GROUP BY clause or be used in an aggregate function Position: 8
To be useful I really need the ids from the query, but cannot group on them since they are unique. Here is an example of output I'd love for the first case above:
+------+---------+------+--------+
| a.id | a.start | c.id | c.name |
+------+---------+------+--------+
| 2 | 2 | 3 | aname |
| 2 | 2 | 4 | bname |
+------+---------+------+--------+
Here is a Sqlfiddle
Edit - removed second case
Case 1
select distinct on (c.name)
a.id, a.start, c.id, c.name
from
a
inner join
b on a.id = b.a_id
inner join
c on b.id = c.b_id
order by c.name, a.start desc
;
id | start | id | name
----+-------+----+-------
2 | 2 | 3 | aname
2 | 2 | 4 | bname
Case 2
select distinct on (c.name)
a.id, a.start, c.id, c.name
from
a
inner join
b on a.id = b.a_id
inner join
c on b.id = c.b_id
where
b.a_id in (
select a_id
from b
group by a_id
having count(*) > 1
)
order by c.name, a.start desc
;
id | start | id | name
----+-------+----+-------
1 | 1 | 1 | aname

Choose column based on max() of another column

Given the data below from the two tables cases and acct_transaction, how can I include just the acct_transaction.create_date of the largest acct_transaction amount whilst also calculating the sum of all amounts and the value of the largest amount? Platform is t-sql.
id amount create_date
---|----------|------------|
1 | 1.99 | 01/09/2009 |
1 | 2.99 | 01/13/2009 |
1 | 578.23 | 11/03/2007 |
1 | 64.57 | 03/03/2008 |
1 | 3.99 | 12/12/2012 |
1 | 31337.00 | 04/18/2009 |
1 | 123.45 | 05/12/2008 |
1 | 987.65 | 10/10/2010 |
Result set should look like this:
id amount create_date sum max_amount max_amount_date
---|----------|------------|----------|-----------|-----------
1 | 1.99 | 01/09/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 2.99 | 01/13/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 578.23 | 11/03/2007 | 33099.87 | 31337.00 | 04/18/2009
1 | 64.57 | 03/03/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 3.99 | 12/12/2012 | 33099.87 | 31337.00 | 04/18/2009
1 | 31337.00 | 04/18/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 123.45 | 05/12/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 987.65 | 10/10/2010 | 33099.87 | 31337.00 | 04/18/2009
This is what I have so far, I just don't know how to pull the date of the largest acct_transaction amount for max_amount_date column.
SELECT cases.id, acct_transaction.amount, acct_transaction.create_date AS 'create_date', SUM(acct_transaction.amount) OVER () AS 'sum', MIN(acct_transaction.amount) OVER () AS 'max_amount'
FROM cases INNER JOIN
acct_transaction ON cases.id = acct_transaction.id
WHERE (cases.id = '1')
;WITH x AS
(
SELECT c.id, t.amount, t.create_date,
s = SUM(t.amount) OVER(),
m = MAX(t.amount) OVER(),
rn = ROW_NUMBER() OVER(ORDER BY t.amount DESC)
FROM dbo.cases AS c
INNER JOIN dbo.acct_transaction AS t
ON c.id = t.id
)
SELECT x.id, x.amount, x.create_date,
[sum] = y.s,
max_amount = y.m,
max_amount_date = y.create_date
FROM x CROSS JOIN x AS y WHERE y.rn = 1;
You can just do a full outer join to the table which defines the aggregates:
select id, amount, create_date, x.sum, x.max_amount, x.max_amount_date
from table1
full outer join
(select sum(amount) as sum, max(amount) as max_amount,
(select top 1 create_date from table1 where amount = (select max(amount) from table1)) as max_amount_date
from table1) x
on 1 = 1
SQL Fiddle demo
Try this abomination of a query... I make no claims for its speed or elegance. It's likely I should pray that Cod have mercy on my soul.
Here is the out put of a join on the two tables that you mention but for which you do not provide schemas.
[SQL Fiddle][1]
SELECT A.case_id
,A.trans_id
,A.trans_amount
,A.trans_create_date
,A.trans_type
,B.max_amount
,B.max_amount_date
,E.sum_amount
FROM acct_transaction AS A
INNER JOIN (select C.case_id
,MAX(C.trans_amount) AS max_amount
,C.trans_create_date AS max_amount_date
FROM acct_transaction AS C group by C.case_id, C.trans_create_date ) AS B ON B.case_id = A.case_id
inner JOIN (select D.case_id, SUM(D.trans_amount) AS sum_amount FROM acct_transaction AS D GROUP BY D.case_id) AS E on E.case_id = A.case_id
WHERE (A.case_id = '1') AND (A.trans_type = 'F')
GROUP BY A.case_id
Thanks, that got me on the right track to this which is working:
,CAST((SELECT TOP 1 t2.create_date from acct_transaction t2
WHERE t2.case_sk = act.case_sk AND (t2.trans_type = 'F')
order by t2.amount, t2.create_date DESC) AS date) AS 'max_date'
It won't let me upvote because I have less than 15 rep :(

Filter Left join sql

I have this sql query :
SELECT Customer.IDCustomer, Customer.Name,
Sign.IdSign, Sign.Name, Sign.Delete
FROM Customer
LEFT JOIN Sign_Customer ON Sign_Customer.IDCustomer=Customer.IDCustomer
AND ( SELECT CAST(CASE WHEN S_C.Delete=0 OR S_C.Delete is Null THEN 1 ELSE 0 END AS BIT )
FROM Sign AS S_C
WHERE S_C.IdSign=Sign_Customer.IdSign)=1
LEFT JOIN Sign ON Sign.IdSign=Sign_Customer.IdSign
ORDER BY Customer.Name
This query works fine, but I want to know if it exists another way to filter my first left join without use the SELECT CAST... condition.
Update
Sorry, I don't explain what I want in my result:
All my customer without Sign
All my customer whith enable Sign
A Customer with disable Sign doesn't appear or appear like a "without Sign" if no enable Sign exist for it.
Exemple:
my customer table
IDCustomer | Name
1 | Customer 1
2 | Customer 2
3 | Customer 3
4 | Customer 4
Sign_Customer:
IDCustomer | IdSign
1 | 1
3 | 2
3 | 3
3 | 5
4 | 4
Sign
IdSign | Name | Delete
1 | Sign1 | 0
2 | Sign2 | 1
3 | Sign3 | 0
4 | Sign4 | 1
5 | Sign5 | 0
Result
Customer.IDCustomer | Customer.Name | Sign.IdSign | Sign.Name | Sign.Delete
1 | Customer 1 | 1 | Sign1 | 0
2 | Customer 2 | null | null | null
3 | Customer 3 | 3 | Sign3 | 0
3 | Customer 3 | 5 | Sign5 | 0
4 | Customer 4 | null | null | null
How about the following?
SELECT Customer.IDCustomer
, Customer.Name
, Sign.IdSign
, Sign.Name
, Sign.Delete
FROM Customer
LEFT JOIN Sign_Customer
JOIN Sign
ON Sign_Customer.IdSign = Sign.IdSign
AND Sign.Delete = 0
ON Customer.IDCustomer = Sign_Customer.IDCustomer
ORDER BY Customer.NAME;
SELECT Customer.IDCustomer, Customer.Name, Sign.IdSign, Sign.LibEnseigne, Sign.Delete
FROM Customer
LEFT JOIN Sign_Customer ON Sign_Customer.IDCustomer=Customer.IDCustomer
LEFT JOIN Sign ON Sign.IdSign=Sign_Customer.IdSign
AND (S_C.Delete=0 OR S_C.Delete is Null)
ORDER BY TEnseigne.NomEnseigne, Customer.Name
I believe this meets your criteria:
SELECT Customer.IDCustomer, Customer.Name,
Sign.IdSign, Sign.Name, Sign.Delete
FROM Customer
LEFT JOIN (
select Sign_Customer.*
from Sign_Customer
join Sign on S_C.IdSign=Sign_Customer.IdSign
where isnull(S_C.Delete,0) = 0
) Sign_Customer ON Sign_Customer.IDCustomer=Customer.IDCustomer
LEFT JOIN Sign ON Sign.IdSign=Sign_Customer.IdSign
ORDER BY Customer.Name

Joining 6 tables into single query?

Hey can anyone help me join the 5 tables below into a single query? I currently have the query below but is doesn't seem to work as if there are two products with the same ID inside the hires table all of the products are returned form the products table which is obviously wrong.
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Table data:
PRODUCTS
--------------------------------------------
| Prod_ID | Title | Price | Type_ID |
|------------------------------------------|
| 1 | A | 5 | 1 |
| 2 | B | 7 | 1 |
| 3 | C | 3 | 2 |
| 4 | D | 3 | 3 |
|------------------------------------------|
PRODUCT_TYPES
----------------------
| Type_ID | Type |
|--------------------|
| 1 | TYPE_A |
| 2 | TYPE_B |
| 3 | TYPE_C |
| 4 | TYPE_D |
|--------------------|
PRODUCT_SUPPLIERS
-------------------------
| Prod_ID | Supp_ID |
|-----------------------|
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
|-----------------------|
SUPPLIERS
----------------------
| Supp_ID | Name |
|--------------------|
| 1 | SUPP_A |
| 2 | SUPP_B |
| 3 | SUPP_C |
| 4 | SUPP_D |
|--------------------|
HIRES
---------------------------------------------------------------
| Hire_ID | Prod_ID | Cust_ID | Hire_Start | Hire_End |
|-----------------------|------------|------------------------|
| 1 | 1 | 1 | 22-Jan-13 | 23-Jan-13 |
| 2 | 2 | 2 | 27-Jan-13 | 29-Jan-13 |
| 3 | 1 | 3 | 30-Jan-13 | 31-Jan-13 |
|-----------------------|------------|------------|-----------|
PRODUCTS
--------------------------------
| Cust_ID | Name | Phone |
|------------------------------|
| 1 | Cust_A | 555-666 |
| 2 | Cust_B | 444-234 |
| 3 | Cust_C | 319-234 |
| 4 | Cust_D | 398-092 |
|------------------------------|
The output from the query at the moment looks like this:
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 1 | A | 5 | Type_A | SUPP_A,SUPP_B |
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
When it should look like this surely? as Prod_ID '1' is hired out between the dates in the query
-------------------------------------------------------------
| Prod_ID | Title | Price | Type_ID | Suppliers |
|------------------------------------------|----------------|
| 2 | B | 7 | Type_B | SUPP_B |
| 3 | C | 3 | Type_C | SUPP_C |
| 4 | D | 3 | Type_D | SUPP_D |
|------------------------------------------|----------------|
If anyone can help modify the query to output as suggested i would be really grateful. Because my understanding is that it should work as written?
Your issue is that Prod_Id 1 is both in and out of those date ranges. So instead, use a subquery to filter out which Prod_Id are in those ranges, and exclude those.
This is a much simplified version of your query:
SELECT P.Prod_ID
FROM Products P
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON P.Prod_ID = H.Prod_ID
WHERE h.prod_id IS NULL
And the SQL Fiddle.
Assuming I copied and pasted correctly, this should be your query:
SELECT products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT JOIN (
SELECT Prod_ID
FROM Hires
WHERE hire_end >= To_Date('20130121', 'yyyymmdd') AND hire_start <= To_Date('20130126', 'yyyymmdd')
) H ON products.Prod_ID = H.Prod_ID
WHERE H.Prod_ID IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
Hope this helps.
Your left outer join will return null values when there is no match, meaning you still have a row (with no HIRE table data) when the results of this join query are Null:
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13')
OR hires.hire_start > to_date('26-JAN-13'))
OR hires.prod_id IS NULL
Try adding a select from the hires table (eg. hire.Hire_Start) to see this happening, then switch it to an inner join as well and I think your problem will be solved.
OR add a WHERE clause on the full query with something like hire.Hire_Start is not null
EDIT
If you change your original query to:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
GROUP BY products.prod_id, products.title, products.price, product_types.name
What comes back in the Hire_Start column?
Then if you add it to the where clause do you get the expected result:
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
LEFT OUTER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
OR hires.prod_id IS NULL
WHERE hires.Hire_Start is not null
GROUP BY products.prod_id, products.title, products.price, product_types.name
Finally, dropping the Outer Join altogether, does this work as expected?
SELECT hires.Hire_Start, products.prod_id, products.title, products.price, product_types.name,
listagg(suppliers.name, ',') WITHIN GROUP(ORDER BY suppliers.name) suppliers
FROM products
INNER JOIN product_suppliers ON products.prod_id = product_suppluer.prod_id
INNER JOIN product_types ON product_types.type_id = products.type_id
INNER JOIN suppliers ON product_suppliers.supp_id = suppliers.supp_id
INNER JOIN hires ON hires.prod_id = products.prod_id
WHERE (hires.hire_end < to_date('21-JAN-13') OR hires.hire_start > to_date('26- JAN-13'))
GROUP BY products.prod_id, products.title, products.price, product_types.name
And note: is the OR Hires.prod_ID sopposed to indicate that if the result returns no hire information it is available, in which case you need to write the query more like the other answer provided.
Here is some code that may help you:
SELECT L.V_PRODUCT_ID "PROD_ID" , L.TITLE "TITLE" , L.PRICE "PRICE" , L.TYPE "TYPE" , S.NAME "SUPPLIERS"
FROM
(SELECT V_PRODUCT_ID , TITLE , PRICE , TYPE , SUPPLIER_ID FROM
((select p.prod_id v_product_id , p.title TITLE , p.price PRICE , t.type TYPE
from products p , products_types t
where p.type_id = t_type_id) A
JOIN
(SELECT PROD_ID VV_PRODUCT_ID , SUPP_ID SUPPLIER_ID
FROM PRODUCTS_SUPPLIERS) H
ON (A.V_PRODUCT_ID = H.VV_PRODUCT_ID))) L
JOIN
SUPLLIERS S
ON (L.SUPPLIER_ID = S.SUPP_ID);
SELECT Emp.Empid, Emp.EmpFirstName, Emp.EmpLastName, Dept.DepartmentName
FROM Employee Emp
INNER JOIN Department dept
ON Emp.Departmentid=Dept.Departmenttid