Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Brad.

Asked: May 23, 2004 - 1:10 am UTC

Last updated: May 27, 2004 - 8:22 pm UTC

Version: 9.2.0

Viewed 1000+ times

You Asked


I would like your help with SQL to drive a report on customer survey responses. The main problem is needing to account for responses that were NOT given.

-- Dim table containing all potential survey responses
--
create table survey_response_dim
( survey_response_dim_pk number(10)
, survey_name char(8)
, question_code char(4)
, response_code char(4)
, response_descr varchar2(60));

-- Fact table containing customer survey responses
--
create table cust_survey_resp_fct
( customer_pk number(10)
, survey_response_dim_pk number(10)
, response_date date);

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 1, 'S1', 'Q1', 'F', 'FEMALE' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 2, 'S1', 'Q1', 'M', 'MALE' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 3, 'S1', 'Q2', 'A', '0-10' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 4, 'S1', 'Q2', 'B', '11-30' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 5, 'S2', 'Q1', '1', 'Strongly Agree' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 6, 'S2', 'Q1', '2', 'Agree' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 7, 'S2', 'Q1', '3', 'Disagree' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 8, 'S2', 'Q1', '4', 'Strongly Disagree' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 9, 'S1', 'Q3', 'Y', 'Yes' );

INSERT INTO survey_response_dim
( survey_response_dim_pk, survey_name, question_code, response_code, response_descr )
VALUES ( 10, 'S1', 'Q3', 'N', 'No' );

-- Fact table containing customer survey responses
--
create table cust_survey_resp_fct
( customer_pk number(10)
, survey_response_dim_pk number(10)
, response_date date);

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 10, 2, to_date('05/21/2004', 'mm/dd/yyyy') );

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 10, 4, to_date('05/21/2004', 'mm/dd/yyyy') );

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 20, 3, to_date('05/21/2004', 'mm/dd/yyyy') );

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 30, 2, to_date('05/21/2004', 'mm/dd/yyyy') );

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 30, 3, to_date('05/21/2004', 'mm/dd/yyyy') );

INSERT INTO cust_survey_resp_fct
( customer_pk, survey_response_dim_pk, response_date )
VALUES ( 40, 6, to_date('05/22/2004', 'mm/dd/yyyy') );


Select sd.survey_name, sd.question_code
, sd.response_code, count(*) as responders
from cust_survey_resp_fct s
, survey_response_dim sd
where s.survey_response_dim_pk = sd.survey_response_dim_pk
group by sd.survey_name, sd.question_code, sd.response_code


SURVEY_NAME QUESTION_CODE RESPONSE_CODE RESPONDERS
S1 Q1 M 2.00
S1 Q2 A 2.00
S1 Q2 B 1.00
S2 Q1 2 1.00


I would like to also account for responses that were NOT given. The report would look similar to:

SURVEY_NAME QUESTION_CODE RESPONSE_CODE RESPONDERS
S1 Q1 F 0.00
S1 Q1 M 2.00
S1 Q2 A 2.00
S1 Q2 B 1.00
S1 Q3 N 0.00
S1 Q3 Y 0.00
S2 Q1 2 1.00

You addressed a similar issue (missing dates in a transaction table) in a recent article in Oracle Magazine but I just didn't "get" it. Hopefully, I'll clue in this time. Thanks for your help.

and Tom said...

You need a table of customer ids and survey question ids -- all combinations. We'll outer join to that and you'll get them all.

I have to assume in your schema, these objects already exist, if not, you'll need to supply the data somehow. We need distinct customer ids:

ops$tkyte@ORA9IR2> drop table cust;
Table dropped.

ops$tkyte@ORA9IR2> create table cust
2 as
3 select distinct customer_pk from cust_survey_resp_fct;
Table created.

we already have the survey question ids (the primary key of the survey response dim table) so we can cartesian product them in to X below:


ops$tkyte@ORA9IR2> Select sd.survey_name, sd.question_code
2 , sd.response_code, count(s.customer_pk) as responders
3 from cust_survey_resp_fct s,
4 survey_response_dim sd,
5 (select survey_response_dim.survey_response_dim_pk, cust.customer_pk from survey_response_dim,cust) X
6 where s.survey_response_dim_pk (+) = x.survey_response_dim_pk
7 and s.customer_pk (+) = x.customer_pk
8 and sd.survey_response_dim_pk = x.survey_response_dim_pk
9 group by sd.survey_name, sd.question_code, sd.response_code
10 /

SURVEY_N QUES RESP RESPONDERS
-------- ---- ---- ----------
S1 Q1 F 0
S1 Q1 M 2
S1 Q2 A 2
S1 Q2 B 1
S1 Q3 N 0
S1 Q3 Y 0
S2 Q1 1 0
S2 Q1 2 1
S2 Q1 3 0
S2 Q1 4 0

10 rows selected.


In 10g, you'll have partitioned outer joins for doing the same -- without the cartesian product:
</code> https://asktom.oracle.com/Misc/oramag/on-uniqueness-space-and-numbers.html <code>


Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Different, but similar requirement

Brad Peek, May 26, 2004 - 1:04 am UTC

Tom - Sorry, but I provided a different desired output than I had intended and still need your help.

What I should have said is that I need to account for questions that were not answered (rather than responses not given). Using the same tables and data as before, the desired output would look similar to this:

SURVEY_N QUES RESP RESPONDERS
-------- ---- ---- ----------
S1 Q1 F 0
S1 Q1 M 2
S1 Q1 N/A 1 <= 1 person did not answer
S1 Q2 A 2
S1 Q2 B 1
S1 Q2 N/A 0 <= 0 persons did not answer
S1 Q3 N 0
S1 Q3 Y 0
S1 Q3 N/A 3 <= 3 persons did not answer
S2 Q1 1 0
S2 Q1 2 1
S2 Q1 3 0
S2 Q1 4 0
S2 Q1 N/A 0

The sum(responders) for each survey/question would include all persons who took the survey and there would be the additional N/A (no answer) response containing the number of persons who did not answer that particular question.

If needed, we could add a N/A response to the Survey_Response_Dim but we are trying to avoid actually storing the N/A responses in the Cust_Survey_Resp_Fct table. This may be just a slight variation of the original question, but I'm not seeing the solution.

Tom Kyte
May 26, 2004 - 8:02 am UTC

You had "4" customers -- should not the 1/0/3 be in fact 2/1/4?

Here you go:

ops$tkyte@ORA9IR2> break on question_code skip 1
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>  Select sd.survey_name,
  2          sd.question_code ,
  3                  decode( grouping_id(sd.response_code), 0, sd.response_code, 'N/A' ) response_code,
  4          decode( grouping_id(sd.response_code), 0, count(s.customer_pk),
  5                     (select count(*) from cust)-count(s.customer_pk)) as responders
  6    from cust_survey_resp_fct s,
  7         survey_response_dim sd,
  8             (select survey_response_dim.survey_response_dim_pk, cust.customer_pk from survey_response_dim,cust) X,
  9                     (select count(*) tot_cust from cust) Y
 10   where s.survey_response_dim_pk (+) = x.survey_response_dim_pk
 11     and s.customer_pk (+) = x.customer_pk
 12     and sd.survey_response_dim_pk = x.survey_response_dim_pk
 13   group by grouping sets( (sd.survey_name, sd.question_code, sd.response_code ),
 14                           (sd.survey_name, sd.question_code ) )
 15  /
 
SURVEY_N QUES RESP RESPONDERS
-------- ---- ---- ----------
S1       Q1   F             0
S1            M             2
S1            N/A           2
 
S1       Q2   A             2
S1            B             1
S1            N/A           1
 
S1       Q3   N             0
S1            Y             0
S1            N/A           4
 
S2       Q1   1             0
S2            2             1
S2            3             0
S2            4             0
S2            N/A           3
 
 
14 rows selected.
 
 

Exactly what I needed!!

Brad Peek, May 26, 2004 - 1:11 pm UTC

Thanks for your help. This site is making the entire Oracle community more effective.

Regarding the difference between the responders counts in my desired results versus your solution: The distinction is that customer 40 is the only one who took survey S2. He would not be counted as not having answered questions in survey S1 and the 3 persons who only took survey S1 would not be counted as not having answered questions in S2. I think I can get around that issue using your solution as the basis.

Thanks again.

Grouping ID did the trick

Brad, May 27, 2004 - 11:55 am UTC

Tom - Just wanted to show off the final version of the SQL. The grouping ID technique you demonstrated worked brilliantly. Unless I'm missing something, it doesn't look like I needed the cartesians to solve this particular issue so I took them out.

Select sd.survey_name
, sd.question_code
, decode( grouping_id(sd.response_code), 0, sd.response_code, 'N/A' ) response_code
, decode( grouping_id(sd.response_code), 0, count(s.customer_pk),
(select count(distinct customer_pk)
from cust_survey_resp_fct csf2
, survey_response_dim sd2
where csf2.survey_response_dim_pk = sd2.survey_response_dim_pk
and sd2.survey_name = sd.survey_name) - count(s.customer_pk)
) as responders
from cust_survey_resp_fct s
, survey_response_dim sd
where sd.survey_response_dim_pk = s.survey_response_dim_pk (+)
group by grouping sets( (sd.survey_name, sd.question_code, sd.response_code )
, (sd.survey_name, sd.question_code ) ) ;

On my system, the rusults of this query exactly matched my desired results (the second one that is).

Tom Kyte
May 27, 2004 - 8:22 pm UTC

yes, you probably don't need the cartesian join for that one in hindsight.

(grouping sets, rollup and cube are *cool*)