SQL: Create multiple columns from a single column - sql

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;

Related

Referencing a calculated field in SQL statement

I have the following schema
CREATE TABLE QUOTE (id int, amount int);
CREATE TABLE QUOTE_LINE (id int, quote_id int, line_amount int);
INSERT INTO QUOTE VALUES(1, 100);
INSERT INTO QUOTE VALUES(2, 200);
INSERT INTO QUOTE VALUES(3, 100);
INSERT INTO QUOTE VALUES(4, 300);
INSERT INTO QUOTE_LINE VALUES(1, 1, 5);
INSERT INTO QUOTE_LINE VALUES(2, 1, 6);
INSERT INTO QUOTE_LINE VALUES(3, 1, 4);
INSERT INTO QUOTE_LINE VALUES(4, 1, 2);
INSERT INTO QUOTE_LINE VALUES(1, 2, 5);
INSERT INTO QUOTE_LINE VALUES(2, 2, 5);
INSERT INTO QUOTE_LINE VALUES(3, 2, 5);
INSERT INTO QUOTE_LINE VALUES(4, 2, 5);
And I need to run the following query:
SELECT QUOTE.id,
line_amount*12 AS amount,
amount*2 as amount_doubled
from QUOTE_LINE
LEFT JOIN QUOTE ON QUOTE_LINE.quote_id=QUOTE.id;
The 3rd line in the query amount*2 as amount_double needs to reference the amount calculated in the prior line i.e. line_amount*12 AS amount.
However if I run this query, it picks the amount from the QUOTE table instead the amount that was calculated. How can I make my query use the calculated amount without changing the name of the calculated field?
Here is the sqlfiddle for this:
http://sqlfiddle.com/#!17/914b2/1
Note: I understand that I can create a sub-query, CTE or a lateral join, but the tables I am working are very very wide tables, and the queries have many many joins. As such, I need to keep the LEFT INNER JOINS and also I don't always know if a calculated field will be duplicated in JOINed table or not. Table structures change.
Move the definition to the FROM clause using a LATERAL JOIN:
select q.id, v.amount, v.amount * 2 as as amount_doubled
from QUOTE_LINE ql left join
QUOTE q
on ql.quote_id = q.id CROSS JOIN LATERAL
(values (line_amount*12)) v(amount);
You can also use a subquery or CTE, but I like the lateral join method.
Note: I would expect QUOTE to be the first table in the LEFT JOIN.
Qualify all column names with the table name and use a subquery:
SELECT q.id,
q.amount,
q.amount * 2 AS amount_doubled
FROM (SELECT quote.id,
quote_line.line_amount * 12 AS amount,
FROM quote_line
LEFT JOIN quite
ON quote_line.quote_id = quote.id
) AS q;
Just a little simple algebra resolves the issue quite easily. It is clear that calculated amount is 12 times the line_amount and that amount_doubles is 2 times that. So
select q.id
, ql.line_amount*12 as amount
, ql.line_amount*12*2 as amount_doubled
from quote_line ql
left join quote q
on ql.quote_id = q.id;
However, child left join parent seems strange as it basically says "Give me the quote line amounts where there is no quote". One would hope a FK from line to quote would prevent that from happening.
If so then a inner join would suffice. Further if the id is the only column from quote the join can removed by taking quote_id from quote_line. So perhaps reducing to:
select ql.quote_id as id
, ql.line_amount*12 as amount
, ql.line_amount*24 as amount_doubled
from quote_line ql;

What is the correct order for recursive queries using two tables on SQL?

I have the following two tables:
CREATE TABLE empleados (
id INTEGER PRIMARY KEY,
nombre VARCHAR(255) NOT NULL,
gerenteId INTEGER,
FOREIGN KEY (gerenteId) REFERENCES empleados(id)
);
CREATE TABLE ventas (
id INTEGER PRIMARY KEY,
empleadoId INTEGER NOT NULL,
valorOrden INTEGER NOT NULL,
FOREIGN KEY (empleadoId) REFERENCES empleados(id)
);
With the following data:
INSERT INTO empleados(id, nombre, gerenteId) VALUES(1, 'Roberto', null);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(2, 'Tomas', null);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(3, 'Rogelio', 1);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(4, 'Victor', 3);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(5, 'Johnatan', 4);
INSERT INTO empleados(id, nombre, gerenteId) VALUES(6, 'Gustavo', 2);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(1, 3, 400);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(2, 4, 3000);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(3, 5, 3500);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(4, 2, 40000);
INSERT INTO ventas(id, empleadoId, valorOrden) VALUES(5, 6, 3000);
I'm trying to get a query to obtain the sum of all the "Orders" which belong directly or inderectly to the main managers. The main managers are the ones whose doesn't report to anybody else. In this calse, Roberto and Tomas are the main managers but there could be other ones. The result must to take into account not just the sales (ventas) made directly by him but also by any of their employees (direct employees or employees of their employees).
So in this case I'm expecting the following result:
-- Id TotalVentas
-- ----------------
-- 1 6900
-- 2 43000
Where the Id column refers to the employees' id which are "main" managers and TotalVentas column is the sum of all the ventas (valorOrden) made by them and their employees.
So Roberto has no records for orders but Rogelio (his employee) has one of 400, Victor (Rogelio's employee) has one for 3000 and Johnatan (Victor's employee) has another for 3500. So the sum of all of them is 6900. And it is the same case with Tomas which has one venta directly made by him plus another one made by Gustavo who is his employee.
The query that I have so far is the following:
WITH cte_org AS (
SELECT
id,
nombre,
gerenteId,
0 as EmpLevel
FROM
dbo.empleados
WHERE gerenteId IS NULL
UNION ALL
SELECT
e.id,
e.nombre,
e.gerenteId,
o.EmpLevel + 1
FROM
dbo.empleados e
INNER JOIN cte_org o
ON o.id = e.gerenteId
WHERE e.gerenteId IS NOT NULL
)
SELECT cte.id, SUM(s.orderValue)
FROM cte_org cte, dbo.sales s
WHERE (cte.id = s.employeeId AND cte.gerenteId is null)
OR
(cte.id = s.employeeId AND cte.EmpLevel <> 0 AND
cte.gerenteId in (select ee.id from dbo.empleados ee where ee.gerenteId is null)
)
--AND
--(cte.gerenteId in (select ee.id from dbo.empleados ee where ee.gerenteId is null)
--OR
--cte.gerenteId is null)
--AND cte.gerenteId = NULL
group by cte.id
;
Could anybody help me with this?
This is traversing a hierarchy, starting with the highest level managers, and then joining in the sales:
with cte as (
select id, nombre, id as manager
from empleados e
where gerenteid is null
union all
select e.id, e.nombre, cte.manager
from cte join
empleados e
on cte.id = e.gerenteid
)
select cte.manager, sum(valororden)
from cte join
ventas v
on cte.id = v.empleadoid
group by cte.manager;
Here is a db<>fiddle. The Fiddle uses SQL Server, because that is consistent with the syntax that you are using.
In oracle you can do this using below query:
SQLFiddle
select manager, sum(amount) as total_amount from
(
select level, CONNECT_BY_ROOT employeeid Manager, a.* from
(
SELECT a.id as employeeid, a.nombre as name , a.GERENTEID as manager_id,
b.EMPLEADOID as sales_id, b.VALORORDEN amount
from empleados a left outer join ventas b
on (a.id = b.empleadoId)
) a
start with manager_id is null
connect by prior employeeid = manager_id) x
group by manager;

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.

SQL Inner/Sub group wise Query for a specific scenario

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;

Is there a nesting limit for correlated subqueries in some versions of Oracle?

Here is the code that will help you understand my question:
create table con ( content_id number);
create table mat ( material_id number, content_id number, resolution number, file_location varchar2(50));
create table con_groups (content_group_id number, content_id number);
insert into con values (99);
insert into mat values (1, 99, 7, 'C:\foo.jpg');
insert into mat values (2, 99, 2, '\\server\xyz.mov');
insert into mat values (3, 99, 5, '\\server2\xyz.wav');
insert into con values (100);
insert into mat values (4, 100, 5, 'C:\bar.png');
insert into mat values (5, 100, 3, '\\server\xyz.mov');
insert into mat values (6, 100, 7, '\\server2\xyz.wav');
insert into con_groups values (10, 99);
insert into con_groups values (10, 100);
commit;
SELECT m.material_id,
(SELECT file_location
FROM (SELECT file_location
FROM mat
WHERE mat.content_id = m.content_id
ORDER BY resolution DESC) special_mats_for_this_content
WHERE rownum = 1) special_mat_file_location
FROM mat m
WHERE m.material_id IN (select material_id
from mat
inner join con on con.content_id = mat.content_id
inner join con_groups on con_groups.content_id = con.content_id
where con_groups.content_group_id = 10);
Please consider the number 10 at the end of the query to be a parameter. In other words this value is just hardcoded in this example; it would change depending on the input.
My question is: Why do I get the error
"M"."CONTENT_ID": invalid identifier
for the nested, correlated subquery? Is there some sort of nesting limit? This subquery needs to be ran for every row in the resultset because the results will change based on the content_id, which can be different for each row. How can I accomplish this with Oracle?
Not that I'm trying to start a SQL Server vs Oracle discussion, but I come from a SQL Server background and I'd like to point out that the following, equivalent query runs fine on SQL Server:
create table con ( content_id int);
create table mat ( material_id int, content_id int, resolution int, file_location varchar(50));
create table con_groups (content_group_id int, content_id int);
insert into con values (99);
insert into mat values (1, 99, 7, 'C:\foo.jpg');
insert into mat values (2, 99, 2, '\\server\xyz.mov');
insert into mat values (3, 99, 5, '\\server2\xyz.wav');
insert into con values (100);
insert into mat values (4, 100, 5, 'C:\bar.png');
insert into mat values (5, 100, 3, '\\server\xyz.mov');
insert into mat values (6, 100, 7, '\\server2\xyz.wav');
insert into con_groups values (10, 99);
insert into con_groups values (10, 100);
SELECT m.material_id,
(SELECT file_location
FROM (SELECT TOP 1 file_location
FROM mat
WHERE mat.content_id = m.content_id
ORDER BY resolution DESC) special_mats_for_this_content
) special_mat_file_location
FROM mat m
WHERE m.material_id IN (select material_id
from mat
inner join con on con.content_id = mat.content_id
inner join con_groups on con_groups.content_id = con.content_id
where con_groups.content_group_id = 10);
Can you please help me understand why I can do this in SQL Server but not Oracle 9i? If there is a nesting limit, how can I accomplish this in a single select query in Oracle without resorting to looping and/or temporary tables?
Recent versions of Oracle do not have a limit but most older versions of Oracle have a nesting limit of 1 level deep.
This works on all versions:
SELECT (
SELECT *
FROM dual dn
WHERE dn.dummy = do.dummy
)
FROM dual do
This query works in 12c and 18c but does not work in 10g and 11g. (However, there is at least one version of 10g that allowed this query. And there is a patch to enable this behavior in 11g.)
SELECT (
SELECT *
FROM (
SELECT *
FROM dual dn
WHERE dn.dummy = do.dummy
)
WHERE rownum = 1
)
FROM dual do
If necessary you can workaround this limitation with window functions (which you can use in SQL Server too:)
SELECT *
FROM (
SELECT m.material_id, ROW_NUMBER() OVER (PARTITION BY content_id ORDER BY resolution DESC) AS rn
FROM mat m
WHERE m.material_id IN
(
SELECT con.content_id
FROM con_groups
JOIN con
ON con.content_id = con_groups.content_id
WHERE con_groups.content_group_id = 10
)
)
WHERE rn = 1
#Quassnoi This was the case in oracle 9. From Oracle 10 ...
From Oracle Database SQL Reference 10g Release 1 (10.1)
Oracle performs a correlated subquery when a nested subquery references a column from a table referred to a parent statement any number of levels above the subquery
From Oracle9i SQL Reference Release 2 (9.2)
Oracle performs a correlated subquery when the subquery references a column from a table referred to in the parent statement.
A subquery in the WHERE clause of a SELECT statement is also called a nested subquery. You can nest up to 255 levels of subqueries in the a nested subquery.
I don't think it works if you have something like select * from (select * from ( select * from ( ....))))
Just select * from TableName alias where colName = (select * from SomeTable where someCol = (select * from SomeTable x where x.id = alias.col))
Check out http://forums.oracle.com/forums/thread.jspa?threadID=378604
Quassnoi answered my question about nesting, and made a great call by suggesting window analytic functions. Here is the exact query that I need:
SELECT m.material_id, m.content_id,
(SELECT max(file_location) keep (dense_rank first order by resolution desc)
FROM mat
WHERE mat.content_id = m.content_id) special_mat_file_location
FROM mat m
WHERE m.material_id IN (select material_id
from mat
inner join con on con.content_id = mat.content_id
inner join con_groups on con_groups.content_id = con.content_id
where con_groups.content_group_id = 10);
Thanks!