plsql append rows together from select statements (similar to rbind) - sql

Say I have a table:
CREATE TABLE Test_Scores
( last_name VARCHAR2(40),
score number(10));
And I want to select the top and bottom scores into a table. The two queries would be:
select last_name,score, 'MAX' as Score_Type from Test_Scores
where score = (select max(score) from Test_Scores)
-> Last_name | Score | Score_Type
Smith | 15 | Max
select last_name,score, 'MIN' as Score_Type from Test_Scores
where score = (select min(score) from Test_Scores)
-> Last_name | Score | Score_Type
Jones | 5 | Min
How do I set these two rows together? In SAS I would use a set statement, and in R I would use rbind. Is there a pl sql equivalent (Oracle 11g)?
The final output would be:
Last_name | Score | Score_Type
Smith | 15 | Max
Jones | 5 | Min
Thanks in advance.

This can be done using plain SQL and a window (aka "analytic") function. There is no need for PL/SQL here (PL/SQL is only used for stored procedures)
select last_name,
score
from (
select last_name,
score,
dense_rank() over (order by score desc) as ranked_first,
dense_rank() over (order by score asc) as ranked_last
from test_scores
)
where ranked_first = 1
or ranked_last = 1;

Related

Row Number by Certain Column in SQL

I have a table that contains customer transactions. It looks like this:
Tha data is sorted by Total Transaction. I want to create a column that contains number by City. For Example, the first row shows City is London so the values is 1, second row becaus it's from London too, the value is also 1. When the Next Row is not London, the value is 2. So it looks like this:
Is there a way to create that row number in SQL Server?
You can try using dense_rank()
select *,dense_rank() over(order by city) as cityNumber
from tablename
order by total_transaction desc
I believe the question is valid and as per my understanding on the requirement , you need a two level of sub query to get to the final result,
Here I have used max as the data first has to be sorted by Total Transaction and then we can use dense_rank to give a row number using the max value and city.
select t.city as "City"
,dense_rank() over (order by max_total_per_city desc,city) as "City Number"
,t.customer as "Customer"
,t.total_transaction as "Total Transaction"
from
(
select *
,max(total_transaction) over (partition by city) as max_total_per_city
from tableName t
) t
order by total_transaction desc
You can get the CityNumbers with ROW_NUMBER() window function:
select City, row_number() over (order by max(TotalTransaction) desc) CityNumber
from tablename
group by City
so you can join the above query to the table:
select t.City, c.CityNumber, t.Customer, t.Totaltransaction
from tablename t inner join (
select City, row_number() over (order by max(TotalTransaction) desc) CityNumber
from tablename
group by City
) c on c.City = t.City
order by t.TotalTransaction desc
Or with DENSE_RANK() window function:
select t.City,
dense_rank() over (order by (select max(TotalTransaction) from tablename where City = t.City) desc) as cityNumber,
t.Customer,
t.TotalTransaction
from tablename t
order by t.TotalTransaction desc
See the demo.
Results:
> City | CityNumber | Customer | Totaltransaction
> :--------- | ---------: | :------- | ---------------:
> London | 1 | Michael | 250
> London | 1 | Edward | 180
> Paris | 2 | Michael | 160
> Madrid | 3 | Luis | 153
> London | 1 | Serena | 146
> Madrid | 3 | Lionel | 133
> Manchester | 4 | Frank | 96

Reverse col and rows in SQL

I have to create query, to reverse rows and cols correct. I am using MS SQL SERVER 2016.
This is what I have:
Row_ID | Group_ID | Group_Status | MemberRole | name
2807 | 10568 | accept | chairman | Rajah
2808 | 10568 | accept | member | Vaughan
2812 | 10568 | accept | secretary | Susan
This is what I need:
Group_ID | Status | Chairman | Secretary | Member1 | Member2 | Member3 | ... | Member20
10568 | Accept | Rajah | Susan | Vaughan | Kane | Oprah | ... | Imelda
(users with member role can be between 0-20)
Probably I should use pivot, but I have no idea how.
Ok, I have this code:
SELECT *
FROM
(
SELECT group_id,
group_status,
memberRole,
name
FROM DataGroup
) dataSource PIVOT(MAX(name) FOR memberRole IN([chairman],
[secretary],
[member])) pivotTab;
But I losing rows with members (get only one member), how to extract them to columns?
You can try this with a unioned query:
Some mockup (please provide such a dummy table with your sample data yourself in your next question):
DECLARE #mockup TABLE(Row_ID INT,Group_ID INT,Group_Status VARCHAR(100),MemberRole VARCHAR(100),[name] VARCHAR(100));
INSERT INTO #mockup VALUES
(2807,10568,'accept','chairman','Rajah')
,(2808,10568,'accept','member','Vaughan')
,(2812,10568,'accept','secretary','Susan')
,(2899,10568,'accept','member','Onemore');
--The query
SELECT p.*
FROM
(
SELECT Group_ID
,Group_Status
,[name]
,MemberRole
FROM #mockup
WHERE MemberRole IN('chairman','secretary')
UNION ALL
SELECT Group_ID
,Group_Status
,[name]
,CONCAT('Member',ROW_NUMBER() OVER(PARTITION BY Group_ID ORDER BY Row_ID))
FROM #mockup
WHERE MemberRole='member'
) t
PIVOT
(
MAX([name]) FOR MemberRole IN(Chairman,Secretary,Member1,Member2,Member3 /*add as many as you need*/)
) p;
The result
Group_ID Group_Status Chairman Secretary Member1 Member2 Member3
10568 accept Rajah Susan Vaughan Onemore NULL
In short:
The first part of the query will Just pick the two fix names.
The second part will pick the members and number them sorted by their Row_ID.
The PIVOT will then transform this to a single row, using the column MemberRole for the new column names.
You will have to think about some more things:
What if not all the lines are accepted?
What of there are many groups?
If you need help, you can comeback with a new question. Happy Coding!
I would simply use conditional aggregation:
select group_id, group_status,
max(case when member_role = 'chairman' then name end) as chairman,
max(case when member_role = 'secretary' then name end) as secretary,
max(case when member_role = 'member' and seqnum = 1 then name end) as member_01,
max(case when member_role = 'member' and seqnum = 2 then name end) as member_02,
. . .
from (select m.*,
row_number() over (partition by group_id, member_role order by row_id) as seqnum
from #mockup m
) m
group by group_id, group_status;
I find conditional aggregation to be much more flexible than pivot. This is an example of the situation where the query is simpler.

SQL: SyntaxError: order by with row_number() function

I have the following table Employee:
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 150 |
| 2 | 290 |
| 3 | 302 |
+----+--------+
I am using the following code to find the second highest salary:
with t as
(
select
Salary,
row_number() over (order by Salary desc) as salary_ord
from
Employee
)
select Salary
from t
where salary_ord == 2
However, I get an error:
SyntaxError: near 't as (
select Salary, row_number() over (order by Salary desc) as salary_ord'
What did I do wrong here? Thanks!
In SQL, the correct comparison operator is =, not ==. So, this is the ANSI SQL version of your query:
with t as (
select Salary, row_number() over (order by Salary desc) as salary_ord
from Employee
)
select Salary
from t
where salary_ord = 2;
However, your error suggests that your database doesn't support with or window functions.
In SQL Server,
You can do:
select top 1 Salary
from Employee
order by Salary desc
offset 1 row fetch next 1 row only

SQL group by with a count

I have a table (simplified below)
|company|name |age|
| 1 | a | 3 |
| 1 | a | 3 |
| 1 | a | 2 |
| 2 | b | 8 |
| 3 | c | 1 |
| 3 | c | 1 |
For various reason the age column should be the same for each company. I have another process that is updating this table and sometimes it put an incorrect age in. For company 1 the age should always be 3
I want to find out which companies have a mismatch of age.
Ive done this
select company, name age from table group by company, name, age
but dont know how to get the rows where the age is different. this table is a lot wider and has loads of columns so I cannot really eyeball it.
Can anyone help?
Thanks
You should not be including age in the group by clause.
SELECT company
FROM tableName
GROUP BY company, name
HAVING COUNT(DISTINCT age) <> 1
SQLFiddle Demo
If you want to find the row(s) with a different age than the max-count age of each company/name group:
WITH CTE AS
(
select company, name, age,
maxAge=(select top 1 age
from dbo.table1 t2
group by company,name, age
having( t1.company=t2.company and t1.name=t2.name)
order by count(*) desc)
from dbo.table1 t1
)
select * from cte
where age <> maxAge
Demontration
If you want to update the incorrect with the correct ages you just need to replace the SELECT with UPDATE:
WITH CTE AS
(
select company, name, age,
maxAge=(select top 1 age
from dbo.table1 t2
group by company,name, age
having( t1.company=t2.company and t1.name=t2.name)
order by count(*) desc)
from dbo.table1 t1
)
UPDATE cte SET AGE = maxAge
WHERE age <> maxAge
Demonstration
Since you mentioned "how to get the rows where the age is different" and not just the comapnies:
Add a unique row id (a primary key) if there isn't already one. Let's call it id.
Then, do
select id from table
where company in
(select company from table
group by company
having count(distinct age)>1)

Counting number of medals in weekly tournaments

I have a table holding weekly scores of players:
# select * from pref_money limit 5;
id | money | yw
----------------+-------+---------
OK32378280203 | -27 | 2010-44
OK274037315447 | -56 | 2010-44
OK19644992852 | 8 | 2010-44
OK21807961329 | 114 | 2010-44
FB1845091917 | 774 | 2010-44
(5 rows)
This SQL statement gets me the weekly winners and how many times each player has won:
# select x.id, count(x.id) from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money
) x
where x.ranking = 1 group by x.id;
id | count
------------------------+-------
OK9521784953 | 1
OK356310219480 | 1
MR797911753357391363 | 1
OK135366127143 | 1
OK314685454941 | 1
OK308121034308 | 1
OK4087658302 | 5
OK452217781481 | 6
....
I would like to save the latter number in the medals column of the players table:
# \d pref_users;
Table "public.pref_users"
Column | Type | Modifiers
------------+-----------------------------+--------------------
id | character varying(32) | not null
first_name | character varying(64) |
last_name | character varying(64) |
city | character varying(64) |
medals | integer | not null default 0
How to do this please? I can only think of using a temporary table, but there must be an easier way... Thank you
UPDATE:
The query suggested by Clodoaldo works, but now my cronjob occasionally fails with:
/* reset and then update medals count */
update pref_users set medals = 0;
psql:/home/afarber/bin/clean-database.sql:63: ERROR: deadlock detected
DETAIL: Process 31072 waits for ShareLock on transaction 124735679; blocked by process 30368.
Process 30368 waits for ShareLock on transaction 124735675; blocked by process 31072.
HINT: See server log for query details.
update pref_users u
set medals = s.medals
from (
select id, count(id) medals
from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money where yw <> to_char(CURRENT_TIMESTAMP, 'IYYY-IW')
) x
where ranking = 1
group by id
) s
where u.id = s.id;
update pref_users u
set medals = s.medals
from (
select id, count(id) medals
from (
select id,
row_number() over(partition by yw order by money desc) as ranking
from pref_money
) x
where ranking = 1
group by id
) s
where u.id = s.id
You could create a view which uses your "medal-select" and joins it with the actual data:
CREATE VIEW pref_money_medals AS
SELECT *
FROM pref_money
JOIN (SELECT count(x.id)
FROM (SELECT id, row_number()
OVER(PARTITION BY yw ORDER BY money DESC) AS ranking
FROM pref_money
) x
WHERE x.ranking = 1 group by x.id) medals
ON pref_money.id = medals.id;