ssrs: Group field without messing up the table - ssrs-2012

In a table I have a repeating field that I want to group by but without messing up the table.
The data I have is the following:
and i want to get to this:
As you can see, group A is repeated and it's what I want. The problem is that it groups all the cities A into a single group and the table is messed up. Can you help me?

As per my comment you will need something to order the rows by then it's a rather clunky solution to get what you need. There will probably be a much more elegant way but this will work.
The script reproduces you data, adds a row id and adds a groupid column.
You can use the group ID column to group your data by in the report even if it's not visible.
declare #t TABLE(client varchar(10), product varchar(10), [value] int, city varchar(10))
INSERT INTO #t VALUES
('A', 'ASD', 13, 'A'),
('B', 'QWE', 40, 'A'),
('G', 'SDF', 31, 'A'),
('F', 'ERT', 12, 'B'),
('D', 'DFG', 2, 'B'),
('G', 'GFHJ', 4, 'C'),
('B', 'TY', 13, 'C'),
('V', 'URTY', 29, 'A'),
('C', 'ERT', 33, 'A')
DROP TABLE IF EXISTS #r
CREATE TABLE #r (client varchar(10), product varchar(10), [value] int, city varchar(10), RowID int IDENTITY(1,1), GroupID int)
-- this just adds a sort order for the data as #r has an identity column
INSERT INTO #r (client, product, [value], city)
SELECT client, product, [value], city FROM #t
-- some variables to track the previous city and group counter
DECLARE #g int = 0 -- counter for row group
DECLARE #prvCity varchar(10) ='' -- previous city
WHILE EXISTS (SELECT * FROM #r WHERE GroupID IS NULL) -- for any rows not updated
BEGIN -- put the rowid into #r and city into #curCity
declare #r int = (SELECT MIN(RowID) FROM #r WHERE GroupID is null)
declare #curCity varchar(10) = (SELECT city from #r WHERE RowID = #r)
IF #prvCity != #curCity -- if the city changed
BEGIN
SET #g = #g + 1 -- increment the group
SET #prvCity = #curCity -- set previous city to current city
END
UPDATE #r SET GroupID = #g WHERE RowID = #r -- update the temp table
END
-- final output dataset query
SELECT * FROM #r
Here is the report design including the row groups
and here is the final output

Related

Deleting multiple rows in oracle

DELETE FROM MYTABLE WHERE ID = 1 and NAME ='xyz';
DELETE FROM MYTABLE WHERE ID = 2 and NAME ='abc';
DELETE FROM MYTABLE WHERE ID = 3 and NAME ='abc';
I have multiple delete statements mentioned above. How can I delete them in less statements. Will I have to write 100 delete statements?
You can do this:
delete from mytable
where (id, name) in ((1, 'xyz'),
(2, 'abc'),
(3, 'abc'));
You could use IN:
DELETE FROM MYTABLE
WHERE (ID, NAME) IN (SELECT 1 AS ID, 'xyz' AS NAME FROM dual UNION ALL
SELECT 2 AS ID, 'abc' AS NAME FROM dual UNION ALL
SELECT 3 AS ID, 'abc' AS NAME FROM dual);
Of course inside subquery you could use any select (for instance from global temporary table).
DELETE FROM MYTABLE
WHERE ID IN (1, 2, 3) AND NAME IN ('XYZ', 'ABC');
If your id field is unique then use:
DELETE FROM MYTABLE WHERE ID IN (1, 2, 3);

How to bind horizontal values of a table to a vertical values of another table in oracle database

i have 2 tables .
The columns start with attributes are change based on department. the description of attributes are here
My requirement is to get the values of each attributes with its primary key based on the department as table bellow.
Honestly i am stuck on this problem in my program. I have no permission to change the tables and there is no common unique key column.i would appreciate if anyone could provide me a suggestion.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
e as (
select employeeId, department, attribute1, 1 rn from employees union all
select employeeId, department, attribute2, 2 rn from employees union all
select employeeId, department, attribute3, 3 rn from employees
)
select e.employeeId, a.attributeid, e.department, a.attribute, a.meaning,
e.attribute1 as value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
Test data and output:
create table employees (employeeID number(3), name varchar2(10), department varchar2(5), age number(3), attribute1 varchar2(10), attribute2 varchar2(10), attribute3 varchar2(10));
insert into employees values (1, 'john', 'IT', 22, 'attr1val1', 'attr2val2', null);
insert into employees values (2, 'jane', 'HR', 32, 'attr1val3', 'attr2val4', 'attr3val5');
insert into employees values (3, 'joe', 'HR', 23, 'attr1val6', 'attr2val7', 'attr3val8');
insert into employees values (4, 'jack', 'IT', 45, 'attr1val9', 'attr2val10', null);
create table attributes (attributeID number(3), department varchar2(10), attribute varchar2(10), meaning varchar2(10));
insert into attributes values (1, 'IT', 'attribute1', 'laptoptype');
insert into attributes values (2, 'IT', 'attribute2', 'networkloc');
insert into attributes values (3, 'HR', 'attribute1', 'location');
insert into attributes values (4, 'HR', 'attribute2', 'position');
insert into attributes values (5, 'HR', 'attribute3', 'allocation');
EMPLOYEEID ATTRIBUTEID DEPARTMENT ATTRIBUTE MEANING VALUE
---------- ----------- ---------- ---------- ---------- ----------
1 1 IT attribute1 laptoptype attr1val1
1 2 IT attribute2 networkloc attr2val2
2 3 HR attribute1 location attr1val3
2 4 HR attribute2 position attr2val4
2 5 HR attribute3 allocation attr3val5
3 3 HR attribute1 location attr1val6
3 4 HR attribute2 position attr2val7
3 5 HR attribute3 allocation attr3val8
4 1 IT attribute1 laptoptype attr1val9
4 2 IT attribute2 networkloc attr2val10
Edit: Explanation
In answer I used with
clause just to divide solution into readable steps. You can move them into from clause of main query if it is
more comfortable for you. Anyway: subquery a reads data from table attributes and adds number for rows,
so for each department they are allways numbered from 1. I used row_number() for that. Subquery e unions (all) required attributes and numbers
them accordingly. Numbers generated in both subqueries are then used in main join: a.department=e.department and a.rn=e.rn.
Alternative 1 - if you are using Oracle 11g you could use the unpivot. See what is generated by subquery, and how it is joined with attributes table:
with e as (
select employeeId, name, department, attribute, value from employees
unpivot (value for attribute in ("ATTRIBUTE1", "ATTRIBUTE2", "ATTRIBUTE3"))
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join attributes a on a.department=e.department
and lower(a.attribute)=lower(e.attribute)
order by e.employeeId, a.attributeid;
Alternative 2 - with hierarchical subquery generator (subquery r), realised by connect by which simple creates numbers from 1, 2, 3 which are next joined with employees and proper attribute
is attached as value in case clause. Rest is made in similiar way like in original answer.
with a as (
select a.*, row_number() over (partition by department order by attributeID) rn
from attributes a),
r as (select level rn from dual connect by level<=3),
e as (
select employeeId, department, rn,
case when r.rn = 1 then attribute1
when r.rn = 2 then attribute2
when r.rn = 3 then attribute3
end value
from employees cross join r
)
select e.employeeId, a.attributeid, e.department, a.attribute,
a.meaning, e.value
from e join a on a.department=e.department and a.rn=e.rn
order by e.employeeId, a.attributeid
All three versions gave me the same output. I also tested first option on similiar table with 100k rows and get output in few seconds (for 5 attributes). Please test all solutions and try to understand them. If you can use unpivot version I would prefer this.
Sorry for delayed explanation and any language mistakes.
The WITH clause was added with Oracle 9.2 and should do the trick. For the other attributes just add more sub queries where the filter is att.attribute = 'attribute2' or 'Attribute3'...
WITH e AS
(SELECT emp.employee_ID, emp.department, emp.attribute1
FROM employee emp),
a AS (SELECT att.attribute_id, att.attribute, att.meaning
FROM attribute_TYPE att
WHERE att.attribute = 'attribute1')a
SELECT e.employeeid, att.attributeid, e.department, a.attribute,
a.meaning e.attribute1
FROM e JOIN a ON e.department = a.department

connect by prior giving incorrect results

Assume I have 2 tables as shown below – PARENT and CHILD
create table parent
(
parent_id number,
parent_name varchar2(10),
cnt number
)
create table children
(
child_id number,
parent_id number,
name varchar2(10)
)
insert into parent values (1, 'A', 3);
insert into parent values (2, 'B', 2);
insert into children values (1, 1, 'AB');
insert into children values (1, 2, 'AC');
insert into children values (1, 3, 'AD');
insert into children values (2, 1, 'BA');
insert into children values (2, 2, 'BB');
The output has to be something like below:
Parent_ID Parent_Name Cnt Child_Names
1 A 3 AB, AC, AD
2 B 2 BA, BB
I have written the below query to achieve this. I don't know where it is going wrong, query seems to be fine but the output is not what is desired.
Please help me out as I am almost saturated debugging this.
select parent_id, parent_name, substr(max(sys_connect_by_path(child_name, ',')),2)
from
( select p.parent_id, p.parent_name, ch.child_name, row_number() over (partition by p.parent_id, p.parent_name order by ch.child_name) rn
from parent p, children ch
where p.parent_id = ch.parent_id
)
start with rn =1
connect by prior rn+1 = rn
group by parent_id, parent_name
You don't see hierartical data. It is simple master detail.
select p.parent_id, p.parent_name, p.cnt, c.children_names, c.real_count
from parent p left outer join
(select parent_id, listagg(name, ', ') within group (order by name) children_names,
count(*) real_count from children group by parent_id) c
on p.parent_id = c.parent_id
'AD' is not connected because it parent_id is 3.
Hm... listagg is a 11g feature.
The general solution before 11g was to use Tom Kyte's aproach:
http://www.sqlsnippets.com/en/topic-11591.html
And have a look at http://www.oracle-developer.net/display.php?id=515 for performance expectations
Ok, let's continue with "connect by" aproach.
First of all, lets reduce complexity by forgetting about parent table. We will be building right side of left outer join query above, but using connect by construction.
Second, I think you missed how parent_id and child_id are named. My guess is that your child_id is actually your parent_id in parent table. So name is wrong.
3 conditions to put all children in a row:
All children have the same child_id (parent.parent_id)
first one has parent_id = 1 (more like order_id)
every next one has parent_id +1 comparing to previous one
Let's put it all into query:
select c.*, level from children c
start with parent_id = 1 --2nd condition
connect by prior
c.parent_id = c.parent_id -1 --3rd condition
and prior child_id = child_id --1st condition
order by child_id, parent_id
Then you add your SYS_CONNECT_BY_PATH(name, ', '), get the last element and only then join it with parent table, but based on parent.parent_id = children.child_id
here is your query after correction, actually you did some mistake in column name of children table(wrote ch.child_name while it is ch.name or name)
select parent_id, parent_name,cnt,substr(max(sys_connect_by_path(child_name, ',')),2) child_name
from
( select p.parent_id, p.parent_name, p.cnt , ch.name child_name, row_number() over (partition by p.parent_id, p.parent_name order by ch.name) rn
from parent p, children ch
where p.parent_id = ch.parent_id
)
start with rn =1
connect by prior rn+1 = rn
group by parent_name,cnt,parent_id

PL/SQL I want the corresponding column of MAX(O.CO_YEAR). How to do it?

Below is the code for PL/SQL query for which I have to grab the names of the instructors who are qualified to teach a particular course when name is supplied along with number of times they have taught this course and the last time (year and term) when they taught this course.
I have done most part of the question, but couldn't figure out how to get the corresponding details of the max(o.co_year) i.e. term
declare
gname varchar2(20);
count_id number(2);
id varchar(20);
year1 number(4);
cursor abc // cursor 1
is
SELECT i.i_gname
into gname
FROM INSTRUCTOR I
WHERE i.i_id in (
SELECT t.i_id FROM TeachingQualification T
WHERE t.c_id in (SELECT c.c_id FROM COURSE C
WHERE c.c_title = 'Advanced Database App')) ;
cursor bcd // cursor two
is
select o.i_id, count(o.i_id), max(o.co_year)
into id, count_id, year1
from courseoffering o
where (o.i_id = i_id and o.c_id = 1234567)
group by o.i_id;
Begin
open abc;
open bcd;
loop
FETCH abc into gname;
exit when abc%NOTFOUND;
FETCH bcd into id, count_id, year1;
exit when bcd%NOTFOUND;
DBMS_OUTPUT.PUT_LINE ('NAME: ' || gname || ' Number of times taught ' || count_id || ' Year ' || year1 || ); // want to output corresponding column details for the year1 attribute.
end loop;
close bcd;
close abc;
end;
PL/SQL I want the corresponding column of MAX(O.CO_YEAR). How to do it?
Yeah I know, but its an university task which I'm suppose to be implemented in PL/SQL.
Blah! I'd rather that your university teaches you about the proper use of PLSQL and SQL, and gave a meaningful task. If you can do something in SQL, then do it in SQL.
Also: Why are your cursors containing the INTO keyword? Why do you need a loop? It seems you only ever expect 1 returned value?
I dissected your code a bit:
--instructors who are qualified to teach a course
SELECT i.i_gname --instructor name
FROM INSTRUCTOR I
WHERE i.i_id IN ( SELECT t.i_id --instructor_id
FROM TeachingQualification T
WHERE t.c_id in (SELECT c.c_id --course_id
FROM COURSE C
WHERE c.c_title = 'Advanced Database App'));
--instructors who taught a course, with amount and last time
SELECT o.i_id instructor_id, count(o.i_id) times_taught, max(o.co_year) last_time_taught
FROM courseoffering o
WHERE o.c_id = 1234567 --course_id
GROUP BY o.i_id;
--all instructors (ID) who taught advanced database app, how many times, and last time
--consider that this may produce NO_DATA_FOUND
SELECT o.i_id instructor_id, count(o.i_id) times_taught, max(o.co_year) last_time_taught
FROM course c
JOIN courseoffering o
ON c.c_id = o.c_id
WHERE c.c_title = 'Advanced Database App'
GROUP BY o.i_id;
-- i don't think teachingqualification is required. The last select providers instructor IDs.
-- There is no need to go through that table, unless it would contain extra data you'd want to
-- filter by. Since courseoffering is being queried, and it has instructors, it stands to reason
-- that those instructor are qualified to teach the course.
SELECT (SELECT i_gname FROM instructor WHERE i_id = o.i_id) instructor
,count(o.i_id) times_taught
, max(o.co_year) last_time_taught
FROM course c
JOIN courseoffering o
ON c.c_id = o.c_id
WHERE c.c_title = 'Advanced Database App'
GROUP BY o.i_id;
-- Look, if you do want a PLSQL block for this, go ahead.
BEGIN
FOR r IN (SELECT (SELECT i_gname FROM instructor WHERE i_id = o.i_id) instructor
,count(o.i_id) times_taught
, max(o.co_year) last_time_taught
FROM course c
JOIN courseoffering o
ON c.c_id = o.c_id
WHERE c.c_title = 'Advanced Database App'
GROUP BY o.i_id)
LOOP
DBMS_OUTPUT.PUT_LINE ('NAME: ' || r.instructor || ' Number of times taught ' || r.times_taught || ' Year ' || r.last_time_taught);
END LOOP;
END;
Oh, and please give your columns meaningful names. Call it instructor_id instead of i_id for example.
I set up some example data:
create table course (id number(5,0), cname varchar2(50), constraint course_pk primary key (id))
/
create table courseoffering(id number(5,0), course_id number(5,0), instructor_id number(5,0), course_year number(5,0), constraint offering_pk primary key (id), constraint course_fk foreign key (course_id) references course (id))
/
insert into course values (1, 'Tech I');
insert into course values (2, 'Basic SQL');
insert into course values (3, 'Advanced SQL');
--Instructor 1
insert into courseoffering values (1, 1, 1, 2009); --Tech I
insert into courseoffering values (2, 1, 1, 2010); --Tech I
insert into courseoffering values (3, 1, 1, 2011); --Tech I
insert into courseoffering values (4, 2, 1, 2011); --Basic SQL
insert into courseoffering values (5, 2, 1, 2012); --Basic SQL
--Instructor 2
insert into courseoffering values (6, 2, 2, 2008); --Basic SQL
insert into courseoffering values (7, 2, 2, 2009); --Basic SQL
insert into courseoffering values (8, 2, 2, 2010); --Basic SQL
insert into courseoffering values (9, 3, 2, 2010); --Advanced SQL
insert into courseoffering values (10, 3, 2, 2011); --Advanced SQL
insert into courseoffering values (11, 3, 2, 2012); --Advanced SQL
insert into courseoffering values (12, 1, 2, 2009); --Tech I
insert into courseoffering values (13, 1, 2, 2010); --Tech I
commit;
Running this:
SELECT c.cname, o.instructor_id, count(o.instructor_id) times_taught, max(o.course_year) last_time_taught
FROM course c
JOIN courseoffering o
ON c.id = o.course_id
GROUP BY c.cname, o.instructor_id
ORDER BY c.cname, o.instructor_id;
Produces:
CNAME INSTRUCTOR_ID TIMES_TAUGHT LAST_TIME_TAUGHT
Advanced SQL 2 3 2012
Basic SQL 1 2 2012
Basic SQL 2 3 2010
Tech I 1 3 2011
Tech I 2 2 2010
You can even easily turn the required data into a view.
No PLSQL required. Only a couple of lines in SQL. And if you want it in PLSQL you can still use a loop to cover multiple instructors per course, or if you narrow it to one course and one instructor, some variables. Always minimize switching between SQL and PLSQL contexts.
Cursors in Oracle can have parameters. See http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#BABHICAF for details.
How it might work in your code:
cursor bcd(p_c_id courseoffering.c_id%type) // cursor two
is
select o.i_id, count(o.i_id), max(o.co_year)
into id, count_id, year1
from courseoffering o
where (o.i_id = i_id and o.c_id = p_c_id)
group by o.i_id;

how to create a temporary table or to select only distinct values from a column in a loop

I have a table with 2 columns, first column has repetitive values, now in a while loop i want to select each distinct value at a time, i created a temporary table in sql, but in oracle sql developer how do i write the code?
CREATE TABLE look_up_table
(row_id INT NOT NULL,
attribute VARCHAR(500),
VARCHAR(700)
)
/* now manually populating this table */
INSERT INTO look_up_table
VALUES
(1, grmacolor_frame_access, black);
(2, grmacolor_frame_access, blue);
(3, grmacolor_frame_access, red);
(4, grmamaterial_frame_access, acetate);
(5, grmamaterial_frame_access, metal);
(6, grmamaterial_frame_access, nylon);
(7, grmamaterial_frame_access, plastic);
DECLARE #temp_col_val NVARCHAR (700), #counter1 INT,
SET #counter1 = 0;
SET #column_count = (SELECT COUNT (DISTINCT attribute) FROM look_up_table);
CREATE TABLE #temp1 AS
SELECT DISTINCT attribute AS attrib,
ROW_NUMBER() OVER (ORDER BY attribute) AS seqno1,
FROM look_up_table;
WHILE (#counter1 < #column_count)
BEGIN;
SET #temp_col_val = (SELECT attrib FROM #temp1 WHERE seqno1 = #counter1;
please help
The following code will loop over each attribute and print it :
declare
curField varchar2(100);
resultCnt number ;
begin
select count(distinct attribute) into resultCnt from look_up_table;
for ind in 1..resultCnt loop
select attribute into curField from (
select attribute, rownum rwn
from
(
select distinct attribute
from look_up_table
)
) where rwn = ind;
dbms_output.put_line (curField);
end loop;
end;
/

Resources