only using select in sql instead of group by - sql

I have this table:
supplier | product | qty
--------------------------
s1 | p1 | 300
s1 | p2 | 90
s2 | p3 | 89
I want to find suppliers with more than 2 products.
But only with select and where, no group by. Any suggestion?

Why would you want not to use group by is beyond me, but this might work:
SELECT Supplier FROM table outer WHERE
(
select count(Products) from table inner
where inner.Supplier = outer.Supplier
) > 2
Please bear in mind, that group by is made for stuff like that and should be used.

;WITH
sequenced_data AS
(
SELECT
supplier,
ROW_NUMBER() OVER (PARTITION BY supplier ORDER BY product) AS supplier_product_ordinal
FROM
YourTable
)
SELECT
supplier
FROM
sequenced_data
WHERE
supplier_product_ordinal = 3
But I'd expect it to be slower than using GROUP BY.
SELECT DISTINCT
supplier
FROM
yourTable
WHERE
EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product < yourTable.product)
AND EXISTS (SELECT * FROM yourTable AS lookup WHERE supplier = yourTable.supplier AND product > yourTable.product);

In the usual parts and suppliers database, this relvar is named SP:
SELECT DISTINCT T1.SNO
FROM SP AS T1
JOIN SP AS T2
ON T1.SNO = T2.SNO
AND T2.PNO <> T1.PNO
JOIN SP AS T3
ON T1.SNO = T3.SNO
AND T3.PNO <> T1.PNO
AND T3.PNO <> T2.PNO;
Noting that you can use HAVING without GROUP BY:
SELECT DISTINCT T1.SNO
FROM SP AS T1
WHERE EXISTS (
SELECT 1
FROM SP AS T2
WHERE T2.SNO = T1.SNO
HAVING COUNT(*) > 2
);

;WITH T AS
(
SELECT *,
COUNT(*) OVER (PARTITION BY S) AS Cnt
FROM YourTable
)
SELECT DISTINCT S
FROM T
WHERE Cnt > 2

with subquery:
select distinct supplier
from table a
where (select count(*)
from table b
where b.supplier = a.supplier and b.product <> a.product
) > 1

Related

SQL - Finding Customer's largest Location by Order $

I have a table with customer IDs, location IDs, and their order values. I need to select the location ID for each customer with the largest spend
Customer | Location | Order $
1 | 1A | 100
1 | 1A | 20
1 | 1B | 100
2 | 2A | 50
2 | 2B | 20
2 | 2B | 50
So I would get
Customer | Location | Order $
1 | 1A | 120
2 | 2B | 70
I tried something like this:
SELECT
a.CUST
,a.LOC
,c.BOOKINGS
FROM (SELECT DISTINCT TOP 1 b.CUST, b.LOC, sum(b.ORDER_VAL) as BOOKINGS
FROM ORDER_TABLE b
GROUP BY b.CUST, b.LOC
ORDER BY BOOKINGS DESC) as c
INNER JOIN ORDER_TABLE a
ON a.CUST = c.CUST
But that just returns the top order.
Just use variables to emulate ROW_NUM()
DEMO
SELECT *
FROM ( SELECT `Customer`, `Location`, SUM(`Order`) as `Order`,
#rn := IF(#customer = `Customer`,
#rn + 1,
IF(#customer := `Customer`, 1, 1)
) as rn
FROM Table1
CROSS JOIN (SELECT #rn := 0, #customer := '') as par
GROUP BY `Customer`, `Location`
ORDER BY `Customer`, SUM(`Order`) DESC
) t
WHERE t.rn = 1
Firs you have to sum the values for each location:
select Customer, Location, Sum(Order) as tot_order
from order_table
group by Customer, Location
then you can get the maximum order with MAX, and the top location with a combination of group_concat that will return all locations, ordered by total desc, and substring_index in order to get only the top one:
select
Customer,
substring_index(
group_concat(Location order by tot_order desc),
',', 1
) as location,
Max(tot_order) as max_order
from (
select Customer, Location, Sum(Order) as tot_order
from order_table
group by Customer, Location
) s
group by Customer
(if there's a tie, two locations with the same top order, this query will return just one)
This seems like an order by using aggregate function problem. Here is my stab at it;
SELECT
c.customer,
c.location,
SUM(`order`) as `order_total`,
(
SELECT
SUM(`order`) as `order_total`
FROM customer cm
WHERE cm.customer = c.customer
GROUP BY location
ORDER BY `order_total` DESC LIMIT 1
) as max_order_amount
FROM customer c
GROUP BY location
HAVING max_order_amount = order_total
Here is the SQL fiddle. http://sqlfiddle.com/#!9/2ac0d1/1
This is how I'd handle it (maybe not the best method?) - I wrote it using a CTE first, only to see that MySQL doesn't support CTEs, then switched to writing the same subquery twice:
SELECT B.Customer, C.Location, B.MaxOrderTotal
FROM
(
SELECT A.Customer, MAX(A.OrderTotal) AS MaxOrderTotal
FROM
(
SELECT Customer, Location, SUM(`Order`) AS OrderTotal
FROM Table1
GROUP BY Customer, Location
) AS A
GROUP BY A.Customer
) AS B INNER JOIN
(
SELECT Customer, Location, SUM(`Order`) AS OrderTotal
FROM Table1
GROUP BY Customer, Location
) AS C ON B.Customer = C.Customer AND B.MaxOrderTotal = C.OrderTotal;
Edit: used the table structure provided
This solution will provide multiple rows in the event of a tie.
SQL fiddle for this solution
How about:
select a.*
from (
select customer, location, SUM(val) as s
from orders
group by customer, location
) as a
left join
(
select customer, MAX(b.tot) as t
from (
select customer, location, SUM(val) as tot
from orders
group by customer, location
) as b
group by customer
) as c
on a.customer = c.customer where a.s = c.t;
with
Q_1 as
(
select customer,location, sum(order_$) as order_sum
from cust_order
group by customer,location
order by customer, order_sum desc
),
Q_2 as
(
select customer,max(order_sum) as order_max
from Q_1
group by customer
),
Q_3 as
(
select Q_1.customer,Q_1.location,Q_1.order_sum
from Q_1 inner join Q_2 on Q_1.customer = Q_2.customer and Q_1.order_sum = Q_2.order_max
)
select * from Q_3
Q_1 - selects normal aggregate, Q_2 - selects max(aggregate) out of Q_1 and Q_3 selects customer,location, sum(order) from Q_1 which matches with Q_2

Calculate only distinct values

I have a table and i want output as given below
want only distinct values.
I used cross apply but doesn't work.
Customer Book
C1 B1
C2 B1
C3 B1
I need output: All combination of all customers. Only distinct values
Example:
Customers
C1,C2
C1,C3
C2,C3
You can do this with Primary key column
CREATE TABLE #TAB( ID INT IDENTITY,Customer VARCHAR(10), Book VARCHAR(10))
INSERT INTO #TAB
SELECT 'C1','B1'
UNION ALL
SELECT 'C2','B1'
UNION ALL
SELECT 'C3','B1'
SELECT T2.Customer ,T.Customer
FROM #TAB T
INNER JOIN #TAB T2 ON T.ID >T2.ID
Result :
+----------+----------+
| Customer | Customer |
+----------+----------+
| C1 | C2 |
| C1 | C3 |
| C2 | C3 |
+----------+----------+
This should work, at least for your sample data and the narrative:
select distinct case when t1.customer > t2.customer then t2.customer + ',' + t1.customer else t1.customer + ',' + t2.customer end
from tbl t1
join tbl t2
on t1.book = t2.book
and t1.customer <> t2.customer
If you want customers with the same book, query should be something similar. Note the < operator, since we want (C1, C2), not (C2, C1):
select distinct a.customer_col , b.customer_col
from customer_table a join customer_table b
on a.book_col = b.book_col and a.customer_col < b.customer_col
Hoping i understood question correctly,
Please check below query. Please replace table and columns with your original one.
Rextester link - http://rextester.com/RPPXQO21553
select
a.customer_col , b.customer_col , a.rn , b.rn
from
(select customer_col , row_number() over (order by customer_col) rn from customer_table) a join
(select customer_col , row_number() over (order by customer_col) rn from customer_table) b
on a.customer_col <> b.customer_col
and a.rn < b.rn
order by 1
;
You can achieve this using self join (hope you want customers with the same book), Sample script below
select t1.customer,t2.customer
from #tble t1
inner join #tble t2 on t1.book = t2.book
and t1.customer < t2.customer
I got my answer as
select B,A from(select a.customer A,b.customer B, ROW_NUMBER() over(order by a.customer)rn from CustomerData a
JOIN CustomerData b ON a.customer>b.customer )x

SQL issue on query. need a better/alternate query

Say we have a table T
+------+
| NUMM |
+------+
| 1 |
| 5 |
| 3 |
| 8 |
+------+
I want the nearest bigger number from the column numm to be in column numm1.
The result will look like this
+-------------+
| NUMM | NUMM1|
+-------------+
| 1 | 3 |
| 3 | 5 |
| 5 | 8 |
+-------------+
I wrote a query like this and it works. But i would like to know if there is a better way for sollution.
select numm, numm + min(dif) as numm1
from (select distinct a.numm numm, b.numm numm1, b.numm - a.numm dif
from (select *
from T
where numm != (select max(numm) from T )) a
join T b
on 1 = 1)
where dif > 0
group by numm
If you want to get the direct successor, you can use the lead() windowing function:
select * from (
select
numm,
lead(numm) over (order by numm) as numm1
from t
)
where numm1 is not null
order by numm;
If it's oracle, you can use row_number() function to rank, then inner join with [left_table].rank = [right_table].rank - 1:
SELECT a.numm,
b.numm
FROM
(SELECT numm, row_number() over(order by numm) AS rank FROM pn_test) a
INNER JOIN
(SELECT numm, row_number() over(order by numm) AS rank FROM pn_test) b
ON a.rank = b.rank - 1;
Try the below query if you have repeated records in your table:
WITH CTE_ABC
AS (
SELECT DISTINCT NUMM
FROM [Table]
)
,CTE_XYZ
AS (
SELECT *
FROM (
SELECT NUMM
,lead(NUMM) OVER (
ORDER BY NUMM
) AS numm1
FROM CTE_ABC
) A
WHERE numm1 IS NOT NULL
)
SELECT A.NUMM
,B.numm1
FROM [Table] A
LEFT JOIN CTE_XYZ B
ON A.columnId = B.columnId
WHERE B.numm1 IS NOT NULL
Frank's answer is probably the best when there are no duplicate numbers, but if you can end up with duplicates here's one possible solution:
with t1 as (
select numm
, dense_rank() over (order by numm) rnk
from t
)
select t1.numm
, t2.numm numm1
from t1
join (select distinct numm, rnk-1 rnk from t1) t2
on t1.rnk = t2.rnk;
In this solution the DENSE_RANK analytic function is first used in T1 to give every distinct NUMM a sequential number (RNK). In the second stage T1 is join on RNK to the distinct set of numm and rnk-1 values from t1.
Looking for better performance, this might do the job:
with t1 as (
select numm
, dense_rank() over (order by numm) rnk
, row_number() over (partition by numm order by rownum) ord
from t
)
select t1.numm
, t2.numm numm1
from t1
join t1 t2
on t1.rnk = t2.rnk-1
and t2.ord = 1;
Here I added a way to grab one record for each numm to the subfactored query and eliminated the distinct operation in T2. With a limited data set of just 5 records and no indexes it has a cost of 9 vs a cost of 10 for the prior query.

SQL Dictate number of joined rows based on column value

I have a strange requirement.
Say I have two tables:
ORDER_TB
SEQ PRODUCT_ID ORDER_QTY
1 1 1
2 1 1
3 1 1
STOCK_TB
LOCATION STOCK_QTY
A1 2
B1 1
Desired Join Result:
PRODUCT_ID ORDER_QTY ASSIGNED_LOCATION
1 1 A1
1 1 A1
1 1 B1
In other words, I'd like to assign each products in order table a location from stock_tb based on the quantity of stocks.
This doesn't look like a set operation to me. Is this possible with joins or are there any other clean alternatives in approaching this problem?
What you need to do is get cumulative sums for each of the columns -- this gives you a first and last order for each value. Then you can do a join on a range.
with o as (
select o.*, cumesum
from ORDER_TB o OUTER APPLY
(select sum(o2.order_qty) as cumesum
from ORDER_TB o2
where o2.seq <= o.seq
) o2
),
s as (
select s.*, s2.cumeqty
from STOCK_TB s outer apply
(select sum(s2.order_qty) as cumeqty
from STOCK_TB
where s2.location <= s.location
) s2
)
select o.*, s.location
from o join
s
on o.cumesum between s.cumeqty - s.order_qty + 1 and s.cumeqty;
Note: this works for the data you provided. However, if the quantities in the two tables don't align, then the logic would be more complicated.
Another approach is to use analytic functions:
with cum as
(select s.*, sum(stock_qty) over(order by location) as cum_qty
from stock_tb s)
select x.product_id, x.order_qty, y.location as assigned_location
from (select o.*,
row_number() over(partition by product_id order by seq) as curr_qty
from order_tb o) x
cross join cum y
where y.cum_qty =
(select min(z.cum_qty) from cum z where z.cum_qty >= x.curr_qty)
or (not exists (select 1 from cum z where z.cum_qty > x.curr_qty) and
y.cum_qty = x.curr_qty)
order by seq
Fiddle: http://sqlfiddle.com/#!6/e41b0/1/0

left join without duplicate values using MIN()

I have a table_1:
id custno
1 1
2 2
3 3
and a table_2:
id custno qty descr
1 1 10 a
2 1 7 b
3 2 4 c
4 3 7 d
5 1 5 e
6 1 5 f
When I run this query to show the minimum order quantities from every customer:
SELECT DISTINCT table_1.custno,table_2.qty,table_2.descr
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno AND qty = (SELECT MIN(qty) FROM table_2
WHERE table_2.custno = table_1.custno )
Then I get this result:
custno qty descr
1 5 e
1 5 f
2 4 c
3 7 d
Customer 1 appears twice each time with the same minimum qty (& a different description) but I only want to see customer 1 appear once. I don't care if that is the record with 'e' as a description or 'f' as a description.
First of all... I'm not sure why you need to include table_1 in the queries to begin with:
select custno, min(qty) as min_qty
from table_2
group by custno;
But just in case there is other information that you need that wasn't included in the question:
select table_1.custno, ifnull(min(qty),0) as min_qty
from table_1
left outer join table_2
on table_1.custno = table_2.custno
group by table_1.custno;
"Generic" SQL way:
SELECT table_1.custno,table_2.qty,table_2.descr
FROM table_1, table_2
WHERE table_2.id = (SELECT TOP 1 id
FROM table_2
WHERE custno = table_1.custno
ORDER BY qty )
SQL 2008 way (probably faster):
SELECT custno, qty, descr
FROM
(SELECT
custno,
qty,
descr,
ROW_NUMBER() OVER (PARTITION BY custno ORDER BY qty) RowNum
FROM table_2
) A
WHERE RowNum = 1
If you use SQL-Server you could use ROW_NUMBER and a CTE:
WITH CTE AS
(
SELECT table_1.custno,table_2.qty,table_2.descr,
RN = ROW_NUMBER() OVER ( PARTITION BY table_1.custno
Order By table_2.qty ASC)
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno
)
SELECT custno, qty,descr
FROM CTE
WHERE RN = 1
Demolink