SQL Inner/Sub group wise Query for a specific scenario - sql

I have a table as below
Schema: ID | Category | Keyword | Bid Price
Write a sql to fetch out top 5 keywords based on the bid price per category.
Details:
Table definition considered on oracle 10.x:
create table test (
ID number,
Category varchar (20),
Keyword varchar (20),
BidPrice number
);
Data:
insert into test values (1, 'Category-A', 'Keyword-A1', 110);
insert into test values (2, 'Category-A', 'Keyword-A2', 121);
insert into test values (3, 'Category-A', 'Keyword-A3', 130);
insert into test values (4, 'Category-A', 'Keyword-A4', 125);
insert into test values (5, 'Category-A', 'Keyword-A5', 115);
insert into test values (6, 'Category-A', 'Keyword-A6', 133);
insert into test values (7, 'Category-B', 'Keyword-B1', 105);
insert into test values (8, 'Category-B', 'Keyword-B2', 111);
insert into test values (9, 'Category-B', 'Keyword-B3', 108);
insert into test values (10, 'Category-B', 'Keyword-B4', 128);
insert into test values (11, 'Category-B', 'Keyword-B5', 144);
insert into test values (12, 'Category-B', 'Keyword-B6', 101);
insert into test values (13, 'Category-C', 'Keyword-C1', 150);
insert into test values (14, 'Category-C', 'Keyword-C2', 137);
insert into test values (15, 'Category-C', 'Keyword-C3', 126);
insert into test values (16, 'Category-C', 'Keyword-C4', 121);
insert into test values (17, 'Category-C', 'Keyword-C5', 112);
insert into test values (18, 'Category-C', 'Keyword-C6', 118);
Output expected:
KEYWORD CATEGORY BIDPRICE
-------------- ------------ ----------
Keyword-A6 Category-A 133
Keyword-A3 Category-A 130
Keyword-A4 Category-A 125
Keyword-A2 Category-A 121
Keyword-A5 Category-A 115
Keyword-B5 Category-B 144
Keyword-B4 Category-B 128
Keyword-B2 Category-B 111
Keyword-B3 Category-B 108
Keyword-B1 Category-B 105
Keyword-C1 Category-C 150
Keyword-C2 Category-C 137
Keyword-C3 Category-C 126
Keyword-C4 Category-C 121
Keyword-C6 Category-C 118
Note: Answer has to be only in SQL without use of any Oracle or database specific function.

try this:
select KEYWORD, CATEGORY ,BIDPRICE
from
(
select KEYWORD, CATEGORY ,BIDPRICE ,
ROW_NUMBER() over (partition by Category order by BidPrice desc) as rn
from test
) a
where a.rn <= 5;
Sql fiddle demo

make use of ROW_NUMBER()
SELECT KEYWORD ,CATEGORY, BIDPRICE
FROM
(
SELECT KEYWORD ,CATEGORY, BIDPRICE,
ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY BIDPRICE DESC) rn
FROM test
) a
WHERE rn <= 5
ORDER BY CATEGORY, BIDPRICE DESC
SQLFiddle Demo

I made this query but it is leaving the 6th row if it is biggest. Can any one help on this?
select a.keyword, a.category, a.bidprice
from
test a,
(
select b.id, b.category
from test b
where (select distinct(c.category) from test c where b.category=c.category) = b.category
) xx
where a.id in (select ss.id from test ss where a.category=ss.category and rownum<=5)
group by a.category, a.bidprice, a.keyword
order by a.category, a.bidprice desc;
Guys,
I solved it, please consider this:
select a.keyword, a.category, a.bidprice
from
test a,
(
select b.id, b.category
from test b
where (select distinct(c.category) from test c where b.category=c.category) = b.category
order by b.bidprice desc
) xx
where a.id in (
select id
from (select dd.id, dd.category from test dd order by dd.bidprice desc) ss
where a.category=ss.category
and rownum<=5
)
group by a.category, a.bidprice, a.keyword
order by a.category, a.bidprice desc;

Related

SQL: Create multiple columns from a single column

The input data:
The query for the input data-
CREATE TABLE CustTable
(CustNum NUMBER(5),
CntYear NUMBER(4),
TotalBill NUMBER(4));
INSERT INTO CustTable (CustNum, CntYear, TotalBill)
VALUES (101, 2013, 800);
INSERT INTO CustTable (CustNum, CntYear, TotalBill)
VALUES (101, 2015, 700);
INSERT INTO CustTable (CustNum, CntYear, TotalBill)
VALUES (102, 2013, 900);
INSERT INTO CustTable (CustNum, CntYear, TotalBill)
VALUES (102, 2015, 1000);
I want to get the following output-
Thank you.
How about this?
select a.custnum, a.cntyear CurrContractYear, a.totalbill CurrBill,
b.cntyear NextContractYear, b.totalbill NextBill
from CustTable a inner join CustTable b
on a.CustNum = b.CustNum
where b.CntYear > a.CntYear;
This version should work for cases where there are more than 2 distinct contract years for a given customer. It's also more efficient.
select * from (
select a.custnum, a.cntyear CurrContractYear, a.totalbill CurrBill,
lead(a.cntyear,1) over ( partition by a.custnum order by a.cntyear) NextContractYear,
lead(a.totalbill,1) over ( partition by a.custnum order by a.cntyear) NextBill
from CustTable a
)
where nextcontractyear is not null
order by 1,2;

oracle correlated subquery using distinct listagg

I have an interesting query I'm trying to figure out. I have a view which is getting a column added to it. This column is pivoted data coming from other tables, to form into a single row. Now, I need to wipe out duplicate entries in this pivoted data. Listagg is great for getting the data to a single row, but I need to make it unique. While I know how to make it unique, I'm tripping up on the fact that correlated sub-queries only go 1 level deep. So... not really sure how to get a distinct list of values. I can get it to work if I don't do the distinct just fine. Anyone out there able to work some SQL magic?
Sample data:
drop table test;
drop table test_widget;
create table test (id number, description Varchar2(20));
create table test_widget (widget_id number, test_fk number, widget_type varchar2(20));
insert into test values(1, 'cog');
insert into test values(2, 'wheel');
insert into test values(3, 'spring');
insert into test_widget values(1, 1, 'A');
insert into test_widget values(2, 1, 'A');
insert into test_widget values(3, 1, 'B');
insert into test_widget values(4, 1, 'A');
insert into test_widget values(5, 2, 'C');
insert into test_widget values(6, 2, 'C');
insert into test_widget values(7, 2, 'B');
insert into test_widget values(8, 3, 'A');
insert into test_widget values(9, 3, 'C');
insert into test_widget values(10, 3, 'B');
insert into test_widget values(11, 3, 'B');
insert into test_widget values(12, 3, 'A');
commit;
Here is an example of the query that works, but shows duplicate data:
SELECT A.ID
, A.DESCRIPTION
, (SELECT LISTAGG (WIDGET_TYPE, ', ') WITHIN GROUP (ORDER BY WIDGET_TYPE)
FROM TEST_WIDGET
WHERE TEST_FK = A.ID) widget_types
FROM TEST A
Here is an example of what does NOT work due to the depth of where I try to reference the ID:
SELECT A.ID
, A.DESCRIPTION
, (SELECT LISTAGG (WIDGET_TYPE, ', ') WITHIN GROUP (ORDER BY WIDGET_TYPE)
FROM (SELECT DISTINCT WIDGET_TYPE
FROM TEST_WIDGET
WHERE TEST_FK = A.ID))
WIDGET_TYPES
FROM TEST A
Here is what I want displayed:
1 cog A, B
2 wheel B, C
3 spring A, B, C
If anyone knows off the top of their head, that would fantastic! Otherwise, I can post up some sample create statements to help you with dummy data to figure out the query.
You can apply the distinct in a subquery, which also has the join - avoiding the level issue:
SELECT ID
, DESCRIPTION
, LISTAGG (WIDGET_TYPE, ', ')
WITHIN GROUP (ORDER BY WIDGET_TYPE) AS widget_types
FROM (
SELECT DISTINCT A.ID, A.DESCRIPTION, B.WIDGET_TYPE
FROM TEST A
JOIN TEST_WIDGET B
ON B.TEST_FK = A.ID
)
GROUP BY ID, DESCRIPTION
ORDER BY ID;
ID DESCRIPTION WIDGET_TYPES
---------- -------------------- --------------------
1 cog A, B
2 wheel B, C
3 spring A, B, C
I was in a unique situation using the Pentaho reports writer and some inconsistent data. The Pentaho writer uses Oracle to query data, but has limitations. The data pieces were unique but not classified in a consistent manner, so I created a nested listagg inside of a left join to present the data the way I wanted to:
left join
(
select staff_id, listagg(thisThing, ' --- '||chr(10) ) within group (order by this) as SCHED_1 from
(
SELECT
staff_id, RPT_STAFF_SHIFTS.ORGANIZATION||': '||listagg(
RPT_STAFF_SHIFTS.DAYS_OF_WEEK
, ',' ) within group (order by BEGIN_DATE desc)
as thisThing
FROM "RPT_STAFF_SHIFTS" where "RPT_STAFF_SHIFTS"."END_DATE" is null
group by staff_id, organization)
group by staff_id
) schedule_1 on schedule_1.staff_id = "RPT_STAFF"."STAFF_ID"
where "RPT_STAFF"."STAFF_ID" ='555555'
This is a different approach than using the nested query, but it some situations it might work better by taking into account the level issue when developing the query and taking an extra step to fully concatenate the results.

How to reference a table in a Sub-Sub Query

I have the following tables:
Bradford_Score_Bands
BandNo InclusiveScore
------------------------
1 0
2 150
3 500
Bradford_Scores
ClockNo Dated Score
--------------------------------
2 30/10/14 123
99 30/10/14 3
2 29/10/14 101
99 29/10/14 8
Employees
ClockNo
--------------------
2
3
99
My aim is to work out the BandNo for each ClockNo for today and yesterday based on their score
I can find the correct BandNo based on a score value like this:
SELECT MIN(BandNo) FROM Bradford_Score_Bands WHERE InclusiveScore >= 123
I can find the score for today and yesterday for each person like this:
SELECT DISTINCT EMP.ClockNo,
ISNULL((SELECT Score FROM Bradford_Scores BFT WHERE Dated = '2014-10-30' AND BFT.ClockNo = EMP.ClockNo), 0) As ScoreToday,
ISNULL((SELECT Score FROM Bradford_Scores BFT WHERE Dated = '2014-10-29' AND BFT.ClockNo = EMP.ClockNo), 0) As ScoreYesterday
FROM Employees EMP
But I can't seem to be able to combine the two. I thought something like this would work:
SELECT DISTINCT EMP.ClockNo,
(SELECT MIN(BandNo) FROM Bradford_Score_Bands WHERE InclusiveScore >=
(SELECT Score FROM Bradford_Scores BFT1 WHERE Dated = '2014-10-30' AND BFT1.ClockNo = EMP.ClockNo)),
(SELECT MIN(BandNo) FROM Bradford_Score_Bands WHERE InclusiveScore >=
(SELECT Score FROM Bradford_Scores BFT2 WHERE Dated = '2014-10-29' AND BFT2.ClockNo = EMP.ClockNo))
FROM Employees EMP
But the parts in the subquery where I reference BFTX.ClockNo = EMP.ClockNo seem to be causing the query to fail. I get the helpful pervasive error "Data Record ManagerCurrency not on a record"
EDIT:
I tried this exact same query in SQL Server and it works, so is there a way to re-write this to make it more Pervasive friendly?
Now this is tagged with SQL Server I don't feel the need to write a pervasive query that works.
I took your original query and rewrote it in a simpler fashion. Maybe try this and see if it solves your problem?
DECLARE #Bradford_Score_Bands TABLE (BandNo INT, InclusiveScore INT);
INSERT INTO #Bradford_Score_Bands VALUES (1, 0);
INSERT INTO #Bradford_Score_Bands VALUES (2, 150);
INSERT INTO #Bradford_Score_Bands VALUES (3, 500);
DECLARE #Bradford_Scores TABLE (ClockNo INT, Dated DATE, Score INT);
INSERT INTO #Bradford_Scores VALUES (2, '20141030', 123);
INSERT INTO #Bradford_Scores VALUES (99, '20141030', 3);
INSERT INTO #Bradford_Scores VALUES (2, '20141029', 101);
INSERT INTO #Bradford_Scores VALUES (99, '20141029', 8);
DECLARE #Employees TABLE (ClockNo INT);
INSERT INTO #Employees VALUES (2);
INSERT INTO #Employees VALUES (3);
INSERT INTO #Employees VALUES (99);
--Original Query
SELECT DISTINCT
EMP.ClockNo,
(SELECT MIN(BandNo) FROM #Bradford_Score_Bands WHERE InclusiveScore >= (SELECT Score FROM #Bradford_Scores BFT1 WHERE Dated = '2014-10-30' AND BFT1.ClockNo = EMP.ClockNo)),
(SELECT MIN(BandNo) FROM #Bradford_Score_Bands WHERE InclusiveScore >= (SELECT Score FROM #Bradford_Scores BFT2 WHERE Dated = '2014-10-29' AND BFT2.ClockNo = EMP.ClockNo))
FROM
#Employees EMP;
--New query
SELECT
e.ClockNo,
MIN(bsbt.BandNo),
MIN(bsby.BandNo)
FROM
#Employees e
LEFT JOIN #Bradford_Scores bst ON bst.ClockNo = e.ClockNo AND bst.Dated = '20141030'
LEFT JOIN #Bradford_Scores bsy ON bsy.ClockNo = e.ClockNo AND bsy.Dated = '20141029'
LEFT JOIN #Bradford_Score_Bands bsbt ON bsbt.InclusiveScore >= bst.Score
LEFT JOIN #Bradford_Score_Bands bsby ON bsby.InclusiveScore >= bsy.Score
GROUP BY
e.ClockNo;
I got exactly the same results for both queries when running this on SQL Server.

How to Get Sum of One Column Based On Other Table in Sql Server

I have 2 table in my database (like this):
tblCustomers:
id CustomerName
1 aaa
2 bbb
3 ccc
4 ddd
5 eee
6 fff
tblPurchases:
id CustomerID Price
1 1 300
2 2 100
3 3 500
4 1 150
5 4 50
6 3 250
7 6 700
8 2 30
9 1 310
10 4 25
Now I want with "Stored Procedures" take a new table that give me the sum of price for each customer. Exactly like under.
How can do that?
Procedures Result:
id CustomerName SumPrice
1 aaa 760
2 bbb 130
3 ccc 750
4 ddd 75
5 eee 0
6 fff 700
select c.id, c.customername, sum(isnull(p.price, 0)) as sumprice
from tblcustomers c
left join tblpurchases p
on c.id = p.customerid
group by c.id, c.customername
SQL Fiddle test: http://sqlfiddle.com/#!3/9b573/1/0
Note the need for an outer join because your desired result includes customers with no purchases.
You can use the below query to get the result
select id,CustomerName,sum(price) as TotalPrice
from
(
select tc.id,tc.CustomerName,tp.price
from tblCustomers tc
join
tblPurchases tp on tc.id = tp.CustomerID
) tab
group by id,CustomerName
Although the other answers here do work, they don't appear to be what I would consider standard practice, or optimal.
The simplest solution (standard, but not always optimal) requires no sub-query of any variety.
SELECT
cust.id,
cust.CustomerName,
SUM(prch.price) AS SumPrice
FROM
tblCustomers AS cust
INNER JOIN
tblPurchases AS prch
ON cust.id = prch.CustomerID
GROUP BY
cust.id,
cust.CustomerName
The only reason that this is not necessarily optimal is that it involves grouping by two fields, one of which is a string. This involves creating 'counters' in memory that are identified by this composite of an id and string, which can be inefficient due to the fact that you only really need to use the id to uniquely identify the counter. (The identifier is only one item and is a small (probably only 4 bytes), rather than multiple items one of which is long (potentially many many bytes)).
This means that you can do the following as a possible optimisation. Though depending on your data this many be a premature optimsation, it has no performance down-side and is always good to know about...
SELECT
cust.id,
cust.CustomerName,
prch.SumPrice
FROM
tblCustomers AS cust
INNER JOIN
(
SELECT
CustomerID,
SUM(price) AS SumPrice
FROM
tblPurchases
GROUP BY
CustomerID
) AS prch
ON cust.id = prch.CustomerID
This makes the in-memory aggregation as simple as possible, as so as quick as possible.
In both cases you should have the best possible efficiency in the query by ensuring that you have indexes on tblCustomer(id) and on tblPurchases(CustomerID),
DECLARE #tblcustomers table (id int, customername varchar(10));
insert into #tblcustomers values (1, 'aaa');
insert into #tblcustomers values (2, 'bbb');
insert into #tblcustomers values (3, 'ccc');
insert into #tblcustomers values (4, 'ddd');
insert into #tblcustomers values (5, 'eee');
insert into #tblcustomers values (6, 'fff');
DECLARE #tblpurchases table (id int, customerid int, price int);
insert into #tblpurchases values (1, 1, 300);
insert into #tblpurchases values (2, 2, 100);
insert into #tblpurchases values (3, 3, 500);
insert into #tblpurchases values (4, 1, 150);
insert into #tblpurchases values (5, 4, 50);
insert into #tblpurchases values (6, 3, 250);
insert into #tblpurchases values (7, 6, 700);
insert into #tblpurchases values (8, 2, 30);
insert into #tblpurchases values (9, 1, 310);
insert into #tblpurchases values (10, 4, 25);
WITH CTE AS(
select c.id,c.customername from #tblcustomers c
)
Select c.id,c.customername,(Select SUM(ISNULL(P.price,0)) from #tblpurchases P
WHERE P.customerid = C.id) AS Price from CTE c

SQL: Fetching total shares of a customer through multiple tables

I have 3 tables customer(cid, name, phone) and transactions (cid (reference), fundid, date, shares) and fund (fundid, fund_name).
I am trying to write an sql query that would get me the total number of shares for each customer for each fund.
Here are the sample inserts:
INSERT INTO CUSTOMER(1, 'Alex', '123456678');
INSERT INTO CUSTOMER(2, 'Bill', '6323450236');
INSERT INTO CUSTOMER(3, 'Marie', '8568289912');
INSERT INTO FUND (1, 'Docotel');
INSERT INTO FUND (2, 'Armen');
INSERT INTO FUND (3, 'TD');
INSERT INTO TRANSACTIONS(1, 2, '2010-2-12', 234); (means shares bought)
INSERT INTO TRANSACTIONS(3, 1, '2010-4-2', 192);
INSERT INTO TRANSACTIONS(1, 2, '2010-4-22', -45); (the '-' means shares sold)
INSERT INTO TRANSACTIONS(1, 3, '2010-4-26', 220);
INSERT INTO TRANSACTIONS(3, 2, '2010-7-21', 170);
I want the sql result to look something like this:
Name| Fund_Name | Total_Shares |
Alex Docotel 189
Alex TD 220
Marie Docotel 192
Marie Armen 170
Thanks
Try this:
SELECT customer.name, fund.fund_name, T1.total_shares
FROM
(
SELECT cid, fundid, SUM(shares) AS total_shares
FROM transactions
GROUP BY cid, fundid
) T1
JOIN customer ON T1.cid = customer.cid
JOIN fund ON T1.fundid = fund.fundid
ORDER BY customer.name