Sort the Items with Stored Procedure - sorting

So I have following example from my database:
"John" "1"
"Diva" "1"
"Christ" "2"
"Azzam" "2"
"Sunny" "3"
"Daniel" "3"
"Alex" "4"
"Mike" "4"
Two Names can have same NameID. Now I want to sort in such a way that when I pass the parameter "NameID" it will show Name related to that Id at first and the other Name close to that ID respectively. Example If I pass the parameter "3" to Stored Procedure the result should be:
"Sunny" "3"
"Daniel" "3"
"Alex" "4"
"Mike" "4"
"Christ" "2"
"Azzam" "2"
"John" "1"
"Diva" "1"
What I did till now?
Select * From Database
Order by NameID
I couldn't go further than that.

Use a CTE or subquery to assign a weight to the matching NameIDs. Then use that weight so those names appear first in the result set.
Here's an example from SQL Server, but the basic idea can be used in almost any SQL dialect:
SET NOCOUNT ON;
DECLARE #name_id_parameter INT = 3;
DECLARE #names TABLE
(
[name] NVARCHAR(MAX) NOT NULL,
[name_id] INT NOT NULL
);
INSERT INTO #names
(
[name],
[name_id]
)
VALUES
('John', 1),
('Diva', 1),
('Christ', 2),
('Azzam', 2),
('Sunny', 3),
('Daniel', 3),
('Alex', 4),
('Mike', 4);
/* Solution #1: Using a common table expression (CTE) */
WITH cte([name], [name_id], [sort_weight])
AS
(
/* Assign a higher "weight" to the [name_id]s that match
#name_id_parameter, so those [name_id]s will appear
first in the final result set. */
SELECT
[name],
[name_id],
CASE
WHEN [name_id] = #name_id_parameter THEN 1
ELSE 0
END
FROM
#names
)
SELECT
[name],
[name_id]
FROM
cte
ORDER BY
[sort_weight] DESC, [name_id] DESC
/* Solution #2: Using a subquery */
SELECT
S.[name],
S.[name_id]
FROM
/* Assign a higher "weight" to the [name_id]s that match
#name_id_parameter, so those [name_id]s will appear
first in the final result set. */
(SELECT
[name],
[name_id],
[sort_weight] =
CASE
WHEN [name_id] = #name_id_parameter THEN 1
ELSE 0
END
FROM
#names) AS S
ORDER BY
S.[sort_weight] DESC, S.[name_id] DESC

DECLARE #ID AS NVARCHAR(50)
SET #ID = '3';
SELECT *
FROM ( SELECT TOP(100)
[Name] ,
[NameID]
FROM [Test].[dbo].[Table_1]
WHERE NameID = #ID
ORDER BY NameID DESC
) a
UNION ALL
SELECT *
FROM ( SELECT TOP(100)
[Name] ,
[NameID]
FROM [Test].[dbo].[Table_1]
WHERE NameID #ID
ORDER BY NameID DESC
) b

Here is I think what you want:
DECLARE #t TABLE ( name VARCHAR(20), id INT )
INSERT INTO #t
VALUES ( 'John', '1' ),
( 'Diva', '1' ),
( 'Christ', '2' ),
( 'Azzam', '2' ),
( 'Sunny', '3' ),
( 'Daniel', '3' ),
( 'Alex', '4' ),
( 'Mike', '4' )
declare #id int = 3
SELECT * FROM #t
ORDER BY ABS(#id - id), #id - id, name
Output:
name id
Daniel 3
Sunny 3
Alex 4
Mike 4
Azzam 2
Christ 2
Diva 1
John 1

Related

Oracle sql query to convert common rows to columns

I had a requirement to build a survey like application. Did this using Oracle APEX 18.2. I'm done designing the table structures and their relationships. I'm now on the part where I need to build a report (can be Interactive Report/Classic Report) where the columns will depend on what questions are available.
So basically, each question is supposed to have a column in the report. I can build the report and add these questions as column on design time. But I was wondering if there is a way to do this at runtime as well? More questions can be added in the future and I would like to report to be flexible enough to accommodate this. For now I would like to try via SQL statement.
Here's my table design for refence.
create table persons (
id number primary key
, name varchar2(100) not null
)
/
insert into persons values ( 1, 'Bruce' )
/
insert into persons values ( 2, 'Jennifer' )
/
create table questions (
id number primary key
, question varchar2(100) not null
)
/
insert into questions values ( 1, 'Gender' )
/
insert into questions values ( 2, 'Currently Employed' )
/
create table choices (
id number primary key
, choice varchar2(100) not null
)
/
insert into choices values ( 1, 'Male' )
/
insert into choices values ( 2, 'Female' )
/
insert into choices values ( 3, 'Yes' )
/
insert into choices values ( 4, 'No' )
/
create table question_choices (
id number primary key
, question_id number
, choice_id number
)
/
insert into question_choices values ( 1, 1, 1 )
/
insert into question_choices values ( 2, 1, 2 )
/
insert into question_choices values ( 3, 2, 3 )
/
insert into question_choices values ( 4, 2, 4 )
/
create table survey (
id number primary key
, name varchar2(100) not null
)
/
insert into survey values ( 1, 'Survey 1' )
/
create table survey_questions (
id number primary key
, survey_id number
, question_id number
)
/
insert into survey_questions values ( 1, 1, 1 )
/
insert into survey_questions values ( 2, 1, 2 )
/
create table survey_responses (
id number primary key
, survey_id number
, person_id number
, question_id number
, choice_id number
)
/
insert into survey_responses values ( 1, 1, 1, 1, 1 )
/
insert into survey_responses values ( 2, 1, 1, 2, 4 )
/
insert into survey_responses values ( 3, 1, 2, 1, 2 )
/
insert into survey_responses values ( 4, 1, 2, 2, 3 )
/
commit
/
I did a bit or research and found out about the pivot command. However, this approach also requires that the columns be defined at design time.
select *
from (
select s.name survey
, p.name respondent
, q.question
, c.choice
, row_number() over (partition by person_id, question order by choice ) rn
from persons p
, questions q
, choices c
, question_choices qc
, survey s
, survey_questions sq
, survey_responses sr
where p.id = sr.person_id
and q.id = sr.question_id
and c.id = qc.choice_id
and s.id = sq.survey_id
and sq.question_id = q.id
and sr.choice_id = c.id
)
pivot (max(question) question, max(choice) for question in ( 'Gender','Currently Employed' ))
Appreciate any comments/suggestions.

Managing dates when they're overlapping

How to write a procedure so that when adding a new insert, rows are added appropriately?
Let's say i have a table:
create table test_table
(
code varchar2(10) not null,
type varchar2(50) not null,
start_date date not null,
end_date date not null,
parameter number
);
1. First test case:
In table we have:
insert into test_table values ('CODE', 'a', to_date('01.01.2021', 'DD,MM,YYYY'), to_date('10.01.2021', 'DD,MM,YYYY'), 1);
[2021-01-01 - 2021-01-10] type = "a" parameter = 1
and when we want to insert:
insert into test_table values ('CODE', 'a', to_date('01.01.2021', 'DD,MM,YYYY'), to_date('20.01.2021', 'DD,MM,YYYY'), 1)
[2021-01-11 - 2021-01-20] type = "a" parameter = 1
*Result should be:
2021-01-01 - 2021-01-20 type = "a" parameter = 1*
2. Second test case:
In table we have:
insert into test_table values ('CODE', 'a', to_date('01.01.2021', 'DD,MM,YYYY'), to_date('10.01.2021', 'DD,MM,YYYY'), 1)
[2021-01-01 - 2021-01-10] type = "a" parameter = 1
and when we want to insert:
insert into test_table values ('CODE', 'a', to_date('06.01.2021', 'DD,MM,YYYY'), to_date('20.01.2021', 'DD,MM,YYYY'), 2)
[2021-01-06 - 2021-01-20] type = "a" parameter = 2
*in result we should have:
[2021-01-01 - 2021-01-05] type = "a" parameter = 1
[2021-01-06 - 2021-01-20] type = "a" parameter = 2*
3. Third test case:
In table we have:
insert into test_table values ('CODE', 'a', to_date('01.01.2021', 'DD,MM,YYYY'), to_date('10.01.2021', 'DD,MM,YYYY'), 1)
[2021-01-01 - 2021-01-20] type = "a" parameter = 1
and when we want to insert:
insert into test_table values ('CODE', 'a', to_date('06.01.2021', 'DD,MM,YYYY'), to_date('15.01.2021', 'DD,MM,YYYY'), 2)
[2021-01-06 - 2021-01-15] type = "a" parameter = 2
*in result we should have:
[2021-01-01 - 2021-01-05] type = "a" parameter = 1
[2021-01-06 - 2021-01-15] type = "a" parameter = 2
[2021-01-16 - 2021-01-20] type = "a" parameter = 1*
When you insert a new date range that is completely contained in the middle of an existing date range then you need to: INSERT of the new range; UPDATE the existing range to the portion of that range before the new range; and INSERT a new range for the portion of the existing range after the new range. So you need a total of 3 changes.
Similarly, when you insert a new date range that completely contains an existing range then you need to: INSERT the new range; and DELETE the existing range (or do a single UPDATE statement).
You can use a single MERGE statement for all of these actions:
MERGE INTO test_table dst
USING (
WITH new_data (code, type, start_date, end_date, parameter) AS (
SELECT 'CODE2', 'a', DATE '2021-01-01', DATE '2021-01-20', 2 FROM DUAL
)
SELECT NULL AS rid,
n.*,
0 AS status -- Insert
FROM new_data n
UNION ALL
-- Existing rows overlapping before
SELECT t.ROWID,
t.code,
t.type,
t.start_date,
n.start_date - INTERVAL '1' DAY,
t.parameter,
1 -- Update overlap before
FROM test_table t
INNER JOIN new_data n
ON ( t.start_date <= n.start_date
AND t.end_date >= n.start_date)
UNION ALL
SELECT t.ROWID,
t.code,
t.type,
n.end_date + INTERVAL '1' DAY,
t.end_date,
t.parameter,
CASE
WHEN n.start_date <= t.end_date AND t.end_date <= n.end_date
THEN 2 -- Delete
WHEN t.start_date < n.start_date AND n.end_date < t.end_date
THEN 0 -- Insert overlap afterwards
ELSE 1 -- Update overlap afterwards
END
FROM test_table t
INNER JOIN new_data n
ON ( t.start_date <= n.end_date
AND t.end_date >= n.start_date)
WHERE NOT (t.start_date <= n.start_date AND t.end_date <= n.end_date)
) src
ON (src.rid = dst.ROWID AND status > 0)
WHEN MATCHED THEN
UPDATE
SET code = src.code,
start_date = src.start_date,
end_date = src.end_date
DELETE
WHERE status = 2
OR src.start_date > src.end_date
WHEN NOT MATCHED THEN
INSERT (code, type, start_date, end_date, parameter)
VALUES (src.code, src.type, src.start_date, src.end_date, src.parameter);
db<>fiddle here

Sum values of columns for multiple groups in Oracle table

I have a table with a list of people, gender, race, & their group. It has 2000 records divided into 8 groups. I am trying to use PL/SQL to get the sum of people per gender per race for each group. I was thinking I should have a cursor populate a variable called v_group with the 8 groups, and then loop through all the records and get the counts for each one. It will put each count into their own variable and then insert the new summed up counts and the corresponding group into a table.
Here is a sample of what I have so far.
OPEN cur_get_group;
LOOP
v_group := '';
FETCH cur_get_group
INTO v_group;
EXIT WHEN cur_get_group%NOTFOUND;
SELECT COUNT(*) INTO v_hisp_m FROM mytable WHERE sex = 'M' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_hisp_f FROM mytable WHERE sex = 'F' AND ethn_code = 'H';
SELECT COUNT(*) INTO v_cauc_m FROM mytable WHERE sex = 'M' AND ethn_code = 'C';
SELECT COUNT(*) INTO v_cauc_f FROM mytable WHERE sex = 'F' AND ethn_code = 'C';
INSERT INTO mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
VALUES
(v_group, v_hisp_m, v_hisp_f, v_cauc_m, v_cauc_f);
COMMIT;
END LOOP;
Am I on the right track here? Do I need to do the loop differently?
Relational data bases work on the set of data matching conditions and are very good at it. They are however very poor single item processing. Learn to think is terms of sets. Your process has to pass the source data table 5 times (1 for group and count process) While loops are sometimes a necessary evil they are a process of last resort. This can be done in 1 statement, passing the source 1 time.
insert into mynewtable
(group, hisp_m, hisp_f, cauc_m, cauc_f)
select group_code
, sum(hisp_m)
, sum(hisp_f)
, sum(cauc_m)
, sum(cauc_f)
from
( select group_code,
, case when sex = 'F' AND ethn_code = 'H' then 1 else 0 end hisp_f
, case when sex = 'M' AND ethn_code = 'H' then 1 else 0 end hisp_m
, case when sex = 'F' AND ethn_code = 'C' then 1 else 0 end cauc_f
, case when sex = 'M' AND ethn_code = 'C' then 1 else 0 end cauc_m
from youroldtable
)
group by group_code;
BTW DO NOT use group as a column name. It is a reserved word and doing so will lead to very difficult to find errors.
Looks to me like you want to use pivot:
with rws as (
select 'F' sex, 'H' eth from dual connect by level <= 3
union all
select 'F' sex, 'C' eth from dual connect by level <= 4
union all
select 'M' sex, 'H' eth from dual connect by level <= 2
union all
select 'M' sex, 'C' eth from dual
)
select * from rws
pivot (
count (*) for ( sex, eth ) in (
( 'F', 'H' ) fh,
( 'F', 'C' ) fc,
( 'M', 'H' ) mh,
( 'M', 'C' ) mc
)
);
FH FC MH MC
3 4 2 1
And insert the result of this query to the table.

Oracle Shuffle columns in rows

Based in the solution if this issue - Shuffle a column between rows - I need to apply to this, a new condition: the names must guarantee the gender: male, female or unknown.
So, my table have a column named gender. I need to shuffle the columns whit the same gender.
I've tried this, but in some cases I cannot guarantee the gender
merge into original_table o
using (
with
helper ( id, gender, rn, rand_rn ) as (
select id,
gender,
row_number() over (order by id),
row_number() over (order by dbms_random.value())
from original_table
)
select ot.name, ot.gender, h2.id
from original_table ot inner join helper h1 on (ot.id = h1.id and ot.gender = h1.gender)
inner join helper h2 on h1.rand_rn = h2.rn
) p
on (o.id = p.id)
when matched then update set o.name = p.name
;
And in cases that gender is not available (is null value), for example company's name, this merge not update anything.
I can split this query in 3 different merge statement. One for each gender that I have. But I'm looking for a better and simply statement. Because I need to apply the same solution in a different context and with different conditions.
Thanks.
EDIT: Sample data
ID GENDER NAME
3721 M MARK
3722 M JUSTIN
3723 F RUTH
3724 F MARY
3725 F ANNE
4639 CAMPANY SA
4640 M JOHN
4641 M LUCAS
4642 COMPANY HOLDER SA
One possible solution:
ID GENDER NAME
3721 M LUCAS
3722 M JOHN
3723 F MARY
3724 F ANNE
3725 F RUTH
4639 CAMPANY HOLDER SA
4640 M MARK
4641 M JUSTIN
4642 COMPANY SA
Include gender in the PARTITION BY clause in the analytic functions and in the JOIN clause. If you don't have a primary key to match on then you could use the ROWID pseudo-column.
Oracle Setup:
CREATE TABLE original_table ( id, gender, name, company ) AS
SELECT 1, 'F', 'Alice', CAST( NULL AS VARCHAR2(20) ) FROM DUAL UNION ALL
SELECT 2, 'M', 'Bobby', 'ACME' FROM DUAL UNION ALL
SELECT 3, 'F', 'Carol', 'XYZ' FROM DUAL UNION ALL
SELECT 4, 'M', 'David', NULL FROM DUAL UNION ALL
SELECT 5, 'M', 'Errol', 'ACME' FROM DUAL UNION ALL
SELECT 6, 'F', 'Fiona', 'XYZ' FROM DUAL;
Update:
MERGE INTO original_table dst
USING (
WITH rnd ( rid, rn, rnd_rn, gender, name ) AS (
SELECT ROWID,
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY id ),
ROW_NUMBER() OVER ( PARTITION BY gender ORDER BY DBMS_RANDOM.VALUE ),
gender,
name
FROM original_table
)
SELECT o.rid, r.name
FROM rnd o INNER JOIN rnd r
ON ( ( o.gender = r.gender OR ( o.gender IS NULL AND r.gender IS NULL ) )
AND o.rn = r.rnd_rn )
)
ON ( dst.ROWID = src.ROWID )
WHEN MATCHED THEN
UPDATE SET name = src.name;
Output:
SELECT * FROM original_table;
ID G NAME COMPANY
-- - ----- -------
1 F Fiona
2 M David ACME
3 F Alice XYZ
4 M Bobby
5 M Errol ACME
6 F Carol XYZ

Codeigniter table join and count, how to get count 0 return?

I have two tables.
table A ( id, title, department )
1, title 1 , 1
2, title 2 , 1
3, title 3 , 2
table B ( uid, mid , is_active )
1, 1, 1
2, 1, 1
3, 3, 1
And here is my query statement:
$this->db->select(" id, title ")
->select(' count( B.uid ) AS B_count ')
//->select(' IFNULL( COUNT( `B.uid`), 0) AS B_count ', false)
//->select( " IFNULL( COUNT(DISTINCT B.uid) AS B_count , 0 )" )
->join( 'B', ' B.mid = A.id ' , 'left')
->where('department', 1 )
->where('B.is_active' , 1 )
->limit( $limit, $offset );
$this->db->group_by( array("B.mid") );
return $this->get_all();
And i expect to get result like this
id, title , B_count
1, title 1, 2
2, title 2, 0
However i can get only the first record but no the second record.
And i already have tried IFNULL function and left join tables.
really don't know how to solve the issue.
Does anyone know the solution or what the problem is? Thanks in advance.
use get('A') instead of get_all()

Resources