Changing Row into Columns - c#

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

Related

Creating an Oracle view that takes a parameter

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));

Return one field per row for multiple rows and transform them into a single row of values to be displayed

Looking for suggestions...
I've got a table that has project data and another table that contains a list of project readiness levels. Since it's likely the project will have many readiness levels and each readiness level can be assigned to many project, this has the potential to create a many-to-many relationship so I created a junction table to avoid that.
The junction table is simply:
projectID (int),
readinessLevelID (int), (the range of values is 1-9 for this field)
readinessLevelDate (date)
Since there will be multiple rows for each project, and all I need to return is the readinessLevelDate field for each readinessLevel, I'd like to append these dates on to the end of a record in a project data query instead of creating a row for each readinessLevel (potentially 9 or more rows per project). Each project may go up (or down) the readiness level scale based on testing and evaluation, therefore projects may contain multiple records for many levels. And, of course, all management wants to see is the latest date each level was achieved.
I tried creating a Stored Procedure
CREATE PROCEDURE [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
--ALTER Proc [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
As
Declare #readDate Date
Select #readDate = max(readinessLevelDate)
From projectReadinessLevel
Where projectID = #Proj_ID AND readinessLevelID = 1
return #readDate ;
This returns the error, "Msg 206, Level 16, State 2, Procedure return_TRL_Level_Dates, Line 19 [Batch Start Line 7]
Operand type clash: date is incompatible with int"
First question is, how do I tell the SP that I want to return a "date" value? (Obviously, it's trying to return an "int."
Without the return value (#readDate), the SP runs and returns "0" (the "int").
Next question is, will the SP return a data row that can be appended to a recordset and be displayed?
The initial thought was to create an output variable that would contain a date for each readiness level achieved but, I can't find an example of how to pass that back to the calling procedure in C#.
Any help would be greatly appreciated.
Thanks,
Bob
Your current syntax is more like a scalar function.
In the procedure, return #readDate; is causing the error. In procedures you do not return values like that, but you can return status codes (which are integers).
For returning data from a procedure, you can either return rowsets by select, or you could use output parameters.
e.g.
CREATE PROCEDURE [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
--ALTER Proc [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
As
begin;
Declare #readDate Date
Select #readDate = max(readinessLevelDate)
From projectReadinessLevel
Where projectID = #Proj_ID AND readinessLevelID = 1
Select readDate = #readDate
end;
Which could be simplified to:
CREATE PROCEDURE [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
--ALTER Proc [dbo].[return_Readiness_Level_Dates] (#Proj_ID Int)
As
begin;
Select readDate = max(readinessLevelDate)
From projectReadinessLevel
Where projectID = #Proj_ID AND readinessLevelID = 1
end;
If you want to return 9 readinessLevelDate in a single row, you could use conditional aggregation like so:
create procedure dbo.return_readiness_level_dates (#Proj_ID int) as
begin;
set nocount on;
select
ProjectId
, Level_1_Ready_Date = max(case when readinessLevelId = 1 then readinessLevelDate end)
, Level_2_Ready_Date = max(case when readinessLevelId = 2 then readinessLevelDate end)
, Level_3_Ready_Date = max(case when readinessLevelId = 3 then readinessLevelDate end)
, Level_4_Ready_Date = max(case when readinessLevelId = 4 then readinessLevelDate end)
, Level_5_Ready_Date = max(case when readinessLevelId = 5 then readinessLevelDate end)
, Level_6_Ready_Date = max(case when readinessLevelId = 6 then readinessLevelDate end)
, Level_7_Ready_Date = max(case when readinessLevelId = 7 then readinessLevelDate end)
, Level_8_Ready_Date = max(case when readinessLevelId = 8 then readinessLevelDate end)
, Level_9_Ready_Date = max(case when readinessLevelId = 9 then readinessLevelDate end)
from ProjectReadinessLevel
where ProjectId = #Proj_ID
group by ProjectId;
end;
go

A more refined version of this LINQ to SQL query

My conundrum is with trying to convert the following T-SQL query into a near equivalent (performance wise) LINQ to SQL query:
SELECT
j1.JOB,
max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'PREP' THEN 'X' ELSE ' ' END) AS prep,
max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'PRINT' THEN 'X' ELSE ' ' END) AS press,
max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'BIND' THEN 'X' ELSE ' ' END) AS bind,
max(CASE WHEN ISNULL(logs.statcategory, ' ') = 'SHIP' THEN 'X' ELSE ' ' END) AS ship
from
job j1
left outer join
(
select
j.job,
l.statcategory,
cnt=count(*)
from
job j
join
jobloc jl
join location l
on
l.code = jl.location and
l.site = jl.site
on j.job = jl.job
WHERE
j.stat = 'O'
group by
j.job,l.statcategory
) logs
on
j1.job = logs.job
WHERE
j1.stat = 'O'
group by
j1.job
This query currently runs just under 0.2 seconds on MS SQL Server. The following LINQ query is what I've come up with that returns the exact same records, but runs nearly 30x slower:
from a0 in Jobs
join a1 in
(
from a0 in Jobs
join a1 in JobLocs on a0.Content equals a1.Job
join a2 in Locations on new {Code = a1.Location, a1.Site} equals new {a2.Code, a2.Site}
where a0.Stat == 'O'
select new {a0.Content, a2.StatCategory}
) on a0.Content equals a1.Content into a1
from a2 in a1.DefaultIfEmpty()
where a0.Stat == 'O'
group a2 by a0.Content into a0
orderby a0.Key
select new
{
Job = a0.Key,
Prep = (bool?)a0.Max(a1 => a1.StatCategory == "PREP" ? true : false),
Print = (bool?)a0.Max(a1 => a1.StatCategory == "PRINT" ? true : false),
BIND = (bool?)a0.Max(a1 => a1.StatCategory == "BIND" ? true : false),
SHIP = (bool?)a0.Max(a1 => a1.StatCategory == "SHIP" ? true : false),
}
Here is the generated SQL from the LINQ query (using LINQPad):
-- Region Parameters
DECLARE #p0 Int = 79
DECLARE #p1 Int = 79
DECLARE #p2 VarChar(1000) = 'PREP'
DECLARE #p3 VarChar(1000) = 'PRINT'
DECLARE #p4 VarChar(1000) = 'BIND'
DECLARE #p5 VarChar(1000) = 'SHIP'
-- EndRegion
SELECT [t4].[Job], [t4].[value] AS [Prep], [t4].[value2] AS [Print], [t4].[value3] AS [BIND], [t4].[value4] AS [SHIP]
FROM (
SELECT MAX(
(CASE
WHEN [t3].[StatCategory] = #p2 THEN 1
WHEN NOT ([t3].[StatCategory] = #p2) THEN 0
ELSE NULL
END)) AS [value], MAX(
(CASE
WHEN [t3].[StatCategory] = #p3 THEN 1
WHEN NOT ([t3].[StatCategory] = #p3) THEN 0
ELSE NULL
END)) AS [value2], MAX(
(CASE
WHEN [t3].[StatCategory] = #p4 THEN 1
WHEN NOT ([t3].[StatCategory] = #p4) THEN 0
ELSE NULL
END)) AS [value3], MAX(
(CASE
WHEN [t3].[StatCategory] = #p5 THEN 1
WHEN NOT ([t3].[StatCategory] = #p5) THEN 0
ELSE NULL
END)) AS [value4], [t0].[Job]
FROM [Job] AS [t0]
LEFT OUTER JOIN ([Job] AS [t1]
INNER JOIN [JobLoc] AS [t2] ON [t1].[Job] = [t2].[Job]
INNER JOIN [Location] AS [t3] ON ([t2].[Location] = [t3].[Code]) AND ([t2].[Site] = [t3].[Site])) ON ([t0].[Job] = [t1].[Job]) AND (UNICODE([t1].[Stat]) = #p0)
WHERE UNICODE([t0].[Stat]) = #p1
GROUP BY [t0].[Job]
) AS [t4]
ORDER BY [t4].[Job]
One thing that stands out is that the generated SQL from the LINQ query runs the aggregate for each column returned in a subquery, whereas in the original it is part of the outer SELECT. I can imagine part of the performance decrease is there.
I'm (tentatively) willing to accept that there is no better way to write this, and just use the DataContext.ExecuteQuery() method in the LINQ API (and just run and shape the first SQL statement directly). However, I'm trying to not include embedded SQL as much as possible in a project that I'm currently working on, so if it can be made to be near the performance of the original query, that'd be ideal. I've been hacking away at this for some time (partly as an academic exercise, and also to actually use this or similar queries like it), and this is the best I've come up with (I did not write the original query BTW--it was part of an older project that is being migrated to a newer one).
Thanks for any assistance.
As per our discussion in the comments,
The issue is the UNICODE conversion that the linq-to-entities adds from some unknown reason.
the DB cannot use the index because of the (unnecessary) conversion.
You can use .Equals instead of == and it will not use UNICODE or change the type to varchar(1) in the db.

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 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;

Categories

Resources