sql - select all rows that have all multiple same cols - sql

I have a table with 4 columns.
date
store_id
product_id
label_id
and I need to find all store_ids that have all products_id with same label_id (for example 4)in one day.
for example:
store_id | label_id | product_id | data|
4 4 5 9/2
5 4 7 9/2
4 3 12 9/2
4 4 7 9/2
so it should return 4 because it's the only store that contains all possible products with label 4 at one day.
I have tried something like this:
(select store_id, date
from table
where label_id = 4
group by store_id, date
order by date)
I dont know how to write the outer query, I tried:
select * from table
where product_id = all(Inner query)
but it didnt work.
Thanks

It is unclear from your question whether the labels are specific to a given day or through the entire period. But a variation of Tim's answer seems appropriate. For any label:
SELECT t.date, t.label, t.store_id
FROM t
GROUP BY t.date, t.label, t.store_id
HAVING COUNT(DISTINCT t.product_id) = (SELECT COUNT(DISTINCT t2product_id)
FROM t t2
WHERE t2.label = t.label
);
For a particular label:
SELECT t.date, t.store_id
FROM t
WHERE t.label = 4
GROUP BY t.date,t.store_id
HAVING COUNT(DISTINCT t.product_id) = (SELECT COUNT(DISTINCT t2product_id)
FROM t t2
WHERE t2.label = t.label
);
If the labels are specific to the date, then you need that comparison in the outer queries as well.

Here is one way:
SELECT date, store_id
FROM yourTable
GROUP BY date, store_id
HAVING COUNT(DISTINCT product_id) = (SELECT COUNT(DISTINCT product_id)
FROM yourTable t2
WHERE t2.date = t1.date)
ORDER BY date, product_id;
This query reads in a pretty straightforward way, and it says to find every product, on some date, whose distinct product count is the same as the distinct product count on the same day, across all stores.

I'd probably aggregate to lists of products in a string or array:
with products_per_day_and_store as
(
select
store_id,
date,
string_agg(distinct product_id order by product_id) as products
from mytable
where label_id = 4
group by store_id, date
)
, products_per_day
(
select
date,
string_agg(distinct product_id order by product_id) as products
from mytable
where label_id = 4
group by date
)
select distinct ppdas.store_id
from products_per_day_and_store ppdas
join products_per_day ppd using (date, products);

Related

Select multiple max values after GROUP BY query

Suppose I have a table look like this:
date ID income
0 9/1 C 10.40
1 9/3 A 33.90
2 9/3 B 29.10
3 9/4 C 19.30
4 9/4 B 17.80
5 9/5 B 9.55
6 9/5 C 11.10
7 9/5 A 13.10
8 9/7 A 29.10
9 9/7 B 29.10
I want to find out the ID who made the most income for each date. The most intuitive approach would be writing
SELECT ID, MAX(income) FROM table GROUP BY date
But there are two IDs who made the same MAX income on 9/7, I want to retain all ties on the same date, by using that query I will ignore one ID on 9/7, and 29.1 appears on 9/3 and 9/7, any other approach?
A join based approach doesn't have this problem, and would retain all records tied for the max income on a given date.
SELECT t1.*
FROM yourTable t1
INNER JOIN
(
SELECT date, MAX(income) AS max_income
FROM yourTable
GROUP BY date
) t2
ON t1.date = t2.date AND t1.income = t2.max_income
ORDER BY
t1.date;
The way the above query works is to join the complete original table to a subquery which finds, for each date, the maximum income value. This has the effect of filtering off any record which did not have the max income on a given date. Pay close attention to the join condition, which has two components, the date, and the income.
If your database supports analytic function, we can also use RANK here:
SELECT date, ID, income
FROM
(
SELECT t.*, RANK() OVER (PARTITION BY date ORDER BY income DESC) rnk
FROM yourTable t
) t
WHERE rnk = 1
ORDER BY date;
one approach can be like below
with cte1
(
Select t1.*
FROM yourTable t1
INNER JOIN
(
SELECT date, MAX(income) AS max_income
FROM yourTable
GROUP BY date
) t2
ON t1.date = t2.date AND t1.income = t2.max_income
) select min(ID) as ID, date,income from cte1
group by date,income
As you not mentioned which id you need in case of two ID's(when income is same on a particular date) so i took minimum id among them when two id's income is same on a particular date But at the same time you may use max() function also
Try below using subquery and as you've tie for one date so take minimum ID which'll give you one id from date 9/7
select date,min(ID),income
from
(SELECT t1.date, t1.ID,t1.income
FROM tablename t1
INNER JOIN
(
SELECT date, MAX(income) AS mincome
FROM yourTable
GROUP BY date
) t2 ON t1.date = t2.date AND t1.income = t2.mincome
)X group by date,income

SQL Select Group By Min() - but select other

I want to select the ID of the Table Products with the lowest Price Grouped By Product.
ID Product Price
1 123 10
2 123 11
3 234 20
4 234 21
Which by logic would look like this:
SELECT
ID,
Min(Price)
FROM
Products
GROUP BY
Product
But I don't want to select the Price itself, just the ID.
Resulting in
1
3
EDIT: The DBMSes used are Firebird and Filemaker
You didn't specify your DBMS, so this is ANSI standard SQL:
select id
from (
select id,
row_number() over (partition by product order by price) as rn
from orders
) t
where rn = 1
order by id;
If your DBMS doesn't support window functions, you can do that with joining against a derived table:
select o.id
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price;
Note that this will return a slightly different result then the first solution: if the minimum price for a product occurs more then once, all those IDs will be returned. The first solution will only return one of them. If you don't want that, you need to group again in the outer query:
select min(o.id)
from orders o
join (
select product,
min(price) as min_price
from orders
group by product
) t on t.product = o.product and t.min_price = o.price
group by o.product;
SELECT ID
FROM Products as A
where price = ( select Min(Price)
from Products as B
where B.Product = A.Product )
GROUP BY id
This will show the ID, which in this case is 3.

Finding customers that only bought items no one else bought

Below is a list of orders, is there a way to find the person_id of the customers, that has only bought products no one else has bought?
CREATE TABLE orders
AS
SELECT product_id, person_id
FROM ( VALUES
( 1 , 1 ),
( 2 , 1 ),
( 2 , 2 ),
( 3 , 3 ),
( 12, 6 ),
( 10, 3 )
) AS t(product_id, person_id);
The result would be the following table:
| person_id |
|-----------|
| 3 |
| 6 |
Do i have to find all the people who did buy items no one else bought and create a table that doesn't include those people?
You want all the products purchased by the person to be unique.
select person_id
from (select t.*,
min(person_id) over (partition by product_id) as minp,
max(person_id) over (partition by product_id) as maxp
from t
) t
group by person_id
having sum(case when minp <> maxp then 1 else 0 end) = 0;
You are probably thinking "Huh? What does this do?".
The subquery calculates the minimum person and maximum person on each product. If these are the same, than that one person is the only purchaser.
The having then checks that there are no non-single-purchaser products for a given person.
Perhaps a more intuitive phrasing of the logic would be:
select person_id
from (select t.*,
count(distinct person_id) over (partition by product_id) as numpersons
from t
) t
group by person_id
having max(numperson) = 1;
Alas, Postgres doesn't support COUNT(DISTINCT) as a window function.
The traditional self join with boolean aggregation
select o0.person_id
from
orders o0
left join
orders o1 on o0.product_id = o1.product_id and o0.person_id <> o1.person_id
group by o0.person_id
having bool_and(o1.product_id is null)
;
person_id
-----------
3
6
The inline view which is being joined gets all the product_ids which have only one person_id. Once all product_ids are found they will be joined to the original customers table to get the person_ids. This should solve your problem!!
SELECT person_id
FROM customers c1
INNER JOIN
(
SELECT product_id
FROM customers
GROUP BY product_id
HAVING COUNT(person_id ) = 1
) c2
ON c1.product_id = c2.product_id;
This is Gordon's logic using aggregates only:
SELECT person_id
FROM
(
SELECT product_id,
-- if count = 1 it's the only customer who bought this product
min(person_id) as person_id,
-- if the combination(person_id,product_id) is unique DISTINCT can be removed
count(distinct person_id) as cnt
FROM customers
GROUP BY product_id
) AS dt
GROUP BY person_id
HAVING max(cnt) = 1 -- only unique products
Here is another solution:
with unique_products as
(select product_id
from orders
group by product_id
having count(*) = 1)
select person_id
from orders
except
select person_id
from orders
where not exists
(select * from unique_products where unique_products.product_id = orders.product_id)
First all the identifier of products that appear in a single order are found. Then we subtract from all the persons (in the orders) those which do not have a order with a single product (i.e. all the persons that have at least ordered a product ordered by somebody else).

Select first purchase for each customer

We are trying to select the first purchase for each customer in a table similar to this:
transaction_no customer_id operator_id purchase_date
20503 1 5 2012-08-24
20504 1 7 2013-10-15
20505 2 5 2013-09-05
20506 3 7 2010-09-06
20507 3 7 2012-07-30
The expected result from the query that we are trying to achieve is:
transaction_no customer_id operator_id first_occurence
20503 1 5 2012-08-24
20505 2 5 2013-09-05
20506 3 7 2010-09-06
The closest we've got is the following query:
SELECT customer_id, MIN(purchase_date) As first_occurence
FROM Sales_Transactions_Header
GROUP BY customer_id;
With the following result:
customer_id first_occurence
1 2012-08-24
2 2013-09-05
3 2010-09-06
But when we select the rest of the needed fields we obviously have to add them to the GROUP BY clause which will make the result from MIN different. We have also tried to joining it on itself, but haven't made any progress.
How do we get the rest of the correlated values without making the aggregate function confused?
You can simply treat the query you have come up with as an inner query. This will work on older version of SQL Server as well (you didn't specify version of SQL Server).
SELECT H.transaction_no, H.customer_id, H.operator_id, H.purchase_date
FROM Sales_Transactions_Header H
INNER JOIN
(SELECT customer_id, MIN(purchase_date) As first_occurence
FROM Sales_Transactions_Header
GROUP BY customer_id) X
ON H.customer_id = X.customer_id AND H.purchase_date = X.first_occurence
You can use the ROW_NUMBER function to help you with that.
This is how to do it for your case.
WITH Occurences AS
(
SELECT
*,
ROW_NUMBER () OVER (PARTITION BY customer_id order by purchase_date ) AS "Occurence"
FROM Sales_Transactions_Header
)
SELECT
transaction_no,
customer_id,
operator_id,
purchase_date
FROM Occurences
WHERE Occurence = 1
Sounds like a job for a CTE!
Clicky!
The CTE will allow you to get the earliest purchase date for each customer. Then you join that back to your original table on customer_id and the date, getting the rest of the information for that transaction.
Like so:
with first_date as(
select customer_id,
min(purchase_date) as first_purchase
from
table1
group by
customer_id
)
select
t1.transaction_no,
t1.customer_id,
t1.operator_id,
t1.purchase_date
from
table1 t1
inner join first_date
on
purchase_date = first_purchase
and t1.customer_id = first_date.customer_id
Below query will also provide the solution
select * from customer_sale_details
where purchase_date in (select min(purchase_date)
from customer_sale_details c1 group by c1.customer_id);

Join two queries from the same table - SELECT DISTINCT?

I have two tables linked by an AUTO_KEY field, from one table I'm retrieving the number (id), from the other I get several statuses by number(id), each status has a date associated to it.
I need to restrict the results only to the maximum/latest date for all numbers(ids) and the corresponding status
SELECT
OPERATION.NUMBER,
STATUS.STATUS,
Max(STATUS.DATE)
FROM
STATUS,
OPERATION
WHERE
OPERATION.AUTO_KEY = STATUS.AUTO_KEY
From here
Number Status Date
-----------------------------
1 A 10/20/13
1 B 10/15/13
2 A 10/10/13
2 AX 10/05/13
2 AD 10/03/13
3 DD 10/03/13
The outcome should be
Number Status Date
-----------------------------
1 A 10/20/13
2 A 10/10/13
3 DD 10/03/13
Thanks in advance
You can use a CTE with ROW_NUMBER() function. Also Please use a Table JOIN instead FROM STATUS, OPERATION
;With CTE AS (
SELECT O.NUMBER, S.STATUS, S.DATE,
ROW_NUMBER() OVER (ORDER BY S.DATE DESC) RN
FROM STATUS S JOIN OPERATION O
ON O.AUTO_KEY = S.AUTO_KEY
)
SELECT NUMBER, STATUS, DATE
FROM CTE
WHERE RN = 1
ORDER BY NUMBER
SELECT OPERATION.CNUMBER,
STATUS.STATUS,
STATUS.CDATE
FROM STATUS,
OPERATION
WHERE OPERATION.AUTO_KEY = STATUS.AUTO_KEY
AND STATUS.CDATE = (
SELECT MAX(STATUS.CDATE) MAX_DATE
FROM STATUS,
OPERATION
WHERE OPERATION.AUTO_KEY = STATUS.AUTO_KEY
GROUP BY OPERATION.CNUMBER )