MySQL result Group by two values - sql

I need some help for a query to group some rows, I'm trying the whole day and find no solution and I'm sure it's easy. Maybe some one can bring me light in the dark:
My Table:
id | Bid | Cid | value
4 | 22 | 11 | 33
5 | 24 | 11 | 33
6 | 25 | 11 | 33
7 | 24 | 11 | 100
8 | 25 | 16 | 150
I want only the result Bid=25 if I have Cid 11, 16 and value 33, 150
I tried
SELECT id, Bid
FROM `table`
WHERE
Cid IN (11, 16) AND
value IN ('33','150')
GROUP BY Bid;
But in this case I get all possible Cid's ...
It seems I'm on a wood way.

Your query is tricky because you are looking for the presence of pairs of column values in a given group. One way to go here is to aggregate by Bid, Cid, and value, first, with the restriction that each record has a matching pair. Then subquery this by Bid and check that the count be 2, indicating that both pairs were present.
SELECT Bid
FROM
(
SELECT Bid, Cid, value
FROM yourTable
WHERE (Cid, value) IN ((11, 33), (16, 150))
GROUP BY Bid, Cid, value
) t
GROUP BY Bid
HAVING COUNT(*) = 2;
Demo
Since you are using SQL Server we can slightly refactor the above query to this:
SELECT Bid
FROM
(
SELECT Bid, Cid, value
FROM yourTable
WHERE (Cid = 11 AND value = 33) OR (Cid = 16 AND value = 150)
GROUP BY Bid, Cid, value
) t
GROUP BY Bid
HAVING COUNT(*) = 2;

You can find your answer when you use GROUP BY in combination with some SUM
SELECT
Bid
FROM
Table1
GROUP BY
Bid
HAVING
SUM(Cid = 11) AND SUM(Cid = 16)
AND
SUM(value = 33) AND SUM(value = 150)
Result
| Bid |
|-----|
| 25 |
demo http://www.sqlfiddle.com/#!9/ce56e97/2

Related

Get the min of one column but select multiple columns

I have a table as following:
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I want the min(Amount) for each ID but I still want to display its Name. So I want this:
ID NAME min(AMOUNT)
______________________
1 A 3
2 C 18
4 I 2
ID's can occur multiple times, Names too. I tried this:
SELECT ID, NAME, min(AMOUNT) FROM TABLE
GROUP BY ID
But of course its an error because I have to
GROUP BY ID, NAME
But then I get
ID NAME AMOUNT
______________________
1 A 3
1 B 4
2 C 18
4 I 2
4 P 9
And I understand why, it looks for the min(AMOUNT) for each combination of ID + NAME. So my question is basically, how can I select multiple column (ID, NAME, AMOUNT) and get the minimum for only one column, still displaying the others?
Im new to SQL but I cant seem to find an answer..
If you are using PostgreSQL, SQL Server, MySQL 8.0 and Oracle then try the following with window function row_number().
in case you have one id with similar amount then you can use dense_rank() instead of row_number()
Here is the demo.
select
id,
name,
amount
from
(
select
*,
row_number() over (partition by id order by amount) as rnk
from yourTable
) val
where rnk = 1
Output:
| id | name | amount |
| --- | ---- | ------ |
| 1 | A | 3 |
| 2 | C | 18 |
| 4 | I | 2 |
Second Option without using window function
select
val.id,
t.name,
val.amount
from myTable t
join
(
select
id,
min(amount) as amount
from myTable
group by
id
) val
on t.id = val.id
and t.amount = val.amount
You did not specify your db vendor. If it is luckily Postgres, the problem can be also solved without nested subquery using proprietary distinct on clause:
with t(id,name,amount) as (values
(1, 'A', 3),
(1, 'B', 4),
(1, 'W', 3),
(2, 'C', 18),
(4, 'I', 2),
(4, 'P', 9)
)
select distinct on (id, name_of_min) id
, first_value(name) over (partition by id order by amount) as name_of_min
, amount
from t
order by id, name_of_min
Just for widening knowledge. I don't recommend using proprietary features. first_value is standard function but to solve problem in simple query is still not enough. #zealous' answer is perfect.
In many databases, the most efficient method uses a correlated subquery:
select t.*
from t
where t.amount = (select min(t2.amount) from t t2 where t2.id = t.id);
In particular, this can take advantage of an index on (id, amount).

How to SUM column 1 and select column 2 by condition?

I've stuck with how to sum column A and select column B with a condition if column B >= 50 select this row id.
Example Table Like this
+----+-----------+---------+
| ID | PRICE | PERCENT |
+----+-----------+---------+
| 1 | 5 | 5 |
| 2 | 18 | 20 |
| 3 | 7 | 50 |
| 4 | 16 | 56 |
| 5 | 50 | 87 |
| 6 | 17 | 95 |
| 7 | 40 | 107 |
+----+-----------+---------+
SELECT ID, SUM(PRICE) AS PRICE, PERCENT FROM Table
Column ID and PERCENT, I want to select from a row with PERCENT >= 50
The result should be
Any suggestions?
Try below query:
declare #tbl table(ID int, PRICE int, [PERCENT] int);
insert into #tbl values
(1, 5, 5),
(2, 18, 20),
(3, 7, 50),
(4, 16, 56),
(5, 50, 87),
(6, 17, 95),
(7, 40, 107);
select top 1 ID,
(select sum(PRICE) from #tbl) PRICE,
[PERCENT]
from #tbl
where [PERCENT] > 50
You could include the total in a subquery in the SELECT clause of your query like this:
SELECT
[ID],
(SELECT SUM([PRICE]) FROM T) AS [PRICE],
[PERCENT]
FROM
T
WHERE
[PRICE] >= 50
However, it remains unclear which of the five valid records should be picked. You indicated it should be the record where PERCENT has value 56, but IMHO value 50 would be possible too, just like 87, 95, and 107 (?). It is unclear why you pick value 56 as the correct one. If it doesn't matter, you could use TOP (1) in the SELECT clause, but if it does matter, you should extend the WHERE clause with appropriate conditions/filters.
Mixing aggregate data from groups back with individual elements/records like this is often fuzzy. I consider it to be a "code smell" and here in your question on StackOverflow, it might indicate an XY-problem. Anyway, these query results might get misinterpreted quite easily if you are not careful. Always remember that such aggregated data in the result (in this case the PRICE field) has practically nothing to do with the detail data in the result (in this case the ID and PERCENT fields). Unless you want to combine your aggregate data with your detail data (in a calculation for example), but you do not indicate you want anything like that in your question...
you can do this Trick to have a result of 2 queries in 1 query:
select ID as ID,T.[PERCENT] AS B, 0 as sumA
from Table_1 as T
where T.[PERCENT]>=50
union All
select 0 as ID,0 AS B, sum(t.[PRICE]) as sumA
from Table_1 as T
Am not sure why you need this but certainly, You can Archive Above Output using below query
Sample Data
declare #data table
(Id int, Price int, [Percent] int)
insert #data
VALUES (1,5,5),
(2,18,20),
(3,7,50),
(4,16,56),
(5,50,87),
(6,17,95),
(7,40,107)
Query
select top 1 ID, (select sum(price) from #data) as Price, [Percent ]
from #data
where [Percent ] >50
You can try the following code:
SELECT TOP (1) [ID], SUM(PRICE) OVER (), [PERCENT]
FROM #tbl
ORDER BY CASE WHEN [PERCENT] > 50 THEN 0 ELSE 1 END, [ID];
I am using OVER clause in order to extract/read data from the table only once - one table scan.

Checking using nvl2 with multiple group by

I have a table like
------------------------
S.No Name Amount Imp_Num
1 A 10 12345
2 B 20
3 A 30
4 C 40 4555
5 B 50
--------------------------
and I want something like
---------------------------------------
Name Total_Amount Imp_Num Imp_Num_Present
A 40 12345 Y
B 70 null N
C 40 4555 Y
---------------------------------------
The important_number_present column should be Y if the important number is present for the particular name at least once and the important number should be captured. The important number for a particular name is assumed to be the same.If different the latest one should be displayed as imp_numb. (But this is of secondary priority).
I tried something like
Select sum(amount) as total_amount, imp_num, nvl2(imp_num,'Y','N') from sampletable group by imp_num;
But name can't be retrieved and the data doesn't make sense without the name. I might be doing something totally wrong. Can a feasible solution be done in SQL rather than in pl/sql.
Group by with name is returning the name with a null entry and imp_num entry.
I am cracking my head on this. Would be of great help, if someone solves it.
Thanks in advance
You could use a (fake) aggregation function on imp_num and group by name
Select Name, sum(amount) as total_amount, max(imp_num), nvl2( max(imp_num),'Y','N')
from sampletable
group by Name;
EDIT: Another solution with COUNT function. DEMO
SELECT name
,SUM(amount) AS total_amount
,MAX(imp_num) AS Imp_Num
,CASE
WHEN Count(imp_num) > 0
THEN 'Y'
ELSE 'N'
END AS Imp_Num_Present
FROM yourtable
GROUP BY name
You may also use a MAX( CASE ) block
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE yourtable
(S_No int, Name varchar2(1), Amount int, Imp_Num varchar2(5))
;
INSERT ALL
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (1, 'A', 10, '12345')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (2, 'B', 20, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (3, 'A', 30, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (4, 'C', 40, '4555')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (5, 'B', 50, NULL)
SELECT * FROM dual
;
Query 1:
SELECT Name,
SUM (amount) AS total_amount,
MAX (imp_num) AS Imp_Num,
CASE
WHEN MAX (CASE WHEN imp_num IS NOT NULL THEN 1 ELSE 0 END) = 1
THEN
'Y'
ELSE
'N'
END
AS Imp_Num_Present
FROM yourtable
GROUP BY Name
Results:
| NAME | TOTAL_AMOUNT | IMP_NUM | IMP_NUM_PRESENT |
|------|--------------|---------|-----------------|
| A | 40 | 12345 | Y |
| B | 70 | (null) | N |
| C | 40 | 4555 | Y |

Group by minimum value in one field while selecting distinct rows

Here's what I'm trying to do. Let's say I have this table t:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
2 | 18 | 2012-05-19 | y
3 | 18 | 2012-08-09 | z
4 | 19 | 2009-06-01 | a
5 | 19 | 2011-04-03 | b
6 | 19 | 2011-10-25 | c
7 | 19 | 2012-08-09 | d
For each id, I want to select the row containing the minimum record_date. So I'd get:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
The only solutions I've seen to this problem assume that all record_date entries are distinct, but that is not this case in my data. Using a subquery and an inner join with two conditions would give me duplicate rows for some ids, which I don't want:
key_id | id | record_date | other_cols
1 | 18 | 2011-04-03 | x
5 | 19 | 2011-04-03 | b
4 | 19 | 2009-06-01 | a
How about something like:
SELECT mt.*
FROM MyTable mt INNER JOIN
(
SELECT id, MIN(record_date) AS MinDate
FROM MyTable
GROUP BY id
) t ON mt.id = t.id AND mt.record_date = t.MinDate
This gets the minimum date per ID, and then gets the values based on those values. The only time you would have duplicates is if there are duplicate minimum record_dates for the same ID.
I could get to your expected result just by doing this in mysql:
SELECT id, min(record_date), other_cols
FROM mytable
GROUP BY id
Does this work for you?
To get the cheapest product in each category, you use the MIN() function in a correlated subquery as follows:
SELECT categoryid,
productid,
productName,
unitprice
FROM products a WHERE unitprice = (
SELECT MIN(unitprice)
FROM products b
WHERE b.categoryid = a.categoryid)
The outer query scans all rows in the products table and returns the products that have unit prices match with the lowest price in each category returned by the correlated subquery.
I would like to add to some of the other answers here, if you don't need the first item but say the second number for example you can use rownumber in a subquery and base your result set off of that.
SELECT * FROM
(
SELECT
ROW_NUM() OVER (PARTITION BY Id ORDER BY record_date, other_cols) as rownum,
*
FROM products P
) INNER
WHERE rownum = 2
This also allows you to order off multiple columns in the subquery which may help if two record_dates have identical values. You can also partition off of multiple columns if needed by delimiting them with a comma
This does it simply:
select t2.id,t2.record_date,t2.other_cols
from (select ROW_NUMBER() over(partition by id order by record_date)as rownum,id,record_date,other_cols from MyTable)t2
where t2.rownum = 1
If record_date has no duplicates within a group:
think of it as of filtering. Simpliy get (WHERE) one (MIN(record_date)) row from the current group:
SELECT * FROM t t1 WHERE record_date = (
select MIN(record_date)
from t t2 where t2.group_id = t1.group_id)
If there could be 2+ min record_date within a group:
filter out non-min rows (see above)
then (AND) pick only one from the 2+ min record_date rows, within the given group_id. E.g. pick the one with the min unique key:
AND key_id = (select MIN(key_id)
from t t3 where t3.record_date = t1.record_date
and t3.group_id = t1.group_id)
so
key_id | group_id | record_date | other_cols
1 | 18 | 2011-04-03 | x
4 | 19 | 2009-06-01 | a
8 | 19 | 2009-06-01 | e
will select key_ids: #1 and #4
SELECT p.* FROM tbl p
INNER JOIN(
SELECT t.id, MIN(record_date) AS MinDate
FROM tbl t
GROUP BY t.id
) t ON p.id = t.id AND p.record_date = t.MinDate
GROUP BY p.id
This code eliminates duplicate record_date in case there are same ids with same record_date.
If you want duplicates, remove the last line GROUP BY p.id.
This a old question, but this can useful for someone
In my case i can't using a sub query because i have a big query and i need using min() on my result, if i use sub query the db need reexecute my big query. i'm using Mysql
select t.*
from (select m.*, #g := 0
from MyTable m --here i have a big query
order by id, record_date) t
where (1 = case when #g = 0 or #g <> id then 1 else 0 end )
and (#g := id) IS NOT NULL
Basically I ordered the result and then put a variable in order to get only the first record in each group.
The below query takes the first date for each work order (in a table of showing all status changes):
SELECT
WORKORDERNUM,
MIN(DATE)
FROM
WORKORDERS
WHERE
DATE >= to_date('2015-01-01','YYYY-MM-DD')
GROUP BY
WORKORDERNUM
select
department,
min_salary,
(select s1.last_name from staff s1 where s1.salary=s3.min_salary ) lastname
from
(select department, min (salary) min_salary from staff s2 group by s2.department) s3

Insert records in table based on 'master' record in same table

I have one table, e.g. pricerules, that have stored special prices by article for customers. Now I want to sync the pricerules, based on an other user. Suppose I have this as dataset:
+---------------------------+
| user_id | prod_id | price |
+---------+---------+-------+
| 10 | 1 | 1 |
| 10 | 2 | 5 |
| 10 | 3 | 7 |
| 20 | 2 | 5 |
| 30 | 2 | 5 |
| 30 | 3 | 7 |
+---------+---------+-------+
Now I would like to update/insert the prices for several other users, based upon the prices of user 10. I already wrote the delete and update query, but I'm stuck with the insert query to insert the new rules that the other users don't have yet.
So effectively this would do the following inserts:
INSERT INTO pricerules
(user_id, prod_id, price)
VALUES
(20, 1, 1),
(20, 3, 7),
(30, 1, 1);
Is there a way to do this in one query? I have been looking for MINUS to select the records that are not present for user 20, but I would have to execute a query for each user.
I thought maybe I could use MERGE.
I am using Oracle 10.1 ..
You were right. Merge is the way to go. Please try the following.
merge into pricerules p
using ( select t1.user_id, t2.prod_id, t2.price
from
(select distinct user_id
from pricerules
where user_id <> 10) t1,
(select distinct prod_id, price
from pricerules
where user_id = 10) t2
) t
on (p.user_id = t.user_id
and p.prod_id = t.prod_id
and p.price = t.price)
when not matched then
insert (user_id, prod_id, price) values (t.user_id, t.prod_id, t.price) ;
I haven't used Oracle in a long while, so my syntax may be slightly off, but the general idea is:
INSERT INTO pricerules
(user_id, prod_id, price)
select 20 as user_id, 1 as prod_id, 1 as price from dual
union all
select 20, 3, 7 from dual
union all
select 30, 1, 1 from dual
Quickly typed this together so I'm not sure if it's correct. But what I'm trying to do is to select all product id's from user 10 that the new user does not already have.
What's missing in your question is where the user_id's originate. You may want to join this with a user table so you can run it for all users.
insert into pricerules
(user_id, prod_id, price)
select &new_user_id
,prod_id
,price
from pricerules p
where user_id = 10
and not exists (select 1
from pricerules p2
where p2.userid = &new_userid
and p2.prod_id = p.prod_id)