Creating an Oracle view that takes a parameter - c#

I have a very long query:
SELECT TO_CHAR(tsc.id) AS status,
CASE WHEN tsc.description IS NULL THEN CAST('' as NVARCHAR2(50)) ELSE tsc.description END AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END) AS "1",
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END) AS "2",
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END) AS "3",
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END) AS "5",
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
GROUP BY tsc.id, tsc.description
UNION ALL
SELECT 'TOTAL 2,4,5' AS status,
NULL AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1 AND tsc.id IN (2, 4, 5)
UNION ALL
SELECT 'Total for All' AS status,
NULL AS description,
SUM(CASE WHEN tr.USER_TYPE = 1 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 2 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 3 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE = 5 THEN 1 ELSE 0 END),
SUM(CASE WHEN tr.USER_TYPE IS NOT NULL THEN 1 ELSE 0 END) AS total
FROM TRANSACTION_STATUS_CODES tsc
LEFT JOIN TRANSACTIONS tr ON tsc.id = tr.status AND tr.User_Type BETWEEN 1 AND 5 AND tr.status != 1 AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
LEFT JOIN TRANSACTION_USER_TYPES ut ON ut.id = tr.user_type
WHERE tsc.id != 1
That does what it does.
I've been asked to save it as view and just "Select * from view" which is nice...
but as you can see I run
AND tr.update_date BETWEEN TO_DATE('2022-01-01', 'yyyy-mm-dd HH24:MI:SS') AND TO_DATE('2023-01-04', 'yyyy-mm-dd HH24:MI:SS')
This line of code few times there. now. If I save it as view it will just be same result over and over.
I have this csharp code:
requestedDateTable = LocalGeneralDbExecuterService1.call_TransactionsReport_StoredProcedure(fromDateStr, toDateStr);
which is a function that stores the query above in a datatable variable with two dates I'm capturing from two different labels and gives me a modified result set based on those dates.
I'm trying to achieve the same kind of workflow but without having to write dozens lines of query code in my program.
Is that possible? If so, how? I've been trying procedures, views... and my SQL knowledge isn't WOW at all.

From 19.6 you can create SQL table macros. In effect these enable you to create parameterized views.
Here's an example based on the standard HR schema:
create or replace function filter_emps (
start_date date, end_date date
)
return clob sql_macro as
begin
return '
select * from hr.employees
where hire_date >= start_date and hire_date < end_date ';
end filter_emps;
/
select employee_id, hire_date
from filter_emps ( date'2003-01-01', date'2003-06-01' );
/*
EMPLOYEE_ID HIRE_DATE
----------- -----------------
115 18-MAY-2003 00:00
122 01-MAY-2003 00:00
*/
var start_date varchar2(10);
var end_date varchar2(10);
exec :start_date := '2005-01-01'
exec :end_date := '2005-03-01';
select employee_id, hire_date
from filter_emps (
to_date ( :start_date, 'yyyy-mm-dd' ), to_date ( :end_date, 'yyyy-mm-dd' )
);
/*
EMPLOYEE_ID HIRE_DATE
----------- -----------------
131 16-FEB-2005 00:00
142 29-JAN-2005 00:00
146 05-JAN-2005 00:00
150 30-JAN-2005 00:00
185 20-FEB-2005 00:00
*/

Views cannot take parameters.
Instead, you can write a stored procedure that takes the start and end dates as parameters and returns a cursor.
CREATE PROCEDURE procedure_name (
i_start_date IN TRANSACTIONS.UPDATE_DATE%TYPE,
i_end_date IN TRANSACTIONS.UPDATE_DATE%TYPE,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
<your_query>;
END procedure_name;
/
and replace the hard-coded dates with:
AND tr.update_date BETWEEN i_start_date AND i_end_date
Then you can call the procedure from C# and pass the parameters.

As already explained, views cannot take parameters.
Another approach suiting your requirements could be a table function:
-- define a type fitting your output
create type t_my_row as object (
id number,
description varchar2(50)
);
create type t_my_tab is table of t_my_row;
-- write a procedure with the required parameters
create function get_my_tab(i_start_date in date, i_end_date in date) return t_my_tab as
l_tab t_my_tab := t_my_tab();
begin
-- your selection here: (select ... union all select...)
for l_rec in (select id, descr from mytable where update_date between i_start_date and i_end_date) loop
l_tab.extend;
l_tab(l_tab.last) := t_my_row(l_rec.id, l_rec.description);
end loop;
return l_tab;
end;
-- call it:
select * from table(get_my_tab(sysdate-1, sysdate));

Related

How do I get this with only One Query/SubQuery

I have this database table:
Name Beneficiary GenderBeneficiary
---------------------------------------
Karla Karla Female
Carl Mandy Female
Mark Lu Male
Erik Math Male
Jhon Jhon Male
And I need this
Gender
Description Male Female Total
-------------------------------------------
Employee 1 1 2
Familiar 2 1 3
Total 3 2 5
If the name is the same of the beneficiary is an Employee, if not is a familiar.
I get a nice result using UNION with 4 queries and the structure in SQL Server is:
Updated: stored procedure used
USE [BdDiscountCardSystem]
GO
SET ANSI_NULLS ONGO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[FillTableOne]
#dateStart DATETIME,
#dateEnd DATETIME
AS
BEGIN
SET NOCOUNT ON;
SELECT
COUNT (GenderBeneficiary) AS Gender
FROM
CartaDescuento
WHERE
Name = BeneficiaryName
AND GenderBeneficiary = 'Male'
AND DateS BETWEEN #dateStart AND #dateEnd
UNION
SELECT
COUNT (GenderBeneficiary)
FROM
CartaDescuento
WHERE
Name = BeneficiaryName
AND GenderBeneficiary = 'Female'
AND DateS BETWEEN #dateStart AND #dateEnd
UNION
SELECT
COUNT (GenderBeneficiary)
FROM
CartaDescuento
WHERE
Name != BeneficiaryName
AND GenderBeneficiary = 'Male'
AND DateS BETWEEN #dateStart AND #dateEnd
UNION
SELECT
COUNT (GenderBeneficiary)
FROM
CartaDescuento
WHERE
Name != BeneficiaryName
AND GenderBeneficiary = 'Female'
AND DateS BETWEEN #dateStart AND #dateEnd
END
The result:
Title
1
2
1
1
I'm calling the stored procedure from C# like this:
public void GetTableOne()
{
string dateStart = dateInicio.Value.ToShortDateString();
string dateEnd = dateFinal.Value.ToShortDateString();
try
{
con.Open();
SqlCommand cmd = new SqlCommand("FillTableOne", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("dateStart", dateStart);
cmd.Parameters.AddWithValue("dateEnd", dateEnd);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "¡Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
con.Close();
}
}
Updated: but I only get 1 query, not the UNIONS in the stored procedure:
Title
1
Updated: I didn't put the query or the code, because that doesn't work. I'm looking for help in structuring that in a single query. I assumed that it was not necessary to put anything other than the tables and the result that I want, in this way to be able to accommodate the columns as the report requires.
Well, anyone know a better option to get in only one query?
Additional question: I'm reporting with iTextSharp and is kinda tedious typing 120 code lines to get and structure one simple table. Any suggestions on how to get a better reporter in C#?
This isn't exactly the structure you asked for but it gets back all of the data in one query:
SELECT
SUM(CASE WHEN Name != Beneficiary AND GenderBeneficiary= 'Female' THEN 1 ELSE 0 END) AS [FemaleFamiliars],
SUM(CASE WHEN Name != Beneficiary AND GenderBeneficiary= 'Male' THEN 1 ELSE 0 END) AS [MaleFamiliars],
SUM(CASE WHEN Name = Beneficiary AND GenderBeneficiary= 'Female' THEN 1 ELSE 0 END) AS [FemaleEmployees],
SUM(CASE WHEN Name = Beneficiary AND GenderBeneficiary= 'Male' THEN 1 ELSE 0 END) AS [MaleEmployees],
SUM(CASE WHEN GenderBeneficiary= 'Female' THEN 1 ELSE 0 END) AS [TotalFemales],
SUM(CASE WHEN GenderBeneficiary= 'Male' THEN 1 ELSE 0 END) AS [TotalMales],
SUM(CASE WHEN Name != Beneficiary THEN 1 ELSE 0 END) AS [TotalFamiliar],
SUM(CASE WHEN Name = Beneficiary THEN 1 ELSE 0 END) AS [TotalEmployees],
COUNT(*) AS [GrandTotal]
FROM Employees
Here is another approach to this. It requires two queries and a UNION ALL but not a huge deal. It is actually quite a few less characters than the previous answer. :)
declare #Something table
(
Name varchar(10)
, Beneficiary varchar(10)
, GenderBeneficiary varchar(10)
)
insert #Something values
('Karla', 'Karla', 'Female')
,('Carl', 'Mandy', 'Female')
,('Mark', 'Lu', 'Male')
,('Erik', 'Math', 'Male')
,('Jhon', 'Jhon', 'Male')
select case when s.Name = s.Beneficiary then 'Employee' else 'Familiar' end
, sum(case when GenderBeneficiary = 'Male' then 1 end)
, sum(case when GenderBeneficiary = 'Female' then 1 end)
, count(*)
from #Something s
group by case when s.Name = s.Beneficiary then 'Employee' else 'Familiar' end
UNION ALL
select Description = 'Total'
, sum(case when GenderBeneficiary = 'Male' then 1 end)
, sum(case when GenderBeneficiary = 'Female' then 1 end)
, count(*)
from #Something s

SQL: Running two queries at once and Assigning Variables

I am basically trying to run these two queries:
SELECT * FROM ProductTable;
SELECT CAST(CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS BIT)
FROM UserTable WHERE id = 41;
Both queries work properly. The first one returns me all the data in ProductTable. The second query returns me either 1 or 0 after checking if the row ID 41 exists
Running them together:
SELECT * FROM ProductTable SELECT CAST(CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS BIT)
FROM UserTable WHERE id = 41
However, when I run this, the second query does not return any value, this is because I have not set a SQL variable name to it.
How can I set a Variable name to the second query such that I can read that value when reading the SQL response?
DECLARE #val BIT
SELECT #val = CAST(CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS BIT)
FROM UserTable WHERE id = 41
SELECT P.*, #val FROM ProductTable P
If you need either 1 or 0 after checking if the row ID 41 exists then (following Pinwar13 answer) this code performs better, needn't count all rows
DECLARE #val BIT = CASE WHEN EXISTS (SELECT 1 FROM UserTable WHERE id = 41)
THEN 1 ELSE 0 END;
SELECT P.*, #val FROM ProductTable P
Try like this,
SELECT *
,(
SELECT CAST(CASE
WHEN COUNT(*) > 0
THEN 1
ELSE 0
END AS BIT)
FROM UserTable
WHERE id = 41
) AS UserCount
FROM ProductTable;
you can use cross apply also..
SELECT p.*,t.[BIT] FROM ProductTable p
CROSS APPLY (SELECT CAST(CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS [BIT])
FROM UserTable WHERE id = 41)t

How to find next (n) open days from SQL opening Hours Database

I am using a SQL database schema similar to the one found on this link.
Best way to store working hours and query it efficiently
I am storing the Opening hours for a location using this basic schema
Shop - INTEGER
DayOfWeek - INTEGER (0-6)
OpenTime - TIME
CloseTime - TIME
What i am trying to do however is for the current DateTime (i.e. today) get the NEXT (n) number of days that the shop is open. So for example if i wasnted to find the next three days that the shop was open and configured in the opening hours the shop is closed on a Sunday and todays date is 21/02/2015 (Saturday) I would like to return the days (21/02/2015)Saturday, (23/02/2015)Monday and (23/02/2015)Tuesday.
If it was Sunday i would return (23/02/2015)Monday, (24/02/2015)Tuesday and (25/02/2015)Wednesday (as its closed on sunday) and finally if it was (20/02/2015)Friday it would return (20/02/2015)Friday, (21/02/2015)Saturday, (23/02/2015)Monday.
I dont know if this is easier to do in SQL or C# but i am mentally struggling in if figuring out how to calculate this.
Any pointers, guidance would be great.
Thank you
This will give you up to 10 days ahead in a fairly efficient way. First test data:
DECLARE #DaysAhead TABLE (
Delta INT
)
INSERT INTO #DaysAhead (Delta)
SELECT 0
UNION ALL SELECT 1
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4
UNION ALL SELECT 5
UNION ALL SELECT 6
UNION ALL SELECT 7
UNION ALL SELECT 8
UNION ALL SELECT 9
UNION ALL SELECT 10
DECLARE #Opening TABLE (
Shop INT,
DayOfWk INT,
DayNm varchar(10),
OpenTime TIME,
CloseTime TIME
)
INSERT INTO #Opening (Shop, DayOfWk, DayNm, OpenTime, CloseTime)
SELECT 1, 5, 'Fri', '09:00', '17:00' --
UNION ALL SELECT 1, 6, 'Sat' ,'09:00', '17:00'
--UNION ALL SELECT 0, 'Sun', '09:00', '17:00' -- Not open on Sunday
UNION ALL SELECT 1, 1, 'Mon', '09:00', '17:00'
UNION ALL SELECT 1, 2, 'Tue', '09:00', '17:00'
UNION ALL SELECT 1, 3, 'Wed', '09:00', '17:00'
Which can be queried like this:
DECLARE #dt datetime='21-Feb-2015'
DECLARE #dow int=datepart(dw, #dt)-1
SELECT TOP 3 o.Shop, o.DayOfWk, o.DayNm, o.OpenTime, o.CloseTime FROM (
SELECT Delta, ((#dow+Delta)%7) as DayOfWk
FROM #DaysAhead
) daysAhead
INNER JOIN #Opening o on o.DayOfWk=daysAhead.DayOfWk
ORDER BY daysAhead.Delta
Results:
DECLARE #dt datetime='20-Feb-2015' -- Fri
1 5 Fri 09:00:00.0000000 17:00:00.0000000
1 6 Sat 09:00:00.0000000 17:00:00.0000000
1 1 Mon 09:00:00.0000000 17:00:00.0000000
DECLARE #dt datetime='21-Feb-2015' -- Sat
1 6 Sat 09:00:00.0000000 17:00:00.0000000
1 1 Mon 09:00:00.0000000 17:00:00.0000000
1 2 Tue 09:00:00.0000000 17:00:00.0000000
DECLARE #dt datetime='22-Feb-2015' -- Sun
1 1 Mon 09:00:00.0000000 17:00:00.0000000
1 2 Tue 09:00:00.0000000 17:00:00.0000000
1 3 Wed 09:00:00.0000000 17:00:00.0000000
First you can use a simple query like the following to get the days of the week that the shop is open
Select DayOfWeek
From OpenHours
Where ShopId = #ShopID
This assumes that there will not be entries for days that are not open. Adjust this query if instead the open hour column is null, or less than or equal to the close time for days that are not open.
After you run that query and get the results back and preferably translate them into a List<DayOfWeek> you can do the following in your code.
List<Day0fWeek> openDays = GetOpenDaysFromDB();
DateTime start = DateToStartFrom;
int n = numberOfDays;
List<DateTime> nextNOpenDays = new List<DateTime>();
while(nextNOpenDays.Count < n)
{
if(openDays.Contains(start.DayOfWeek))
nextNOpenDays.Add(start);
start = start.AddDays(1);
}
You can use a case to make a day earlier in this week look like that day next week. Here's an example to look up the next open day:
select top 1 dateadd(day, day_diff, #dt) as dt
from (
select case
when dayofweek <= datepart(dw, #dt) then dayofweek + 7
else dayofweek
end - datepart(dw, #dt) as day_diff
, *
from dbo.OpeningHours
) sub1
order by
day_diff
You can then recurse to find more than one day. If we store the above snippet in a function called get_next_open_day, the recursive common table expression could look like:
; with cte as
(
select dbo.get_next_open_day(#dt) as open_day
, 1 as day_number
union all
select dbo.get_next_open_day(prev_day.open_day)
, prev_day.day_number + 1
from cte as prev_day
where prev_day.day_number < #number_of_days
)
select cte.open_day
, datename(dw, cte.open_day)
from cte
option (maxrecursion 100)
;
Here's a full working example:
use Test
if object_id('OpeningHours') is not null
drop table OpeningHours;
if object_id('dbo.get_next_open_day') is not null
drop function dbo.get_next_open_day;
create table OpeningHours (dayofweek int, opentime time, closetime time);
insert dbo.OpeningHours values
(2, '9:00', '17:00'),
(3, '9:00', '17:00'),
(4, '9:00', '17:00'),
(5, '9:00', '17:00'),
(6, '9:00', '21:00'),
(7, '10:00', '17:00')
;
go
create function dbo.get_next_open_day(
#dt date)
returns date
as begin return
(
select top 1 dateadd(day, day_diff, #dt) as dt
from (
select case
when dayofweek <= datepart(dw, #dt) then dayofweek + 7
else dayofweek
end - datepart(dw, #dt) as day_diff
, *
from dbo.OpeningHours
) sub1
order by
day_diff
)
end
go
--declare #dt date = '2015-02-18' -- Wed
--declare #dt date = '2015-02-20' -- Fri
declare #dt date = '2015-02-22' -- Sun
declare #number_of_days int = 10
; with cte as
(
select dbo.get_next_open_day(#dt) as open_day
, 1 as day_number
union all
select dbo.get_next_open_day(prev_day.open_day)
, prev_day.day_number + 1
from cte as prev_day
where prev_day.day_number < #number_of_days
)
select cte.open_day
, datename(dw, cte.open_day)
from cte
option (maxrecursion 100)
;
The implementation of multiple shops is left as an exercise for the reader.
Try this:
DECLARE #t TABLE(WeekID INT, OpenTime time)
DECLARE #c INT = 10
INSERT INTO #t VALUES
(1, '10:00'),--sunday
(2, '10:00'),--monday
(4, '10:00'),--wednsday
(5, '10:00')--thursday
;WITH Tally (n) AS
(
-- 1000 rows
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
CROSS JOIN (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
)
SELECT TOP (#c) DATEADD(dd, t.n, GETDATE())
FROM Tally t
JOIN #t s ON DATEPART(w, DATEADD(dd, t.n, GETDATE())) = s.WeekID
Output:
Date
2015-02-22 --sunday
2015-02-23 --monday
2015-02-25 --wednsday
2015-02-26 --thursday
2015-03-01 --sunday
2015-03-02 --monday
2015-03-04 --wednsday
2015-03-05 --thursday
2015-03-08 --sunday
2015-03-09 --monday
PS: You can replace GETDATE() with any date to look from.
I managed to find a solution:
public List<DateTime> getDaysOpen(int numberOfDays, DateTime start)
{
List<byte> openDays = this.getOpeningHoursDays();
List<DateTime> nextNOpenDays = new List<DateTime>();
while (nextNOpenDays.Count < numberOfDays)
{
if (openDays.Contains(Convert.ToByte(start.DayOfWeek)))
nextNOpenDays.Add(start);
start = start.AddDays(1);
}
return nextNOpenDays;
}
public List<byte> getOpeningHoursDays()
{
return db.OpeningHours.Where(oh => oh.LocationId == this.Id).Select(oh => oh.DateOfWeek).ToList();
}
This was in my opinion the easiest method to find a solution. Thank you for all your help.

How count the 1's and 0's with a bit return type and return it in two separate columns

I would like my sql query to select all the fields in a table by one date or between two dates, 3 of the fields in my database have the return type as bits. This is somewhat my database looks
ID||Name || Surname || Age || Country || SumOfInfection || SumOfOtherInfection|| HasPersonContacted
SumOfInfection, SumOfOtherInfection& HasPersonContacted have the return type of bits. For these three fields i; need to sum the numbers of True(1) and False(0) into two separate columns.
Name|| Surname|| .... ||HasPersonContacted(sum of 1's based on a userID) || HasPersonContacted(sum of 0's based on a userID) .....
so what i am looking for
SumOfInfection <- all the 1's for that ID
output= 10 <- so the person had 10 infection
SumOfInfection <- all the 0's for that ID
output= 3 - so the person had no infection for 3 times
i would like to do same for SumOfOtherInfection and HasPersonContacted .
This is what i have done but it only shows the sum of SumOfInfection how do i get all these data in one go? i rely like to use Select * because in future if i am looking for more data i dont have to rewrite my query.
SELECT COUNT(NULLIF(SumOfInfection,1))
From [TableName]
where ID='1234' AND Cast([Time] AS DATE) >'2012-01-09' AND CAST([Time] AS DATE) < '2014-01-01'
Try using conditional sum():
SELECT sum(case when SumOfInfection = 1 then 1 else 0 end) as NumInfection1,
sum(case when SumOfInfection = 0 then 1 else 0 end) as NumInfection0
From [TableName]
where ID='1234' AND Cast([Time] AS DATE) >'2012-01-09' AND CAST([Time] AS DATE) < '2014-01-01' ;
Alternatively, you can cast the bit as an integer:
SELECT sum(cast(SumOfInfection as int)) as NumInfection1,
sum(1 - cast(SumOfInfection as int)) as NumInfection1
EDIT:
I think the full query is more like:
select Name, Surname,
sum(case when HasPersonContacted = 1 then 1 else 0 end) as NumPersonContacted1,
sum(case when HasPersonContacted = 0 then 1 else 0 end) as NumPersonContacted0,
. . .
from t
group by Name, Surname;

Changing Row into Columns

I am currently getting this output from my query:-
Count(Total)| Type1
-----------------
24 T1
22 T2
But I want the output like this:-
T1 T2
----------
24 22
Note that Type1 column can contain any values like T1,T2,T3 so I cannot fix the values in the query. I am using Oracle 10g, how can I do it?
Oracle 10g does not have a PIVOT function so you can use an aggregate with a CASE:
select
sum(case when type1 = 'T1' then total end) T1,
sum(case when type1 = 'T2' then total end) T2
from <yourquery goes here>
See SQL Fiddle with Demo
Or you can implement this directly into a query similar to this, using the SUM() aggregate will count each occurrence that matches the type1 value in the CASE statement:
select
sum(case when type1 = 'T1' then 1 else 0 end) T1,
sum(case when type1 = 'T2' then 1 else 0 end) T2
from yourtable
If you have an unknown number of values to transform into columns, then you will want to use a procedure similar to this:
CREATE OR REPLACE procedure dynamic_pivot(p_cursor in out sys_refcursor)
as
sql_query varchar2(1000) := 'select ';
begin
for x in (select distinct type1 from yourtable order by 1)
loop
sql_query := sql_query ||
' , sum(case when type1 = '''||x.type1||''' then 1 else 0 end) as '||x.type1;
dbms_output.put_line(sql_query);
end loop;
sql_query := sql_query || ' from yourtable';
open p_cursor for sql_query;
end;
/
Then to execute it:
variable x refcursor
exec dynamic_pivot(:x)
print x

Categories

Resources