SQL One to Many join getting single result per join - sql

I want to export data from a SQL 2008 server to a file, csv or excel doesn't matter. In the database I have two entities, questions and answers. The problem is that the questions has multiple answers. A join will then result in multiple rows per each question. How can I do so the result is just one row per question and answers, just adding a new column per each answer. Like this:
Table structure:
Question
-------------
Id
Text
Category
Answer
-------------
Id
Text
IsCorrect
QuestionId
Example of result:
Col1 Col2 Col3 Col4 Col5 Col6 Col7
Result1: Question1, Question1Text, Answer1Prop1, Answer1Prop2, Answer2Prop1, Answer2Prop2, null
Result2: Question2, Question2Text, Answer3Prop1, Answer3Prop2, Answer4Prop1, Answer4Prop2, Answer5Prop3

If you can produce a result with your joins containing all the questions and all the answers with multiple rows, like you describe, I think you could use a cursor to loop over all the questions and answers, and then have a temptable/"hashtable" (#tableOfAnswers) to hold your final result. For each answer for a question you alter the #table and add a new column.
Something like this might put you in the right direction.
If you have a result looking something like this:
(In my example I've stored the result below in a temp table called "#q")
QuestionId text
1 answer1
1 answer2
1 answer3
1 answer4
2 answer1
2 answer2
Then you can declare a cursor on the result
Declare #questionId int
, #questionText varchar(max)
, #prevId int
, #colNo int = 1
, #colMax int = 0
, #i int = 1
, #sql nvarchar(max)
, #nulls nvarchar(max) = ''
Create table #tempTable
(
QuestionId int not null,
Col1 varchar(max) null
)
Declare question_cursor cursor for
Select QuestionId, text
From #q
Open question_cursor
FETCH NEXT FROM question_cursor
INTO #questionId, #questionText
Set #prevId = 0
WHILE ##FETCH_STATUS = 0
BEGIN
If (#prevId = #questionId)
Begin
If (#colNo > #colMax)
Begin
Set #sql = N'alter table #tempTable add Col' + cast(#colNo as varchar) + ' varchar(max) null'
exec sp_executesql #statement = #sql
Set #colMax = #colMax + 1
End
Set #sql = N'update #tempTable set Col' + cast(#colNo as varchar) + ' = ''' + #questionText + ''' where QuestionId = ' + cast(#questionId as varchar)
print #sql
exec sp_executesql #statement = #sql
Set #colNo = #colNo + 1
End
Else Begin
Set #prevId = #questionId
Set #colNo = 1
while (#i <= #colMax)
begin
set #nulls = #nulls + ', null'
set #i = #i + 1
end
Set #sql = N'insert into #tempTable values (' + cast(#questionId as varchar) + ', ''' + #questionText + '''' + isnull(#nulls, '') +')'
print #sql
exec sp_executesql #statement = #sql
Set #colNo = #colNo + 1
End
FETCH NEXT FROM question_cursor
INTO #questionId, #questionText
END
Close question_cursor
Deallocate question_cursor
Select * from #tempTable
That will produce a result like this:
QuestionId Col1 Col2 Col3 Col4
1 text1 text2 text3 text4
2 text1 text2 NULL NULL
Not quite as elegant as .Net (or javascript even) would handle a problem like this.. But well, what you gonna do? :)
I hope that might shed some light on your problem :)
And for exporting it to a file I would just copy/paste the result in Sql Management Studio and paste it in Excel or something ;)

Related

SQL - How do I get 1 empty column per row in a related table?

I am trying to write a SQL query that adds a certain amount of empty columns, based on the number of rows in a related table (t1) for a Crystal Report. These columns should have the header of the name of the dataset.
So it should look something like this:
However I would need to change the script each time a new row gets added (e.g. opening a store - not very often, but it does happen).
I thought about using the pivot function, but I believe the number of rows must be defined - plus, there is no calculation / aggregation happening.
Does anybody have an idea on how to solve this?
As Larnu already mentioned, dynamic SQL would be one way to go. I would suggest using a combination of XML PATH and dynamic SQL. Following an example:
DECLARE #colList VARCHAR(MAX) = (SELECT STUFF((SELECT ',NULL as t1_row' + cast(col1 AS varchar(3))
FROM MyTable
FOR XML PATH('')) ,1,1,'') AS Txt
)
DECLARE #stmt VARCHAR(MAX) = 'SELECT Col1, Col2, Col3, ' + #colList + ' FROM MyTable'
EXEC (#stmt)
I was able to achieve the result using dynamic SQL.
The Script looks something like this:
DECLARE #STRSQL NVARCHAR(MAX) = 'WITH a AS (SELECT ';
DECLARE #Kst nvarchar(6);
DECLARE #Markt NVARCHAR(30);
DECLARE #SCHEMA_NAME VARCHAR(50) = 'XTRADE';
DECLARE C1 CURSOR FOR
SELECT NUMMER, BEZEICHNUNG
from XTRADE.KUNDE
where NUMMER > 99 and NUMMER not in (194, 196, 198)
and (DATUM_SCHLIESSUNG > GETDATE() or DATUM_SCHLIESSUNG is null)
order by BEZEICHNUNG
OPEN C1
PRINT #Kst + ' ' + #Markt
FETCH NEXT
FROM C1 into #Kst, #Markt
while ##FETCH_STATUS = 0
BEGIN
SET #STRSQL = #STRSQL + 'null as [' + #Markt + '], '
FETCH NEXT
FROM C1 into #Kst, #Markt
END
CLOSE C1
DEALLOCATE C1;
SET #STRSQL = left(#STRSQL, len(#Strsql) - 1) + ')'
DECLARE #Statement nvarchar(max) = ', b as (select 1 as Col1, 1 as Col2, 5 as Col3 union all select 2,2,12 union all select 3, 3, 42)';
DECLARE #Exec nvarchar(max) = #STRSQL + #Statement + 'select * from b cross join a';
print #Exec;
exec sp_executesql #Exec

SQL Server : get column name of a table using condition

I have a sample table here - I want to get all the columns that have the value of 1 only. Is it possible?
Its absolutely possible but the process is lengthy, I am using loop to check each column's
data exists by retrieving column name from sys.columns. Please try this if it helps you in any term:
Here I am checking each column for value 1 only
CREATE TABLE testing(val1 INT, val2 INT, val3 INT)
INSERT INTO testing VALUES
(1, 0, 1),(1, 0, 1),(1, 1, 1)
Table: testing
val1 val2 val3
1 0 1
1 0 1
1 1 1
DECLARE #sql NVARCHAR(500), #list VARCHAR(500)
DECLARE #num INT=1, #col_name VARCHAR(100) = NULL, #cnt INT
WHILE(#num<=3)
BEGIN
SELECT #col_name = name FROM sys.columns
WHERE object_id = OBJECT_ID('testing') and column_id = #num
SET #cnt = 0
SET #sql = '
IF NOT EXISTS(SELECT 1 FROM testing WHERE ' + #col_name + ' = 0) SET #cnt = 1'
EXEC sp_executesql #sql, N'#cnt INT OUT', #cnt OUT
IF #cnt = 1
SET #list = COALESCE(#list + ',', '') + #col_name
SET #num = #num+1
END
SET #sql = '
SELECT ' + #list + ' FROM testing'
EXEC(#sql)
OUTPUT:
val1 val3
1 1
1 1
1 1

Find columns that contain only zeros

I'm working with SQL Server 2008. I have a list of column names on a table and I'd like to know how to use SQL to return the names of those columns which contain nothing but zero or NULL values.
declare #T table
(
Col1 int,
Col2 int,
Col3 int,
Col4 int
)
insert into #T values
(1, 0 , null, null),
(0, null, 0 , 1)
select U.ColName
from
(
select count(nullif(Col1, 0)) as Col1,
count(nullif(Col2, 0)) as Col2,
count(nullif(Col3, 0)) as Col3,
count(nullif(Col4, 0)) as Col4
from #T
) as T
unpivot
(C for ColName in (Col1, Col2, Col3, Col4)) as U
where U.C = 0
Result:
ColName
----------
Col2
Col3
The idea behind this is to count the non null values and only keep those with a count of 0.
COUNT will only count non null values.
NULLIF(ColX, 0) will make all 0 into null.
The inner query returns one row with four columns. UNPIVOT will turn it around so you have two columns and four rows.
Finally where U.C = 0 makes sure that you only get the columns that has no values other than null or 0.
Here is a brute force way, since you know all the column names.
CREATE TABLE dbo.splunge
(
a INT,
b INT,
c INT,
d INT
);
INSERT dbo.splunge VALUES (0,0,1,-1), (0,NULL,0,0), (0,0,0,NULL);
SELECT
cols = STUFF(
CASE WHEN MIN(COALESCE(a,0)) = MAX(COALESCE(a,0)) THEN ',a' ELSE '' END
+ CASE WHEN MIN(COALESCE(b,0)) = MAX(COALESCE(b,0)) THEN ',b' ELSE '' END
+ CASE WHEN MIN(COALESCE(c,0)) = MAX(COALESCE(c,0)) THEN ',c' ELSE '' END
+ CASE WHEN MIN(COALESCE(d,0)) = MAX(COALESCE(d,0)) THEN ',d' ELSE '' END,
1, 1, '')
FROM dbo.splunge;
-- result:
-- a,b
GO
DROP TABLE dbo.splunge;
You could probably generate much of this script instead of doing it manually, assuming you know the naming scheme or data type of the columns you want (or just by leaving off the where clause entirely and removing the columns you don't want manually).
SELECT CHAR(13) + CHAR(10) + ' + CASE WHEN MIN(COALESCE(' + name + ',0)) = '
+ 'MAX(COALESCE(' + name + ',0)) THEN '',' + name + ''' ELSE '''' END'
FROM sys.columns
WHERE [object_id] = OBJECT_ID('dbo.splunge')
-- AND system_type_id = 56
-- AND name LIKE '%some pattern%'
;
The output will look like the middle of the first query, so you can copy & paste and then remove the first + and add the surrounding STUFF and query...
Here's a way that works for any table:
declare #tableName nvarchar(max) = N'myTable'
declare #columnName nvarchar(max)
create table #zeros (column_name varchar(max))
declare c cursor local forward_only read_only for
select column_name
from information_schema.COLUMNS WHERE table_name = #tableName
open c
fetch next from c into #columnName
while ##FETCH_STATUS = 0
begin
declare #retval int
declare #sql nvarchar(max) =
N'set #retval = (select count(*) from ' + #tableName + N' where coalesce(' + #columnName + N', 0) <> 0)'
exec sp_executesql #sql, N'#retval int out', #retval=#retval out
select #retval
if #retval = 0
begin
insert into #zeros (column_name) values (#columnName)
end
fetch next from c into #columnName
end
close c
deallocate c
select * from #zeros
drop table #zeros

Cross Join 'n' times a table

It is possible to write a generic function/procedure/select/somethingElse to cross-join a table against himself 'n' times? (yes, 'n' is a given parameter : )
How would you do it?
Example
Having this table:
Value
-------
1
2
3
cross join it 2 times, would return:
Value | Value
------------------
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
Using dynamic SQL, SQL Server 2005+ (#table_name and #numCrossJoins are stored procedure parameters):
DECLARE #upperLimit INT
SET #upperLimit = 1
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SELECT * FROM '+ #table_name +' '
BEGIN
WHILE (upperLimit <= #numCrossJoins)
BEGIN
SET #SQL = #SQL + 'CROSS JOIN '+ QUOTENAME(#table_name) +' '
SET #upperLimit = #upperLimit + 1
END
EXEC sp_executesql #SQL
END
You can generate dynamic sql to output as many cross joins as you need:
create table #t (value int)
insert into #t values (1)
insert into #t values (2)
insert into #t values (3)
declare #n int
set #n = 4
declare #sql varchar(max)
set #sql = 'SELECT * FROM #t t'
declare #i int
set #i = 0
while (#i <= #n)
begin
set #sql = #sql + ' cross join #t t' + CAST(#i as varchar)
set #i = #i + 1
end
print #sql
execute(#sql)
drop table #t
Try this:
SET #SQL = 'SELECT * FROM ' + replicate('[' + #table_name + '],', #N);
set #SQL = LEFT(LEN(#SQL) - 1);
EXEC sp_executesql #SQL;
If you need to come up with all possible permutations, here is an example:
All Permutations For A String

Cross Tab Query Required SQL 2000

I have already googled for this
I have a Table with following structure in SQL 2000
ID ContactName Designation
1 A CEO
2 B ABC
3 C DEF
4 D GHI
I need the Output as follows
ContactName1 Contactname2 ContactName3 ContactName4
A CEO B ABC C DEF D GHI
Any Suggestions ?
It occurs to me that a lot of the examples are for cross tab queries involving aggregation, which yours does not appear to need. While I do not necessarily condone Dynamic SQL, the below should give you the results you want.
Create table #Contacts (id int)
Declare #ContactTypes int
Declare #CAD varchar(100)
Declare #I int
Declare #sql nvarchar(4000)
Set #i = 1
Select #ContactTypes =
Sum(sub.Types)
from ( Select Count(1) as Types from contacts
group by ContactName, Designation) as sub
Print #ContactTypes
While #i <= #ContactTypes
Begin
set #sql = 'alter table #Contacts Add ContactName' +
Cast(#I as varchar(10)) + ' varchar(100)'
exec sp_executesql #sql
Set #I = #i + 1
End
Insert into #Contacts (id) values (1)
Set #i = 1
Declare crsPivot cursor
for Select ContactName + ' ' + Designation
from contacts
open crsPivot
Fetch next from crsPivot into #CAD
While (##Fetch_Status = 0)
Begin
Set #sql = 'Update #Contacts set ContactName'
+ Cast(#I as varchar(10)) +' = ' + quotename(#CAD,'''')
exec sp_executesql #sql
Set #i = #i + 1
Fetch next from crsPivot into #CAD
End
close crsPivot
Deallocate crsPivot
select * From #Contacts
You need to use a PIVOTED Table
This should work with 2000 also - wg. without PIVOT
http://www.sqlteam.com/item.asp?ItemID=2955
Yet another SQL Cross Tab proc http://johnmacintyre.ca/codespct.asp