How to Use Count in Update Statement condition? - sql

We have two tables, Bucket and Student. We have to update Bucket_id in Student table having same country and city. But in my Bucket table I have two Bucket Ids against same country and city we can differentiate them by Desc.
When we update Bucket_id in Student table keep in mind when can only assign 70 students to same bucket Id. After 70 Students have been assigned, it will update with another bucket Id
Bucket Table
BucketId Desc Country City
1 SOUTH PAK KHI
2 NORTH PAK KHI
Initially Bucket_Id in Student is null
Expected Result Below: After Update Bucket_Id in Student Table
Student Table
StudentId Country City BucketId
101 PAK KHI 1
102 PAK KHI 1
109 PAK KHI 1
201 PAK KHI 2
I have tried using
update student std set Bucket_Id = (
select bucket_id from Bucket b where b.country=std.country and b.city = std.city and count(std.bucket_id) < 70
);
How can we do that? We are using oracle 11g.

You can assign the buckets using a query:
select s.*, b.bucketid
from (select s.*,
row_number() over (partition by country, city order by studentid) - 1 as seqnum
from students s
) s join
(select b.*,
row_number() over (partition by country, city order by bucketid) - 1 as seqnum
from buckets b
) b
on s.seqnum between b.seqnum * 70 and (b.seqnum + 1) * 70 - 1;
Oracle doesn't have very good support in update for handling updates across tables, so you then need to use merge:
merge into
students s using
(select s.*, b.bucketid
from (select s.*,
row_number() over (partition by country, city order by studentid) - 1 as seqnum
from students s
) s join
(select b.*,
row_number() over (partition by country, city order by bucketid) - 1 as seqnum
from buckets b
) b
on s.seqnum between b.seqnum * 70 and (b.seqnum + 1) * 70 - 1
) ss
on s.studentid = ss.studentid
when matched then update
set s.bucketid = ss.bucketid;

See if this works for you:
update students std set BucketId = (
select min(bucketid) from Bucket b where b.country=std.country and b.city = std.city)
where bucketid is null
and std.STUDENTID in (
SELECT x.STUDENTID FROM
(
select StudentId,
rank() over(partition by country, city order by StudentId) rn
from students
) x where rn <= 3);

You can also try this, if I understood the problem correctly:
MERGE INTO student t
USING (
SELECT
studentid
, CAST(
ROW_NUMBER() OVER(
PARTITION BY country,city
ORDER BY studentid
) / 70 + 0.5
AS NUMBER(9,0)
) AS bucketid
FROM student
) s
ON (t.studentid=s.studentid)
WHEN MATCHED THEN UPDATE
SET bucketid = s.bucketid

Related

Pivot two columns

I have data that looks likes:
stu_id
course_name
staff_name
1
Economics - 3
Kuzma, Brian
1
History
Hulings, Kreg
1
IHS IB Lit of Americ
Duncan, Amy
2
Marine Biology A
Crews, Erin
2
Pre-Calculus
Soderholm, Lodi
2
Environ & Wld Iss
Haberman, Helen
I am trying format it as such using SQL, so that each student's data is all in one row:
stu_id
course1
staff1
course2
staff2
etc ...
1
Economics - 3
Kuzma, Brian
History
Hulings, Kreg
etc ...
2
Marine Biology A
Crews, Erin
Pre-Calculus
Soderholm, Lodi
etc ...
Each student can have up to 6 courses and associated staff names all pivoted to a single row.
The basic query is:
SELECT dtbl_students.student_id stu_id,
course_name,
staff_name
FROM k12intel_dw.ftbl_student_schedules
INNER JOIN k12intel_dw.dtbl_students WITH (nolock)
ON ftbl_student_schedules.student_key = dtbl_students.student_key
INNER JOIN k12intel_dw.dtbl_staff WITH (nolock)
ON ftbl_student_schedules.staff_key = dtbl_staff.staff_key
INNER JOIN k12intel_dw.dtbl_courses WITH (nolock)
ON ftbl_student_schedules.course_key = dtbl_courses.course_key
INNER JOIN k12intel_dw.dtbl_schools WITH (nolock)
ON ftbl_student_schedules.school_key = dtbl_schools.school_key
INNER JOIN k12intel_dw.dtbl_school_dates period_date WITH (nolock)
ON ftbl_student_schedules.school_dates_key =
period_date.school_dates_key
WHERE local_school_year = '2019-2020'
AND local_semester = 3
I am attempting to pivot on course_name and staff_name.
I have managed to UNPIVOT as such:
WITH Courses
AS (SELECT dtbl_students.student_id stu_id,
course_name,
staff_name
FROM K12intel_dw.FTBL_STUDENT_SCHEDULES
INNER JOIN K12intel_dw.DTBL_STUDENTS WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.STUDENT_KEY =
DTBL_STUDENTS.STUDENT_KEY
INNER JOIN K12intel_dw.DTBL_staff WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.staff_KEY =
DTBL_staff.staff_KEY
INNER JOIN K12intel_dw.DTBL_COURSES WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.COURSE_KEY =
DTBL_COURSES.COURSE_KEY
INNER JOIN K12intel_dw.DTBL_SCHOOLS WITH (Nolock)
ON FTBL_STUDENT_SCHEDULES.SCHOOL_KEY =
DTBL_SCHOOLS.SCHOOL_KEY
INNER JOIN K12intel_dw.DTBL_SCHOOL_DATES period_date WITH (
Nolock)
ON FTBL_STUDENT_SCHEDULES.SCHOOL_DATES_KEY =
period_date.SCHOOL_DATES_KEY
WHERE local_school_year = '2019-2020'
AND local_semester = 3)
SELECT *
FROM Courses
UNPIVOT ( Course
FOR Value IN ([course_name],
[staff_name]) ) unpiv
Which produces something like:
stu_id
course
value
1
IHS IB Economics - 3
course_name
1
Kuzma, Brian
staff_name
1
IHS IB History of th
course_name
1
Hulings, Kreg
staff_name
I have been experimenting with an unpivot then pivot or two pivots, using all the examples I can find on stack Overflow and elsewhere, but have had no success.
If you have a know or maximum number of pairs AND you want to try the PIVOT
Example
Select *
From (
Select A.stu_id
,B.*
From (
Select *
,RN = row_number() over (partition by stu_id order by course_name)
from YourTable
) A
Cross Apply ( values (concat('course_',RN),course_name)
,(concat('staff_',RN),staff_name)
) B(Item,Value)
) src
Pivot (max(Value) for Item in ([Course_1],[Staff_1],[Course_2],[Staff_2],[Course_3],[Staff_3] )) pvt
Returns
I would suggest row_number() to enumerate the columns, then conditional aggregation:
select
stu_id,
max(case when rn = 1 then course_name end) course1,
max(case when rn = 1 then staff_name end) staff1,
max(case when rn = 2 then course_name end) course2,
max(case when rn = 2 then staff_name end) staff2,
...
max(case when rn = 6 then course_name end) course6,
max(case when rn = 6 then staff_name end) staff6
from (
select t.*, row_number() over(partition by stu_id order by course_name) rn
from mytable t
) t
group by stu_id
This gives you 6 pairs of columns for each stu_id, with the corresponding course and staff names. Courses are sorted alphabetically over the columns. If a student has less than 6 courses, the final columns will be empty.
I am quite unclear on how your query and your sample data relate. This answer is based on the sample data and expected results.

How to select varying count of items per colum value?

I work with Postgresql.
I have a sql code
SELECT lp."RegionId", COUNT(w."Id") FROM public.workplace w
GROUP BY lp."RegionId"
that returns to me
RegionId | Count
1 | 3
2 | 12
3 | 5
I have table 'person'. Each person have RegionId.
So i for region 1 i want to select first 3 persons, for region 2 select first 12 persons, for region 3 select first 5 persons.
So how can i use it as subquery to table 'person'?
WITH (SELECT lp."RegionId", COUNT(w."Id") FROM public.workplace w
GROUP BY lp."RegionId") AS pc
SELECT * FROM public.person p
???????
limit pc."Count"
???
Something like:
SELECT p.*
FROM (SELECT *, row_number() OVER (PARTITION BY RegionId ORDER BY PersonId) AS rn
FROM person) AS p
JOIN (SELECT RegionId, count(*) AS cnt
FROM workplace
GROUP BY RegionId) AS r ON p.RegionId = r.RegionId
WHERE p.rn <= r.cnt
ORDER BY p.RegionId, p.PersonId;

Avoid Unions to get TOP count

Here are two tables:
LocationId Address City State Zip
1 2100, 1st St Austin TX 76819
2 2200, 2nd St Austin TX 76829
3 2300, 3rd St Austin TX 76839
4 2400, 4th St Austin TX 76849
5 2500, 5th St Austin TX 76859
6 2600, 6th St Austin TX 76869
TripId PassengerId FromLocationId ToLocationId
1 746896 1 2
2 746896 2 1
3 234456 1 3
4 234456 3 1
5 234456 1 4
6 234456 4 1
7 234456 1 6
8 234456 6 1
9 746896 1 2
10 746896 2 1
11 746896 1 2
12 746896 2 1
I want TOP 5 locations which each passenger has traveled to (does not matter if its from or to location). I can get it using a UNION, but was wondering if there was a better way to do this.
My Solution:
select top 5 *
from
(select count(l.LocationId) as cnt, l.LocationId, l.Address1, l.Address2, l.City, St.State , l.Zip
from
Trip t
join LOCATION l on t.FromLocationId = l.LocationId
where t.PassengerId = 746896
group by count(l.LocationId) as cnt, l.LocationId, l.Address1, l.Address2, l.City, St.State , l.Zip
UNION
select count(l.LocationId) as cnt, l.LocationId, l.Address1, l.Address2, l.City, St.State , l.Zip
from
Trip t
join LOCATION l on t.ToLocationId = l.LocationId
where t.PassengerId = 746896
group by count(l.LocationId) as cnt, l.LocationId, l.Address1, l.Address2, l.City, St.State , l.Zip
) as tbl
order by cnt desc
This will give you top 5 location.
SELECT TOP 5 tmp.fromlocationid AS locationid,
Count(tmp.fromlocationid) AS Times
FROM (SELECT fromlocationid
FROM trip
UNION ALL
SELECT tolocationid
FROM trip) tmp
GROUP BY tmp.fromlocationid
Method 1: This will give you top 5 location of each passenger.
WITH cte AS
( SELECT passengerid,
locationid,
Count(locationid) AS Times,
Row_number() OVER(partition BY passengerid ORDER BY passengerid ASC) AS RowNum
FROM (SELECT tripid, passengerid, fromlocationid AS locationid
FROM trip
UNION ALL
SELECT tripid, passengerid, tolocationid AS locationid
FROM trip) tmp
GROUP BY passengerid, locationid )
SELECT *
FROM cte
WHERE rownum <= 5
ORDER BY passengerid, Times DESC
Method 2: Same result without Union Operator (Top 5 location of each passenger)
WITH cte AS
( SELECT passengerid,
locationid,
Count(locationid) AS Times,
Row_number() OVER(partition BY passengerid ORDER BY passengerid ASC) AS RowNum
FROM trip
UNPIVOT ( locationid
FOR subject IN (fromlocationid, tolocationid) ) u
GROUP BY passengerid, locationid )
SELECT *
FROM cte
WHERE rownum <= 5
ORDER BY passengerid, times DESC
If you also want to get the location details, you can simply join the location table.
SELECT cte.* , location.*
FROM cte
INNER JOIN location ON location.locationid = cte.locationid
WHERE rownum <= 5
ORDER BY passengerid, times DESC
Reference
- https://stackoverflow.com/a/19056083/6327676
YOou'll need to replace the SELECT *'s with the columns you need, however, something like this should work:
WITH Visits AS (
SELECT *,
COUNT(*) OVER (PARTITION BY t.PassengerID, L.LocationID) AS Visits
FROM Trip T
JOIN [Location] L ON T.FromLocationId = L.LocationId),
Rankings AS (
SELECT *,
DENSE_RANK() OVER (PARTITION BY V.PassengerID ORDER BY Visits DESC) AS Ranking
FROM Visits V)
SELECT *
FROM Rankings
WHERE Ranking <= 5;
Further simplified solution
select top 3 * from
(
Select distinct count(locationId) as cnt, locationId from trip
unpivot
(
locationId
for direction in (fromLocationId, toLocationId)
)u
where passengerId IN (746896, 234456)
group by direction, locationId
)as tbl2
order by cnt desc;
Solution combining columns
The main issue for me is avoiding union to combine the two columns.
The UNPIVOT command can do this.
select top 3 * from (
select count(locationId) cnt, locationId
from
(
Select valu as locationId, passengerId from trip
unpivot
(
valu
for loc in (fromLocationId, toLocationId)
)u
)united
where passengerId IN (746896, 234456)
group by locationId
) as tbl
order by cnt desc;
http://sqlfiddle.com/#!18/cec8b/136
If you want to get the counts by direction:
select top 3 * from (
select count(locationId) cnt, locationId, direction
from
(
Select valu as locationId, direction, passengerId from trip
unpivot
(
valu
for direction in (fromLocationId, toLocationId)
)u
)united
where passengerId IN (746896, 234456)
group by locationId, direction
) as tbl
order by cnt desc;
http://sqlfiddle.com/#!18/cec8b/139
Same Results as you ( minus some minor descriptions )
select top 3 * from
(
select distinct * from (
select count(locationId) cnt, locationId
from
(
Select valu as locationId, direction, passengerId from trip
unpivot
(
valu
for direction in (fromLocationId, toLocationId)
)u
)united
where passengerId IN (746896, 234456)
group by locationId, direction
) as tbl
)as tbl2
order by cnt desc;
You can do this without union all:
select top (5) t.passengerid, v.locationid, count(*)
from trip t cross apply
(values (fromlocationid), (tolocationid)) v(locationid) join
location l
on v.locationid = l.locationid
where t.PassengerId = 746896
group by t.passengerid, v.locationid
order by count(*) desc;
If you want an answer for all passengers, it would be a similar idea, using row_number(), but your query suggests you want the answer only for one customer at a time.
You can include additional fields from location as well.
Here is a SQL Fiddle.

Get the group which has maximum number of records in SQL Server

[ID] [Name] [Dept]
--------------------
1 Manu A
2 Anu A
3 Tanu A
4 Danu A
5 Anu B
6 Danu B
7 Danu C
8 Anu C
9 Tanu C
10 John C
11 Anu D
12 Jane D
13 Danu D
I need to get the Dept with maximum number of employees.
Here is what I tried
SELECT
ID, Name, Dept
FROM
(SELECT
*,
rn = ROW_NUMBER() OVER(PARTITION BY Dept)
FROM Emp) t
WHERE
rn = (SELECT MAX(rn) FROM t)
I need help in the WHERE clause.
You need aggregation to count the number of employees. The approach using row_number() is one approach, but with the right query:
SELECT Dept
FROM (SELECT Dept, COUNT(*) as cnt,
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM Emp
) e
WHERE seqnum = 1;
However, a more common approach would just use ORDER BY and TOP:
SELECT TOP (1) Dept, COUNT(*) as cnt
FROM emp
GROUP BY Dept
ORDER BY COUNT(*) DESC;
If you wanted ties, then you would use WITH TIES in the SELECT.
Selecting all departments having the same max number of employees:
;WITH c -- create a list of depts and number of emp's
AS (SELECT deptid,
Count(*) cnt
FROM emp
GROUP BY deptid)
SELECT d.*
FROM dept d
INNER JOIN c
ON d.deptid = c.deptid
WHERE c.cnt = (SELECT Max(cnt)
FROM c)
No WHERE is needed. Try using a GROUP BY
SELECT COUNT(Name) as NameCount, Dept from Table
GROUP BY Dept
ORDER BY COUNT(Name) DESC
The biggest group(s) will be at the top
Results
NameCount | Dept
4 A
4 C
3 D
2 B
Ok, now you added the table structure:
;WITH c
AS (SELECT Dept,
Count(*) cnt
FROM Emp
GROUP BY Dept)
SELECT c.*
FROM c
WHERE c.cnt = (SELECT Max(cnt)
FROM c)
I can't quite figure your table structure, but this selects the department with the most employees
SELECT * from Dept WHERE deptid = (
SELECT TOP 1 deptid FROM employees
GROUP BY deptid
ORDER BY COUNT(*) DESC
)

Select Max two rows of each account SQL Server

I have this table
ID AGE ACCNUM NAME
--------------------------------
1 10 55409 Intro
2 6 55409 Chapter1
3 4 55409 Chapter2
4 3 69591 Intro
5 6 69591 Outro
6 0 40322 Intro
And I need a query that returns the two max age from each ACCNUM
in this case, records:
1, 2, 4, 5, 6
I have tried too many queries but nothing works for me.
I tried this query
Select
T1.accnum, T1.age
from
table1 as T1
inner join
(select
accnum, max(age) as max
from table1
group by accnum) as T2 on T1.accnum = T2.accnum
and (T1.age = T2.max or T1.age = T2.max -1)
TSQL Ranking Functions: Row_Number() https://msdn.microsoft.com/en-us/library/ms186734.aspx
select id, age, accnum, name
from
(
select id, age, accnum, name, ROW_NUMBER() Over (Partition By accnum order by age desc) as rn
from yourtable
) a
where a.rn <= 2
You can use row_number():
select accnum
, age
from ( select accnum
, age
, row_number() over(partition by accnum order by age desc) as r
from table1 as T1) t where r < 3
CODE:
WITH CTE AS (SELECT ID, AGE, ACCNUM, NAME,
ROW_NUMBER() OVER(PARTITION BY ACCNUM ORDER BY AGE DESC) AS ROW_NUM
FROM T1)
SELECT ID, AGE, ACCNUM, NAME
FROM CTE
WHERE ROW_NUM <= 2
Uses a common table expression to achieve the desired result.
SQL Fiddle