I have a requirement to distribute records equally into two categories. But in case I fall short of records in any one category, I should accommodate count the remaining records in other category.
Sample data:
If like this students of subject s1 are 12, and subject s2 are 20. I need to pick 30 students, result should give me 15 for each subject, but as s1 total is only 12, I should get 12 from s1 and 18 from s2.
This should do the trick:
DECLARE #t TABLE(ID INT, Student VARCHAR(10), Subject CHAR(2))
INSERT INTO #t VALUES
(1, 'Stud1', 's1'),
(2, 'Stud2', 's1'),
(3, 'Stud3', 's2'),
(4, 'Stud4', 's2'),
(5, 'Stud5', 's2'),
(6, 'Stud6', 's2'),
(7, 'Stud7', 's2'),
(8, 'Stud8', 's2'),
(9, 'Stud9', 's2')
;WITH cte AS(SELECT *, ROW_NUMBER() OVER(PARTITION BY Subject ORDER BY ID) AS rn FROM #t)
SELECT TOP 7 *
FROM cte
ORDER BY rn, Subject
The idea is that you are numbering the rows within subjects like:
1 Stud1 s1 1
3 Stud3 s2 1
2 Stud2 s1 2
4 Stud4 s2 2
5 Stud5 s2 3
6 Stud6 s2 4
7 Stud7 s2 5
8 Stud8 s2 6
9 Stud9 s2 7
So, when selecting top N rows, they are distributing automatically because of ordering by that column.
Related
These three tables are part of a larger order management system:
orders
o_id c_id
1 1
2 1
3 2
4 3
5 3
6 4
7 5
order_items
o_id p_id
1 1
2 2
3 1
3 2
3 8
4 1
4 2
5 8
5 9
6 4
6 5
7 12
customers
c_id name
1 Doug
2 Tammy
3 Bill
4 Don
5 Kate
I want to find ALL pairs of customers where the second customer in the pair has purchased NONE of the products that the first customer in the pair has purchased. I can't seem to figure this out! My best attempt was grabbing the count of all unique products and trying to see if I could group and reduce by leveraging that count.
Expected Output
c_id1 c_id2
4 1
4 2
4 3
4 5
5 1
5 2
5 3
Or the exact opposite (no duplicates).
CREATE TABLE orders (
o_id INT,
c_id INT
);
INSERT INTO orders (o_id, c_id) VALUES
(1, 1),
(2, 1),
(3, 2),
(4, 3),
(5, 3),
(6, 4),
(7, 5);
CREATE TABLE order_items (
o_id INT,
p_id INT
);
INSERT INTO order_items (o_id, p_id) VALUES
(1, 1),
(2, 2),
(3, 1),
(3, 2),
(3, 8),
(4, 1),
(4, 2),
(5, 8),
(5, 9),
(6, 4),
(6, 5),
(7, 12);
CREATE TABLE customers (
c_id INT,
name VARCHAR(10)
);
INSERT INTO customers (c_id, name) VALUES
(1, 'Doug'),
(2, 'Tammy'),
(3, 'Bill'),
(4, 'Don'),
(5, 'Kate');
Test
WITH cte AS ( SELECT *
FROM orders
NATURAL JOIN order_items
NATURAL JOIN customers )
SELECT t1.c_id id1, t2.c_id id2
FROM customers t1
JOIN customers t2 ON t1.c_id < t2.c_id
WHERE NOT EXISTS ( SELECT NULL
FROM cte cte1, cte cte2
WHERE cte1.c_id = t1.c_id
AND cte2.c_id = t2.c_id
AND cte1.p_id = cte2.p_id );
fiddle
The idea is to generate all pairs of customers (using a cross join).
Then check that they do not have the same items. This is a little tricky, but it involves not exists and joining down to the item level to see if any match on orders that match the customers:
select c1.c_id, c2.c_id
from customers c1 cross join
customers c2
where not exists (select 1
from order_items oi1 join
order_items oi2
on oi1.i_id = oi2.i_id join
orders o1
on o1.o_id = oi1.o_id join
orders o2
on o2.o_id = oi2.o_id
where o1.c_id = c1.c_id and
o2.c_id = o2.c_id
);
I have this table
serie number
----- ------
A 1
A 2
A 3
A 5
B 1
B 3
I want to show a result like this
serie ocurrences last_number_value
----- ---------- -----------------
A 4 5
B 2 3
I managed to get the ocurrences number with
SELECT serie, number, COUNT(*) AS ocurrences FROM table_name GROUP BY serie
But, how to show the last number too?
This will get you the result your looking for:
CREATE TABLE test (
serie varchar(1) NOT NULL,
num int(1) NOT NULL
);
INSERT INTO test (serie, num) VALUES
('A', 1),
('A', 2),
('A', 3),
('A', 5),
('B', 1),
('B', 3);
If the row order matters:
SELECT
a.serie,
(SELECT count(*) FROM test WHERE serie=a.serie) as occurances,
a.num
FROM
test AS a
LEFT JOIN test AS b on a.serie=b.serie AND a.num<b.num
WHERE
b.serie IS NULL;
If the order or the rows do NOT matter:
SELECT
a.serie,
COUNT(*) AS occurances,
MAX(a.num) AS last_number_value
FROM
test AS a
GROUP BY a.serie;
The example below builds a table that extracts the first two score values by userId and passageId. How can I select only records where each record in the new table contains at least two scores (i.e. ignore records where score2 is null)?
Example
Code:
drop table if exists simon;
drop table if exists simon2;
Create table simon (userId int, passageId int, score int);
Create table simon2 (userId int, passageId int, score1 int,score2 int);
INSERT INTO simon (userId , passageId , score )
VALUES
(10, 1, 2),
(10, 1, 3),
(10, 2, 1),
(10, 2, 1),
(10, 2, 5),
(11, 1, 1),
(11, 2, 2),
(11, 2, 3),
(11, 3, 4);
insert into simon2(userId,passageId,score1,score2)
select t.userId, t.passageId,
substring_index(t.scores,',',1) as score1,
(case when length(t.scores) > 1 then substring_index(t.scores,',',-1)
else null
end
) as score2
from
(select userId,passageId,
substring_index (group_concat(score separator ','),',',2) as scores
from simon
group by userId,passageId) t;
select *from simon2;
This is what I get now:
userId passageId score1 score2
1 10 1 2 3
2 10 2 1 1
3 11 1 1 NULL
4 11 2 2 3
5 11 3 4 NULL
This is what I want:
userId passageId score1 score2
1 10 1 2 3
2 10 2 1 1
4 11 2 2 3
Just add this around your query
Select * from ( ...... ) x where score2 is not null
You have no ordering specifying what score goes into score_1 and score_2, so I'll just use min() and max(). You can then do the logic as:
select s.userid, s.passageid,
max(score) as score_1, max(score) as score_2
from simon s
group by s.userid, s.passageid
having count(*) >= 2;
This doesn't give you exactly the same results for 10/2. However, your results are arbitrary because the group_concat() has no order by. SQL tables represent unordered sets. There is no ordering unless you specify it.
If you want an ordering, then define the table as:
Create table simon (
simonId int auto_increment primary key,
userId int,
passageId int,
score int
);
And then use the simonId column appropriately.
I'm working on another SQL query.
I have the following table.
PURCHASES
ID CUST_ID PROD_CODE PURCH_DATE
1 1 'WER' 01/12/2012
2 2 'RRE' 02/10/2005
3 3 'RRY' 02/11/2011
4 3 'TTB' 15/05/2007
5 3 'GGD' 20/06/2016
6 2 'SSD' 02/10/2011
I'm trying to add another column PURCH_COUNT that would display the purchase count for the CUST_ID based on PURCH_DATE.
If this is a first purchase it would return 1, if second then 2, and so on.
So the result I'm hoping is:
ID CUST_ID PROD_CODE PURCH_DATE PURCH_COUNT
1 1 'WER' 01/12/2012 1
2 2 'RRE' 02/10/2005 1
3 3 'RRY' 02/11/2011 2
4 3 'TTB' 15/05/2007 1
5 3 'GGD' 20/06/2016 3
6 2 'SSD' 02/10/2011 2
Thanks in advance!
Sample Data
DECLARE #Table1 TABLE
(ID int, CUST_ID int, PROD_CODE varchar(7), PURCH_DATE datetime)
;
INSERT INTO #Table1
(ID, CUST_ID, PROD_CODE, PURCH_DATE)
VALUES
(1, 1, 'WER', '2012-01-12 05:30:00'),
(2, 2, 'RRE', '2005-02-10 05:30:00'),
(3, 3, 'RRY', '2011-02-11 05:30:00'),
(4, 3, 'TTB', '2008-03-05 05:30:00'),
(5, 3, 'GGD', '2017-08-06 05:30:00'),
(6, 2, 'SSD', '2011-02-10 05:30:00')
;
IN SQL :
select ID,
CUST_ID,
PROD_CODE,
PURCH_DATE,
ROW_NUMBER()OVER(PARTITION BY CUST_ID ORDER BY (SELECT NULL))RN
from #Table1
In MySql :
SELECT a.ID, a.CUST_ID,a.PROD_CODE,a.PURCH_DATE, (
SELECT count(*) from #Table1 b where a.CUST_ID >= b.CUST_ID AND a.ID = b.ID
) AS row_number FROM #Table1 a
Use a correlated sub-query to get the counts per customer.
SELECT t.*,
(SELECT 1+count(*)
FROM table1
WHERE t.cust_id = cust_id
AND t.purch_date > purch_date) as purch_cnt
FROM table1 t
ORDER BY cust_id,purch_date
SQL Fiddle
Any correlated subquery or window function can be expressed as a join, too. Sometimes a join is easier to understand, or is produced from components you can re-use, and sometimes the DBMS doesn't support the fancier feature. (AFAIK, a subquery in a SELECT clause is nonstandard.)
create table T
(ID, CUST_ID, PROD_CODE, PURCH_DATE);
INSERT INTO T
(ID, CUST_ID, PROD_CODE, PURCH_DATE)
VALUES
(1, 1, 'WER', '2012-01-12 05:30:00'),
(2, 2, 'RRE', '2005-02-10 05:30:00'),
(3, 3, 'RRY', '2011-02-11 05:30:00'),
(4, 3, 'TTB', '2008-03-05 05:30:00'),
(5, 3, 'GGD', '2017-08-06 05:30:00'),
(6, 2, 'SSD', '2011-02-10 05:30:00')
;
select PURCH_COUNT, T.*
from T join (
select count(b.ID) as PURCH_COUNT
, a.CUST_ID, a.PURCH_DATE
from T as a join T as b
on a.CUST_ID = b.CUST_ID
and b.PURCH_DATE <= a.PURCH_DATE
group by a.CUST_ID, a.PURCH_DATE
) as Q
on T.CUST_ID = Q.CUST_ID
and T.PURCH_DATE = Q.PURCH_DATE
;
Output of subquery:
PURCH_COUNT CUST_ID PURCH_DATE
----------- ---------- -------------------
1 1 2012-01-12 05:30:00
1 2 2005-02-10 05:30:00
2 2 2011-02-10 05:30:00
1 3 2008-03-05 05:30:00
2 3 2011-02-11 05:30:00
3 3 2017-08-06 05:30:00
Output of query:
PURCH_COUNT ID CUST_ID PROD_CODE PURCH_DATE
----------- ---------- ---------- ---------- -------------------
1 1 1 WER 2012-01-12 05:30:00
1 2 2 RRE 2005-02-10 05:30:00
2 3 3 RRY 2011-02-11 05:30:00
1 4 3 TTB 2008-03-05 05:30:00
3 5 3 GGD 2017-08-06 05:30:00
2 6 2 SSD 2011-02-10 05:30:00
Emp table
id ename
----------- ----------
1 apv
2 sug
3 raj
4 ram
5 sam
6 apv1
7 sug1
8 raj1
9 ram1
10 sam1
Dept Table
dept salary id
----- ----------- -----------
a 1000 1
b 2000 2
c 3000 3
d 5000 4
e 7000 5
a 20000 6
b 500 7
c 5000 8
a 1000 9
b 90000 10
How can I return both the Max(salary) from each dept and also the details of who earns that salary?
(select id,ename,dept,salary)
select mm, d1.dept, d1.id, ename from DEPT D1,
(select max(salary) mm, dept from DEPT group by dept) D2, EMP
where D2.mm=d1.salary and d2.dep=d1.dept
EMP.ID=DEPT.ID
declare #Emp table (id int, ename varchar(4))
declare #Dept table (dept char(1), salary int, id int)
insert into #Emp values
(1, 'apv'),
(2, 'sug'),
(3, 'raj'),
(4, 'ram'),
(5, 'sam'),
(6, 'apv1'),
(7, 'sug1'),
(8, 'raj1'),
(9, 'ram1'),
(10, 'sam1')
insert into #Dept values
('a', 1000, 1),
('b', 2000, 2),
('c', 3000, 3),
('d', 5000, 4),
('e', 7000, 5),
('a', 20000, 6),
('b', 500, 7),
('c', 5000, 8),
('a', 1000, 9),
('b', 90000, 10)
;with cte as
(
select
id,
salary,
dept,
rank() over(partition by dept order by salary desc) as rn
from #Dept
)
select
e.ename,
e.id,
c.salary,
c.dept
from cte as c
inner join #Emp as e
on c.id = e.id
where rn = 1
Result
ename id salary dept
----- ----------- ----------- ----
apv1 6 20000 a
sam1 10 90000 b
raj1 8 5000 c
ram 4 5000 d
sam 5 7000 e
Prequery by department first, then go back to itself on same department and matching salary. From THAT table, you can tie to the employee table. This version will allow multiple people in the same department with the same salary to be pulled out... ex: 5 people in Dept X earning 75,000.
SELECT
d1.Dept,
d1.Salary,
Emp.id,
Emp.name
from
( SELECT
dept,
MAX(salary) MaxSal
from
Dept
group by
dept ) ByDept
join Dept d1
ON ByDept.dept = d1.dept
and ByDept.MaxSal = d1.Salary
join Emp
ON d1.id = Emp.id
I do not know if I understand you at all but you can try something like:
SELECT id, ename, dept, MAX(salary) AS salary
FROM Dept_Table AS d
LEFT JOIN Emp_Table AS e
ON e.id = d.id
GROUP BY dept
select dept.dept, max(dept.salary), emp.id, emp.ename
from emp inner join dept on emp.id=dept.id
group by dept.dept, emp.id, emp.ename
should do the trick, just know that if two people in same dept have same salary and it is the maximum salary you will have both people as outout.
SELECT emp.ename,dept.dept,max(dept.salary) from dept left join emp on dept.id=emp.id group by dept.dept