How can I exclude users who have only purchase with car? - sql

There is this SQL(from Django) query:
SELECT "id", "name"
FROM "polls_client" INNER JOIN "polls_purchases"
ON ("id" = "client_id")
WHERE "polls_purchases"."product" IN (car, bike)
We need to select from query users who have purchase records only 'car'. I want to do this in one select to the database. How do I do this?

You can group by client and set the condition in the HAVING clause:
SELECT pc.id, pc.name
FROM polls_client pc INNER JOIN polls_purchases pp
ON pc.id = pp.client_id
GROUP BY pc.id, pc.name
HAVING SUM(CASE WHEN pp.product <> 'car' THEN 1 ELSE 0 END) = 0

We need to select from query users who have purchase records only 'car'.
The simplest, most efficient method should be not exists:
SELECT c.*
FROM "polls_client" c
WHERE NOT EXISTS (SELECT 1
FROM "polls_purchases" pp
WHERE c."id" = pp."client_id" AND pp."product" <> 'car'
);
In particular, this can take advantage of an index on polls_purchases(client_id, product).
I would also dissuade your from using double quotes for identifies. They only serve to clutter queries.

Related

SQL ACCESS (need ideas and help about a query)

I am new at access SQL and i need help with some query. What i want is to find those customers that prefer a car (manufacturer and model) from prefer_to_buy AND prefer_to_rent that no one else prefer.
For example if 2 customers prefer toyota aygo must not be in the result table.
customer(customer_id,name)
prefer_to_buy(customer_id,manufacturer,model)
prefer_to_rent(customer_id, manufacturer,model)
I have tried a lot of ways including exists and i know there must be about 2-3 subqueries but i cant get it to work, any ideas?
Your problem definition is very vague, so the answer is also sort of generic. You should try to create a Left Outer Join on customer table and, for example, a "prefer_to_buy" Table using customer_id as a join field, and include:
customer_id,name from the left table and manufacturer,model from the right table. The same logic applies to prefer_to_rent Table: you can actually combine these 3 Tables in a single Access SQL query using the aforementioned Outer Joins.
Hope this may help. Best regards,enter code here
There are a couple of parts to cover here. First, you could use the union operator to treat prefer_to_buy and prefer_to_rent as a single table (possibly with an additional literal "column" to indicate preference type). Once you've done this, you could use the exists operator to make sure no other customers prefer this car:
SELECT c.name, p.manufacturer, p.model
FROM customer c
JOIN (SELECT customer_id, manufacturer, model
FROM prefer_to_buy
UNION
SELECT customer_id, manufacturer, model
FROM prefer_to_buy) p ON c.customer_id = p.customer_id
WHERE NOT EXISTS (SELECT *
FROM prefer_to_buy pb
WHERE c.customer_id != pb.customer_id AND
p.manufacturer = pb.manufacturer AND
p.model = pb.model) AND
NOT EXISTS (SELECT *
FROM prefer_to_rent pr
WHERE c.customer_id != pr.customer_id AND
p.manufacturer = pr.manufacturer AND
p.model = pr.model)
First you need to do to two joins one on prefer_to_buy AND one on prefer_to_rent.
Next, you need to check if any one else would like the same manufacturer and model. Do this with a exist
SELECT *
FROM customer AS c
JOIN prefer_to_buy AS pb ON c.customer_id = pb.customer_id
JOIN prefer_rent AS pr ON c.customer_id = pr.customer_id
WHERE NOT EXISTS ( SELECT 'x' FROM prefer_to_buy AS pb1 WHERE pb.manufacturer = pb1.manufacturer AND pb.model = pb1.model AND pb.customer_id <> pb1.customer_id)
AND NOT EXISTS ( SELECT 'x' FROM prefer_to_rent AS pr1 WHERE pb.manufacturer = pr1.manufacturer AND pb.model = pr1.model AND pb.customer_id <> pr1.customer_id)

How to return rows matched in a table without multiple EXISTS clauses?

I want to pull back results from one table that match ALL specified values where the specified values are in another table. I can do it like this:
SELECT * FROM Contacts
WHERE
EXISTS (SELECT 1 FROM dbo.ContactClassifications WHERE ContactID = Contacts.ID AND ClassificationID = '8C62E5DE-00FC-4994-8127-000B02E10DA5')
AND EXISTS (SELECT 1 FROM dbo.ContactClassifications WHERE ContactID = Contacts.ID AND ClassificationID = 'D2E90AA0-AC93-4406-AF93-0020009A34BA')
AND EXISTS etc...
However that falls over when I get up to about 40 EXISTS clauses. The error message is "The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query."
The gist of this is to
Select all contacts with any GUID from the IN statement
Use a DISTINCT COUNT to get a count for each contactid on matching GUID's
Use the HAVING to retain only those contacts that equal the amount of matching GUID's you've put into the IN statement
SQL Statement
SELECT *
FROM dbo.Contacts c
INNER JOIN (
SELECT c.ID
FROM dbo.Contacts c
INNER JOIN dbo.ContactClassifications cc ON c.ID = cc.ContactID
WHERE cc.ClassificationID IN ('..', '..', 38 other GUIDS)
GROUP BY
c.ID
HAVING COUNT(DISTINCT cc.ClassificationID) = 40
) cc ON cc.ID = c.ID
Test script at data.stackexchange
One solution is to demand that no classification exists without a matching contact. That's a double negation:
select *
from contacts c
where not exists
(
select *
from ContactClassifications cc
where not exists
(
select *
from ContactClassifications cc2
where cc2.ContactID = c.ID
and cc2.ClassificationID = cc.ClassificationID
)
)
This type of problem is known as relational division.
SELECT c.*
FROM Contacts c
INNER JOIN
(cc.ContactID, COUNT(DISTINCT cc.ClassificationID) as num_class
FROM ContactClassifications
WHERE ClassificationID IN (....)
GROUP BY cc.ContactID
) b ON c.ID = b.ContactID
WHERE b.num_class = [number of distinct values - how many different values you put in "IN"]
If you run SQLServer 2005 and higher, you can do pretty much the same with CROSS APPLY, supposedly more efficiently

comparison query taking ages

My query is quite simple:
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a, COMPANIES b
where a.Postcode = b.Postcode
and a.Adres = b.Adres
and (
select COUNT(COMPANYID)
from USERS
where COMPANYID=a.ID
)>(
select COUNT(COMPANYID)
from USERS
where COMPANYID=b.ID
)
Database: sql server 2008 r2
What I'm trying to do:
The table of COMPANIES contains double entries. I want to know the ones that are connected to the most amount of users. So I only have to change the foreign keys of those with the least. ( I already know the id's of the doubles)
Right now it's taking a lot of time to complete. I was wondering if if could be done faster
Try this version. It should be only a little faster. The COUNT is quite slow. I've added a.ID <> b.ID to avoid few cases earlier.
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a INNER JOIN COMPANIES b
ON
a.ID <> b.ID
and a.Postcode = b.Postcode
and a.Adres = b.Adres
and (
select COUNT(COMPANYID)
from USERS
where COMPANYID=a.ID
)>(
select COUNT(COMPANYID)
from USERS
where COMPANYID=b.ID
)
The FROM ... INNER JOIN ... ON ... is a preferred SQL construct to join tables. It may be faster too.
One approach would be to pre-calculate the COMPANYID count before doing the join since you'll be repeatedly calculating it in the main query. i.e. something like:
insert into #CompanyCount (ID, IDCount)
select COMPANYID, COUNT(COMPANYID)
from USERS
group by COMPANYID
Then your main query:
select a.ID, a.adres, a.place, a.postalcode
from COMPANIES a
inner join #CompanyCount aCount on aCount.ID = a.ID
inner join COMPANIES b on b.Postcode = a.Postcode and b.Adres = a.Adres
inner join #CompanyCount bCount on bCount.ID = b.ID and aCount.IDCount > bCount.IDCount
If you want all instances of a even though there is no corresponding b then you'd need to have left outer joins to b and bCount.
However you need to look at the query plan - which indexes are you using - you probably want to have them on the IDs and the Postcode and Adres fields as a minimum since you're joining on them.
Build an index on postcode and adres
The database probably executes the subselects for every row. (Just guessing here, veryfy it in the explain plan. If this is the case you can rewrite the query to join with the inline views (note this is how it would look in oracle hop it works in sql server as well):
select distinct a.ID, a.adres, a.place, a.postalcode
from
COMPANIES a,
COMPANIES b,
(
select COUNT(COMPANYID) cnt, companyid
from USERS
group by companyid) cntA,
(
select COUNT(COMPANYID) cnt, companyid
from USERS
group by companyid) cntb
where a.Postcode = b.Postcode
and a.Adres = b.Adres
and a.ID<>b.ID
and cnta.cnt>cntb.cnt

Joining to the same table in SQL - SQL Server 2008

I have the following table:
ID Type Description IDOfSystem
--------------------------------
1000 Company Company Item NULL
1010 System System Item NULL
1020 Company NULL 1010
I have System and Company Items. I need to write a select query that gets all the company items and system items UNLESS if a company item has a value in IDOfSystem I need to exclude that system item and get the description from the system item.
So, given the above table, the SQL select should return rows 1000, 1020 (with "System Item") as the description.
If 1020 didn't exist, I'd simply get 1000 and 1010.
I guess I can break this up into multiple queries and do a UNION. I tried to do a left outer join on the same table but couldn't get the description from the system row.
Any help?
SELECT ID, Type, Description
FROM MyTable AS A
WHERE IDOfSystem IS NULL AND NOT EXISTS (SELECT *
FROM MyTable AS B
WHERE B.IDOfSystem = A.ID)
UNION ALL
SELECT A.ID, A.Type, B.Description
FROM MyTable AS A INNER JOIN MyTable AS B ON A.IDOfSystem = B.ID
WHERE IDOfSystem IS NOT NULL
What I'm doing is first selecting all rows that don't have a referenced system, and aren't used as some other rows system.
Then I'm doing a union with another query that finds all rows with a referenced system, and joining in the system to grab it's description.
SELECT
Companies.ID
,Companies.Type
,COALESCE(Systems.Description, Companies.Description) as Description
FROM YourTable Companies
LEFT OUTER JOIN YourTable Systems on Systems.ID = Companies.IDOfSystem
WHERE NOT EXISTS
(
SELECT * FROM YourTable T3 WHERE T3.IDOfSystem = Companies.ID
)
Here it is running on SEDE.
This approach uses a self join to look up the corresponding system's description. A separate subquery filters out the referenced systems.
select yt1.Id
, yt1.Type
, coalesce(yt2.Description, yt1.Description) as Description
from YourTable yt1
left join
YourTable yt2
on yt1.type = 'Company'
and yt2.type = 'System'
and yt2.ID = yt1.IDOfSystem
where yt1.type in ('System', 'Company')
and not exists
(
select *
from YourTable yt3
where yt1.type = 'System'
and yt3.type = 'Company'
and yt1.ID = yt3.IDOfSystem
)
Working example at SE Data.
I'm sure there are better ways of doing this, but try:
SELECT A.Id, A.Type, ISNULL(A.Description,B.Description) Description, A.IDOfsystem
FROM YourTable A
LEFT JOIN YourTable B
ON A.IDOFSystem = B.ID
WHERE A.ID NOT IN (SELECT IDOfsystem FROM YourTable WHERE IDOfsystem IS NOT NULL)

Sub-Query Problem

I have two tables like this
Member and their Purchases
I need the output like this
Member_ID | CountofProducts
(and the Product Value not should be 0 and Purchase Status = 1)
SELECT
MemberName,
(SELECT COUNT(*) AS Count
FROM dbo.Purchases
WHERE MemberName = dbo.Members.MemberName
AND Res_Status = 1) AS Count
FROM
dbo.Members
I can get their total CountofPurchased Products from the above query but I need to avoid their count=0 how to do it ?
You can try something like
SELECT m.MemberName,
COUNT(p.*) Cnt
FROM Members m INNER JOIN
Purchases p ON m.MemberName = p.MemberName
WHERE p.Res_Status = 1
GROUP BY m.MemberName
There is no need for the HAVING clause, as the INNER JOIN will exclude all entries in Members that do not have Purchases.
SELECT m.MemberName, COUNT(p.*) AS CountOfProducts
FROM Members m
INNER JOIN Purchases p ON m.MemberName = p.MemberName
WHERE p.Res_Status = 1
GROUP BY m.MemberName
HAVING COUNT(p.*) > 0
I think the above will somewhat do what you want. The key is that you probably do not even need your subquery, but rather you can handle the query (possibly with greater efficiency) just using a join.