Find number of position n each deparment - oracle

Tables CRIMES, SUSPECTS
How many women and how many men have committed crimes in the car theft.
table crimes:
1 2 3 so on...
table Suspects:
1 2 3 so on...
I made some work, but my soulition did not accepted by SQL
select name, sex, crime_type, count(case when sex='Male' then 1 end)
as male_cnt, count(case when sex='Female' then 1 end) as female_cnt
from suspects NATURAL JOIN crimes where crime_type='Car Theft' group
by name, sex;
Blockquote

How many women and how many men have committed crimes in the car theft.
The question only asks for the gender and the count so you do not need to include the name or the crime type in the output.
If you want a single row then you want to aggregate over the entire result-set and can use your conditional aggregation for each gender:
select count(case when s.sex='Male' then 1 end) as male_cnt,
count(case when s.sex='Female' then 1 end) as female_cnt
from suspects s
INNER JOIN crimes c
ON (s.suspect_id = c.suspect_id)
where c.crime_type='Car Theft';
or, if you want one row-per-gender then:
select s.sex,
count(*) as cnt
from suspects s
INNER JOIN crimes c
ON (s.suspect_id = c.suspect_id)
where c.crime_type='Car Theft'
GROUP BY s.sex;

Related

Count(*) in Group By - Returns 1, instead of '0'

I am using the below oracle query to get the count of rows.
SELECT T.ID,T.NAME,COUNT(*) AS NO_OF_STUDENTS FROM STUDENT S RIGHT JOIN
TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID,T.NAME ORDER BY T.ID
Actual Result Should be:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 0 STUDENT
The Result what i am getting is:
TEACHER 1 - 10 STUDENTS
TEACHER 2 - 5 STUDENTS
TEACHER 3 - 1 STUDENT
Since TEACHER 3 is not having any student, the result should be 0 Student. But i am getting the result as 1 Student.
You need to count a specific column (not use *, which includes nulls) in the table being outer joined to - since that is the one that might not have matching data. So:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
The only difference is COUNT(S.ID) instead of COUNT(*).
Simple demo with made-up data provided via CTEs:
with teacher (id, name) as (
select 1, 'Teacher 1' from dual
union all select 2, 'Teacher 2' from dual
union all select 3, 'Teacher 3' from dual
),
student (id, teacher_id) as (
select level, 1 from dual connect by level <= 10
union all
select level + 10, 2 from dual connect by level <= 5
)
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM STUDENT S
RIGHT JOIN TEACHER T ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID;
ID NAME NO_OF_STUDENTS
---------- --------- --------------
1 Teacher 1 10
2 Teacher 2 5
3 Teacher 3 0
You could also do this as a left join, which I find more intuitive:
SELECT T.ID, T.NAME, COUNT(S.ID) AS NO_OF_STUDENTS
FROM TEACHER T
LEFT JOIN STUDENT S ON S.TEACHER_ID = T.ID
GROUP BY T.ID, T.NAME
ORDER BY T.ID
which gets the same result.

Randomize Two Data Sets

I am trying to come up with a way to assign two people together from a larger dataset of about 6 people. I was toying around with the random() function in postgres but had no luck. I have access to postgres or oracle whichever may be easier to accomplish this.
For example if I had 6 names I'd like to take those 6 names and assign them to one another using some sort of randomizing query:
Billy
Bob
Joe
Sam
John
Alex
The output would be something along the lines of:
Orignal Name | Match
Billy | Alex
Bob | Joe
Joe | John
Sam | Bob
John | Billy
Alex | Sam
Any help would be greatly appreciated!
Thank you.
In postgres you could generate a row_number() on a random number and then join on that. This is nice and fast, but it could cause people to be buddied up with themselves:
SELECT t1.name, t2.name
FROM (SELECT row_number() OVER (ORDER BY random()) as id, name FROM table) t1
INNER JOIN (SELECT row_number() OVER (order by random()) as id, name FROM table) t2
ON t1.id = t2.id;
Here is a method using the cartesian product that results from joining the table to itself. This is not a nice solution if the data is huge as there is an intermediate result set that is (N * (N - 1)) rows, but noone will get matched up with themselves:
SELECT name1,
name2
FROM (
SELECT t1.NAME name1,
t2.NAME name2,
row_number() OVER (PARTITION BY t1.NAME ORDER BY random()) AS rn
FROM yourtable t1,
yourtable t2
WHERE t1.NAME <> t2.NAME
) subquery
WHERE rn = 1;
Here is a hybrid of the two. Joining the table to itself on a range of randomly generated ids, also specifying that the names don't match. The intermediate result set will have 1-3 randomly chosen names from t2 for each name in t1. Then we just grab one at random. This has an intermediate result set which will ALWAYS be less than (N*3) records which isn't too bad.
UPDATE: this will, however, match the same person up multiple times... Leaving it here in case it spawns so good idea for that INNER JOIN that prevents that from happening.
WITH randnames AS
(
SELECT row_number() OVER (ORDER BY random()) AS id,
NAME
FROM yourtable
)
SELECT name1, name2
FROM (
SELECT t1.NAME name1,
t2.NAME name2,
ROW_NUMBER() OVER (PARTITION BY t1.NAME ORDER BY 1) AS rn
FROM randnames t1
INNER JOIN randnames t2
ON t1.NAME <> t2.NAME
AND t2.id BETWEEN t1.id - 1 AND t1.id + 1
) subquery
WHERE rn = 1;
I feel like there is probably some prettier way to do this, but the complete lack of answers on this question an hour after it was asked suggests that it's not an easy problem to solve in SQL.

Left Outer Join returns duplicate records - Oracle

Scenario :
Join Table ORDER with Table COST
where COST has multiple rows for a single reference from Table ORDER
Desired outcome:
Return single row per Order with its associated costs.
ID NAME PRICE GST
1 Book 100 10
2 CD 50 5
Ex:
Table ORDER
ID NAME COST
1 Book 110
2 CD 55
Table COST
ID ORDER_ID COST_TYPE VALUE
1 1 PRICE 100
2 1 GST 10
3 2 PRICE 50
4 2 GST 5
LEFT OUTER JOIN returns multiple rows when below condition is used
SELECT * from ORDER
LEFT OUTER JOIN COST
ON ORDER.ID = COST.ORDER_ID
select o.id, o.name, c.price, c.gst
from order o left outer join
( select order_id,
sum(case when cost_type = 'PRICE' then value end) as price,
sum(case when cost_type = 'GST' then value end) as gst
from cost
group by order_id
) c
on o.id = c.order_id
;
So Select ORDER.ID, COST.COST_TYPE, COST.VALUE from ORDERS LEFT OUTER JOIN COST ON ORDER.ID = COST.ORDER_ID and COST.COST_TYPE = 'PRICE'
If you don't specify COST_TYPE then it will return multiple rows because ORDER_ID repeats on your COST TABLE.
This is what i found working in my case.
Had to use 2 LEFT OUTER JOIN's and alias to get it working
SELECT ID, NAME, PRICE.value as PRICE, GST.value as GST
from ORDER
LEFT OUTER JOIN COST as PRICE
ON ORDER.ID = COST.ORDER_ID
AND PRICE.COST_TYPE = 'PRICE'
LEFT OUTER JOIN COST as GST
ON ORDER.ID = COST.ORDER_ID
AND GST.COST_TYPE = 'GST'

Selecting one random data from a column from multiple rows in oracle

I am creating a view that needs to select only one random row for each customer. Something like:
select c.name, p.number
from customers c, phone_numbers p
where p.customer_id = c.id
If this query returns:
NAME NUMBER
--------- ------
Customer1 1
Customer1 2
Customer1 3
Customer2 4
Customer2 5
Customer3 6
I need it to be something like:
NAME NUMBER
--------- ------
Customer1 1
Customer2 4
Customer3 6
Rownum wont work because it will select only the first from all 6 records, and i need the first from each customer. I need solution that won't affect performance much, because the query that selects the data is pretty complex, this is just an example to explain what I need. Thanks in advance.
Use the ROW_NUMBER() analytic function:
SELECT name,
number
FROM (
SELECT c.name,
p.number,
ROW_NUMBER() OVER ( PARTITION BY c.id ORDER BY DBMS_RANDOM.VALUE ) AS rn
FROM customers c
INNER JOIN phone_numbers p
ON ( p.customer_id = c.id )
)
WHERE rn = 1
You can use group by clause to return only one phone number:
select c.name, MAX(p.number) as phone
from customers c, phone_numbers p
where p.customer_id = c.id
group by c.name
You can also use the min or max aggregate function with the keep dense_rank syntax:
select c.name,
min(p.number) keep (dense_rank last order by dbms_random.value) as number
from customers c
join phone_numbers p on p.customer_id = c.id
group by c.id, c.name
order by c.name;
(number isn't a valid column or alias name as it's a reserved word, so use your own real name of course).
If the phone number needs to be arbitrary rather than actually random, you can order by something else:
select c.name,
min(p.number) keep (dense_rank last order by null) as number
from customers c
join phone_numbers p on p.customer_id = c.id
group by c.id, c.name
order by c.name;
You'll probably get the same number back for each customer each time, but not always, and data/stats/plan changes will affect which you see. It seems like you don't care though. But then, using a plain aggregate might work just as well for you, as in #under's answer.
If you're getting lots of columns from that random row, rather than just the phone number, MTO's subquery might be simpler; for one or two values I find this a bit clearer though.

Selecting rows that has exactly same data as other table

I have these tables:
Products, Articles, Product_Articles
Lets say, product_ids are: p1 , p2 article_ids are: a1 , a2 , a3
product_articles is:
(p1,a1)
(p1,a2)
(p2,a1)
(p2,a1)
(p2,a2)
(p2,a3)
How to query for product_id, which has only a1,a2, nothing less, nothing more?
UPDATED Try
SELECT p.*
FROM products p JOIN
(
SELECT product_id
FROM product_articles
GROUP BY product_id
HAVING COUNT(*) = SUM(CASE WHEN article_id IN (1, 2) THEN 1 ELSE 0 END)
AND SUM(CASE WHEN article_id IN (1, 2) THEN 1 ELSE 0 END) = 2
) q ON p.product_id = q.product_id
or
SELECT p.*
FROM products p JOIN
(
SELECT product_id, COUNT(*) a_count
FROM product_articles
WHERE article_id IN (1, 2)
GROUP BY product_id
HAVING COUNT(*) = 2
) a ON p.product_id = a.product_id JOIN
(
SELECT product_id, COUNT(*) total_count
FROM product_articles
GROUP BY product_id
) b ON p.product_id = b.product_id
WHERE a.a_count = b.total_count
Here is SQLFiddle demo for both queries
This is an example of a "set-within-sets" subquery. I advocate using aggregation with a having clause for the logic, because this is the most general way to express the relationships.
The idea is that you can count the appearance of the articles within a product (in this case) in a way similar to using a where statement. The code is a bit more complex, but it offers flexibility. In your case, this would be:
select pa.product_id
from product_articles pa
group by pa.product_id
having sum(case when pa.article_id = 'a1' then 1 else 0 end) > 0 and
sum(case when pa.article_id = 'a2' then 1 else 0 end) > 0 and
sum(case when pa.article_id not in ('a1', 'a2') then 1 else 0 end) = 0;
The first two clauses count the appearance of the two articles, making sure that there is at least one occurrence of each. The last counts the number of rows without those two articles, making sure there are none.
You can see how this easily generalizes to more articles. Or to queries where you have "a1" and "a2" but not "a3". Or where you have three of four of specific articles, and so on.
I believe this can be done entirely using relational joins, as follows:
SELECT DISTINCT pa1.PRODUCT_ID
FROM PRODUCT_ARTICLES pa1
INNER JOIN PRODUCT_ARTICLES pa2
ON (pa2.PRODUCT_ID = pa1.PRODUCT_ID)
LEFT OUTER JOIN (SELECT *
FROM PRODUCT_ARTICLES
WHERE ARTICLE_ID NOT IN (1, 2)) pa3
ON (pa3.PRODUCT_ID = pa1.PRODUCT_ID)
WHERE pa1.ARTICLE_ID = 1 AND
pa2.ARTICLE_ID = 2 AND
pa3.PRODUCT_ID IS NULL
SQLFiddle here.
The inner join looks for products associated with the articles we care about (articles 1 and 2 - produces product 1 and 2). The left outer looks for products associated with articles we don't care about (anything article except 1 and 2) and then only accepts products which don't have any unwanted articles (i.e. pa3.PRODUCT_ID IS NULL, indicating that no row from pa3 was joined in).

Resources