Union two queries ordered by newid - sql

I have a table that stores employees (id, name, and gender). I need to randomly get two men and two women.
CREATE TABLE employees
(
id INT,
name VARCHAR (10),
gender VARCHAR (1),
);
INSERT INTO employees VALUES (1, 'Mary', 'F');
INSERT INTO employees VALUES (2, 'Jake', 'M');
INSERT INTO employees VALUES (3, 'Ryan', 'M');
INSERT INTO employees VALUES (4, 'Lola', 'F');
INSERT INTO employees VALUES (5, 'Dina', 'F');
INSERT INTO employees VALUES (6, 'Paul', 'M');
INSERT INTO employees VALUES (7, 'Tina', 'F');
INSERT INTO employees VALUES (8, 'John', 'M');
My attempt is the following:
SELECT TOP 2 *
FROM employees
WHERE gender = 'F'
ORDER BY NEWID()
UNION
SELECT TOP 2 *
FROM employees
WHERE gender = 'M'
ORDER BY NEWID()
But it doesn't work since I can't put two order by in the same query.

Why not just use row_number()? One method without a subquery is:
SELECT TOP (4) WITH TIES e.*
FROM employees
WHERE gender IN ('M', 'F')
ORDER BY ROW_NUMBER() OVER (PARTITION BY gender ORDER BY newid());
This is slightly less performant than using ROW_NUMBER() in a subquery.
Or, a fun method would use APPLY:
select e.*
from (values ('M'), ('F')) v(gender) cross apply
(select top (2) e.*
from employees e
where e.gender = v.gender
order by newid()
) e;

You cannot put an ORDER BY in the combinable query (the first one) of the UNION. However, you can use ORDER BY if you convert each one into a table expression.
For example:
select *
from (
SELECT TOP 2 *
FROM employees
WHERE gender = 'F'
ORDER BY newid()
) x
UNION ALL
select *
from (
SELECT TOP 2 *
FROM employees
WHERE gender = 'M'
ORDER BY newid()
) y
Result:
id name gender
--- ----- ------
5 Dina F
4 Lola F
2 Jake M
3 Ryan M
See running example at SQL Fiddle.

Related

SQL UNION query with order by giving syntax error on "("

I'm trying to select 2 oldest females and 2 oldest males using 1 query. The union keeps giving me a syntax error near "(". Both queries work independantly but after union I get error.
-- create a table
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
gender TEXT NOT NULL,
age INTEGER NOT NULL
);
-- insert some values
INSERT INTO students VALUES (1, 'Ryan', 'M', 23);
INSERT INTO students VALUES (2, 'Joanna', 'F', 22);
INSERT INTO students VALUES (3, 'Alex', 'F', 25);
INSERT INTO students VALUES (4, 'Ted', 'M', 21);
INSERT INTO students VALUES (5, 'June', 'F', 26);
INSERT INTO students VALUES (6, 'Rose', 'F', 24);
INSERT INTO students VALUES (7, 'Jack', 'M', 25);
-- select * from students;
SELECT * FROM
(SELECT name FROM students WHERE GENDER = 'F' ORDER BY age DESC LIMIT 2)
UNION
(SELECT name FROM students WHERE GENDER = 'M' ORDER BY age DESC LIMIT 2);
Your online compliler uses not MySQL but SQLite!
Execute select sqlite_version(); - the output is '3.31.1'.
Use this:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY gender ORDER BY age DESC) rn
FROM students
)
SELECT name
FROM cte
WHERE rn <= 2;
This code is correct for SQLite.
PS. Add ORDER BY if needed.
For SQLite, both unioned queries, since they contain an ORDER BY clause, must be used as subqueries with an external SELECT clause and you can use an ORDER BY clause at the end which will be applied to the resultset of the union and will put all Fs at the top because they are alphabetically less than the Ms:
SELECT * FROM (SELECT * FROM students WHERE gender = 'F' ORDER BY age DESC LIMIT 2)
UNION
SELECT * FROM (SELECT * FROM students WHERE gender = 'M' ORDER BY age DESC LIMIT 2)
ORDER BY gender, age;
See the demo.

How to SELECT record from a table where column is equal to a value but shouldn't be equal to another value?

I have listed two different solutions to the given problem. is there any better approach to solve these types of problems?
Q: GIVEN EMPLOYEE TABLE below, SELECT name whose id = 1 but not = 3
-- ID NAME
-- 1 ram
-- 2 shayam
-- 1 mohan
-- 7 mohan
-- 4 monu
-- 3 monu
-- 1 monu
-- 5 sonu
-- 1 sonu
-- 2 sonu
-- OUTPUT
-- mohan
-- ram
-- sonu
-- Solution 1:
SELECT DISTINCT(e1.NAME) FROM EMPLOYEE e1 JOIN EMPLOYEE e2 ON e1.name = e2.name WHERE e1.id = 1 AND e1.NAME NOT IN (
SELECT DISTINCT(e1.NAME) FROM EMPLOYEE e1 JOIN EMPLOYEE e2 ON e1.name = e2.name WHERE e2.id = 3);
-- Solution 2:
SELECT DISTINCT(e1.NAME) FROM EMPLOYEE e1 JOIN EMPLOYEE e2 ON e1.name = e2.name WHERE e1.id = 1
MINUS
SELECT DISTINCT(e1.NAME) FROM EMPLOYEE e1 JOIN EMPLOYEE e2 ON e1.name = e2.name WHERE e2.id = 3;
-- Use this code to test the logic:
CREATE TABLE EMPLOYEE( id INT, name VARCHAR(25) );
INSERT INTO EMPLOYEE(id, name) VALUES(1, 'ram');
INSERT INTO EMPLOYEE(id, name) VALUES(2, 'shayam');
INSERT INTO EMPLOYEE(id, name) VALUES(1, 'mohan');
INSERT INTO EMPLOYEE(id, name) VALUES(7, 'mohan');
INSERT INTO EMPLOYEE(id, name) VALUES(4, 'monu');
INSERT INTO EMPLOYEE(id, name) VALUES(3, 'monu');
INSERT INTO EMPLOYEE(id, name) VALUES(1, 'monu');
INSERT INTO EMPLOYEE(id, name) VALUES(5, 'sonu');
INSERT INTO EMPLOYEE(id, name) VALUES(1, 'sonu');
INSERT INTO EMPLOYEE(id, name) VALUES(2, 'sonu');
SELECT * FROM EMPLOYEE;
The minus is fine, but you don't need the select distinct. Minus is a set function that only returns distinct rows. I tend to use aggregation for this:
select e.name
from employee e
where id in (1, 3)
group by e.name
having max(id) = 1; -- there is no 3 if the max is 1
However, your methods are basically fine although I'll repeat that the select distincts are not necessary.

How to get unique records from 3 tables

I have 3 tables and I am trying to get unique results from all 3 tables (including other columns from each table).
I have tried union approach but that approach only works when I have single column selected from each table.
As soon as I want another corresponding column value from each table, I don't get unique values for the field I am trying to get.
Sample Database and query available here as well: http://www.sqlfiddle.com/#!18/1b9a6/10
Here is the example tables i have created.
CREATE TABLE TABLEA
(
id int,
city varchar(6)
);
INSERT INTO TABLEA ([id], [city])
VALUES
(1, 'A'),
(2, 'B'),
(3, 'C');
CREATE TABLE TABLEB
(
id int,
city varchar(6)
);
INSERT INTO TABLEB ([id], [city])
VALUES
(1, 'B'),
(2, 'C'),
(3, 'D');
CREATE TABLE TABLEC
(
id int,
city varchar(6)
);
INSERT INTO TABLEC ([id], [city])
VALUES
(1, 'C'),
(2, 'D'),
(2, 'E');
Desired result:
A,B,C,D,E
Unique city from all 3 table combined. By unique, I am referring to DISTINCT city from the combination of all 3 tables. Yes, the id is different for common values between tables but it doesn't matter in my use-case if id is coming from table A, B OR C, as long as I am getting DISTINCT (aka UNIQUE) city across all 3 tables.
I tried this query but no luck (city B is missing in the output):
SELECT city, id
FROM
(SELECT city, id
FROM TABLEA
WHERE city NOT IN (SELECT city FROM TABLEB
UNION
SELECT city FROM TABLEC)
UNION
SELECT city, id
FROM TABLEB
WHERE city NOT IN (SELECT city FROM TABLEA
UNION
SELECT city FROM TABLEC)
UNION
SELECT city, id
FROM TABLEC) AS mytable
try this. As this should give you distinct city with there first appear id:
select distinct min(id) over(partition by city) id, city from (
select * from TABLEA
union all
select * from TABLEB
union all
select * from TABLEC ) uni
You got the right idea, just wrap the UNION results in a subquery/temp table and then apply the DISTINCT
WITH TABLEE AS (
SELECT city, id FROM TABLEA
UNION
SELECT city, id FROM TABLEB
UNION
SELECT city, id FROM TABLEC
)
SELECT DISTINCT city
FROM TABLEE

Distinct with where condition

I have table as below:
I want to perform distinct on city but if city is duplicate then return row which having maximum ref_id. Result should contains all the columns.
Test data:
DECLARE #t_temp TABLE (ID smallint,
name varchar(10),
city varchar(10),
ref_id smallint);
INSERT INTO #t_temp
VALUES
(1, 'xyz', 'a', 101),
(2, 'pqr', 'a', 102),
(3, 'ijk', 'a', 103),
(4, 'abc', 'b', 104),
(5, 'ahg', 'c', 10);
Actual query:
SELECT ID
, name
, city
, ref_id
FROM (SELECT *
, ROW_NUMBER() OVER (PARTITION BY city ORDER BY ref_id DESC) Ranking
FROM #t_temp) base
WHERE Ranking = 1;
Result:
ID name city ref_id
------ ---------- ---------- ------
3 ijk a 103
4 abc b 104
5 ahg c 10
Basicly, what I'm doing is assigning a 'ranking' to all your records grouped by city and ordered by ref_id, and then retaining only the "number one" record. This is an alternative to what Rahul proposed, which is also a valid solution to your problem. The only difference between the two is that in Rahul's example he'll return multiple records if multiple exist with the same city and ref_id (considering it being the highest one), where the solution above will only return a single record. To reach the same behavior as Rahul, you can change the ROW_NUMBER() to RANK() or DENSE_RANK().
Try this:
Select tb1.* from Table1 as tb1
inner join (
Select city, Max(ref_id) as 'ref_id' from Table1 group by city
) as tb2
on tb1.city = tb2.city and tb1.ref_id = tb2.ref_id

How to get numeric range between row value SQL

I have a table which shows Grades and percentages.
Now I want to run query on table which fetch Grade between these percentages.
Example if a student get 72% I want to show the Grade as C.
How to get Grade from table?
Please refer this table picture:
Drop Table Grades
Drop Table Students
Create Table Students (Name Varchar(200), Percentage Numeric(5,2))
Insert Students Values ('John', 0.00)
Insert Students Values ('Jane', 38.00)
Insert Students Values ('Joe', 45.00)
Insert Students Values ('Greg', 50.00)
Insert Students Values ('Buck', 55.00)
Insert Students Values ('Harold', 60.00)
Insert Students Values ('Jack', 65.00)
Insert Students Values ('Bill', 68.00)
Insert Students Values ('Gerald', 75.00)
Insert Students Values ('Steve', 79.00)
Insert Students Values ('Walter', 85.00)
Insert Students Values ('Mike', 92.00)
Insert Students Values ('Mary', 100.00)
Insert Students Values ('Mark', 101.00)
Select * From Students
Create Table Grades (Grade Char(2), Percentage Numeric(5,2))
Go
Insert Grades Values ('A*', 101.00)
Insert Grades Values ('A', 85.00)
Insert Grades Values ('B', 75.00)
Insert Grades Values ('C', 65.00)
Insert Grades Values ('D', 55.00)
Insert Grades Values ('E', 45.00)
Insert Grades Values ('F', 0.00)
Select S.*, G.Grade
From
(
Select *, IsNull(Lead(Percentage) Over (Order By Percentage), (Select Max(Percentage)+.01 From Grades)) NextPercentage
From Grades ) G
Join Students S On S.Percentage >= G.Percentage And S.Percentage < G.NextPercentage
ORDER BY Percentage DESC with <= the percentage in WHERE and TOP 1 Grade will given the expected result
CREATE TABLE #GradeMaster (Grade VARCHAR(2), Percentage DECIMAL(5,2))
INSERT INTO #GradeMaster
SELECT 'A*', 101 UNION
SELECT 'A', 85 UNION
SELECT 'B', 75 UNION
SELECT 'C', 65 UNION
SELECT 'D', 55 UNION
SELECT 'E', 45 UNION
SELECT 'F', 0
SELECT TOP 1 Grade
FROM #GradeMaster
WHERE Percentage <= 72
ORDER BY Percentage DESC
DROP TABLE #GradeMaster
select grade from table1 where precentage in (
select max(percentage) from table1 where 72 > percentage);
You can substitute 72 for whatever score you like. There may be a way to do it without the 2 selects, but this should work.
You can use a order by limit 1
select grade from my_table
where percentage <= 72
order by percentage desc
limit 1;
Assuming there might also be a student table and assignment table ... I would think the lookup query would look something like this. The below will give you all students regardless of whether they have any graded assignments. Alternatively, you could join the student table directly if you have an overall grade already aggregated.
SELECT
S.*,
A.*,
G.grade
FROM
Student S
LEFT OUTER JOIN Assignment A ON S.Student_id = A.Student_id
LEFT OUTER JOIN Grade G ON A.Percentage >= G.Percentage AND A.Percentage < G.Percentage