get the rows of a table in 1 row - sql

I have first table (the count of rows are varibale) and I want to create the second table. what is the efficient way?

First you have to bring your data to a more 'friendly' format:
;with
data as
(
-- replace this with your select
select * from
(
VALUES ('1', 'a', 'b'),
('2', 'c', 'd'),
('3', 'e', 'f')
) as data(aa,bb,cc)
--------------------------------
),
dataAsXml as
(
select CAST(STUFF((SELECT '<i>' + d.[aa] + '</i><i>' + d.[bb] + '</i><i>' + d.[cc] + '</i>' FROM data d FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'),1,0,'') as XML) as data
),
dataAsList as
(
select x.i.value('for $i in . return count(../*[. << $i]) + 1', 'int') as 'Ord',
x.i.value('.', 'NVARCHAR(100)') AS 'Value'
from dataAsXml
CROSS APPLY [data].nodes('//i') x(i)
),
normalized AS
(
select
case (Ord - 1) % 3 + 1
when 1 then 'aa'
when 2 then 'bb'
when 3 then 'cc'
end + cast((Ord - 1) / 3 + 1 as varchar(10)) as columnName, --fix here
value
from dataAsList
)
select * from normalized
In the query above you can plug in your data in data CTE to see the result.
The output will have two columns one that stores your column names and one with values.
SQL Fiddle
From here you have to use a dynamic query where you pivot the obtained table for columnName in the list of all the column names. I won't describe this process because it has been done many times. Take a look at this answer:
Convert Rows to columns using 'Pivot' in SQL Server
Note:
I didn't tested the performance of this method with large sets of data but
from some points of view it's efficient.

Try this one. I pivoted each of the columns then join them together in one row.
SELECT aa1,bb1,cc1,aa2,bb2,cc2,aa3,bb3,cc3 FROM
(SELECT 1 id,[2]aa1,[3]aa2,[4]aa3 FROM(SELECT aa FROM tablea) AS A
PIVOT(SUM(aa) FOR aa in([2],[3],[4])) AS pvt) A
INNER JOIN
(SELECT 1 id,[400]bb1,[200]bb2,[500]bb3 FROM(SELECT bb FROM tablea) AS A
PIVOT(SUM(bb) FOR bb in([400],[200],[500])) AS pvt) B ON A.id=B.id
INNER JOIN
(SELECT 1 id,[20]cc1,[25]cc2,[20]cc3 FROM(SELECT cc FROM tablea) AS A
PIVOT(MIN(cc) FOR cc in([20],[25])) AS pvt) C ON B.id=C.id

Related

Combining rows and in SQL [duplicate]

This question already has answers here:
How to use GROUP BY to concatenate strings in SQL Server?
(22 answers)
Closed 11 days ago.
My data is as follows:
**Name** **DOB** **B1** **C1**
cust A 01/06/99 ba1 ca1
cust A 01/06/99 ba2 ca2
I need to combine the rows for columns b1 and c1 within one row with the result looking like:
**Name** **DOB** **B1** **C1**
cust A 01/06/99 ba1, ba2 ca1, ca2
I'm tried different ways of concatenating the rows together, but it hasn't seemed to work. There can also be more than 2 rows for a single customer or only 1. Any help is appreciated.
If 2017+ you can use string_agg() in a simple aggregation query.
Select Name
,DOB
,B1 = string_agg(B1,', ')
,c1 = string_agg(c1,', ')
From YourTable
Group By Name,DOB
If you do not have SQL Server 2017, you can use stuff for XML to get a comma separated value of your table.
Like this :
; with mytable as (
select 'Cust A' as cust, '01/06/99' as dob, 'ba1' as b1, 'ca1' as c1
union all select
'Cust A' as cust, '01/06/99' as dob, 'ba2' as b1, 'ca2' as c1
)
select distinct
cust
,dob
,
STUFF(
(
SELECT ',' + b1
FROM mytable A1
WHERE A1.cust = A2.cust FOR XML PATH('')
), 1, 1, '') AS b1
,
STUFF(
(
SELECT ',' + c1
FROM mytable A1
WHERE A1.cust = A2.cust FOR XML PATH('')
), 1, 1, '') AS c1
FROM mytable A2;
Results :

SQL determine if more than one column on a given row has the same value

I have a SQL Server table that includes 9 columns used to indicated what things to include or exclude in part of the UI. Each column can have the value 'A', 'X', or blank. Each row should have at most 1 'A' in any of the columns.
Due to an error many columns have multiple 'A' values. How can I write a query that returns every row that breaks this constraint?
All I have is something like:
SELECT PrimaryKey
FROM Criteria C
WHERE (C.First = 'A' AND C.Second = 'A')
OR (C.First = 'A' AND C.Third = 'A')
OR (C.First = 'A' AND C.Fourth = 'A')
...
OR (C.Eighth = 'A' AND C.Ninth = 'A')
Is there any cleaner or more elegant way to write this code?
You can use APPLY:
SELECT C.*
FROM Criteria C CROSS APPLY
(SELECT COUNT(*) as num_a_s
FROM (VALUES (First), (Second), . . . -- list all the columns here
) V(x)
WHERE v.x = 'A'
) v
WHERE v.num_a_s >= 2;
Note: Something is probably wrong with your data model if you are storing these values in columns rather than in separate rows.
Here is one way to do this
create table dbo.t1(x int, col1 varchar(10), col2 varchar(10))
insert into dbo.t1 values(1,'A','A')
insert into dbo.t1 values(2,'A','')
insert into dbo.t1 values(3,'','')
insert into dbo.t1 values(4,'','X')
select x
,count(case when n='A' then 1 end) as cnt
from dbo.t1
cross apply(values ('col1',col1)
,('col2',col2)
---repeat this for columns up to col9...
)x(m,n)
group by x
having count(case when n='A' then 1 end)>1
try the following:
select
* from YourTable
where REPLACE(TRIM('X' FROM col1),'A',1)
+ REPLACE(TRIM('X' FROM col2),'A',1)
+ REPLACE(TRIM('X' FROM col3),'A',1)
+ REPLACE(TRIM('X' FROM col4),'A',1)
+ REPLACE(TRIM('X' FROM col5),'A',1)
+ REPLACE(TRIM('X' FROM col6),'A',1)
+ REPLACE(TRIM('X' FROM col7),'A',1)
+ REPLACE(TRIM('X' FROM col8),'A',1)
+ REPLACE(TRIM('X' FROM col9),'A',1)> 1
I have used TRIM also which requires SQL Server 2017 or above.
or the following:
select *
from YourTable
where replace(replace(concat(col1,col2,col3,col4,col5,col6,col7,col8,col9), 'X', ''), 'A', 1) > 1
Please see the demo here.
SELECT *
FROM MyTable
WHERE LEN(CONCATENATE(C.First, C.... , C.Eighth)) - LEN(REPLACE(CONCATENATE(C.First, C.... , C.Eighth), 'A', '')) = 1
As an example

Joining a list of values with table rows in SQL

Suppose I have a list of values, such as 1, 2, 3, 4, 5 and a table where some of those values exist in some column. Here is an example:
id name
1 Alice
3 Cindy
5 Elmore
6 Felix
I want to create a SELECT statement that will include all of the values from my list as well as the information from those rows that match the values, i.e., perform a LEFT OUTER JOIN between my list and the table, so the result would be like follows:
id name
1 Alice
2 (null)
3 Cindy
4 (null)
5 Elmore
How do I do that without creating a temp table or using multiple UNION operators?
If in Microsoft SQL Server 2008 or later, then you can use Table Value Constructor
Select v.valueId, m.name
From (values (1), (2), (3), (4), (5)) v(valueId)
left Join otherTable m
on m.id = v.valueId
Postgres also has this construction VALUES Lists:
SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter)
Also note the possible Common Table Expression syntax which can be handy to make joins:
WITH my_values(num, str) AS (
VALUES (1, 'one'), (2, 'two'), (3, 'three')
)
SELECT num, txt FROM my_values
With Oracle it's possible, though heavier From ASK TOM:
with id_list as (
select 10 id from dual union all
select 20 id from dual union all
select 25 id from dual union all
select 70 id from dual union all
select 90 id from dual
)
select * from id_list;
the following solution for oracle is adopted from this source. the basic idea is to exploit oracle's hierarchical queries. you have to specify a maximum length of the list (100 in the sample query below).
select d.lstid
, t.name
from (
select substr(
csv
, instr(csv,',',1,lev) + 1
, instr(csv,',',1,lev+1 )-instr(csv,',',1,lev)-1
) lstid
from (select ','||'1,2,3,4,5'||',' csv from dual)
, (select level lev from dual connect by level <= 100)
where lev <= length(csv)-length(replace(csv,','))-1
) d
left join test t on ( d.lstid = t.id )
;
check out this sql fiddle to see it work.
Bit late on this, but for Oracle you could do something like this to get a table of values:
SELECT rownum + 5 /*start*/ - 1 as myval
FROM dual
CONNECT BY LEVEL <= 100 /*end*/ - 5 /*start*/ + 1
... And then join that to your table:
SELECT *
FROM
(SELECT rownum + 1 /*start*/ - 1 myval
FROM dual
CONNECT BY LEVEL <= 5 /*end*/ - 1 /*start*/ + 1) mypseudotable
left outer join myothertable
on mypseudotable.myval = myothertable.correspondingval
Assuming myTable is the name of your table, following code should work.
;with x as
(
select top (select max(id) from [myTable]) number from [master]..spt_values
),
y as
(select row_number() over (order by x.number) as id
from x)
select y.id, t.name
from y left join myTable as t
on y.id = t.id;
Caution: This is SQL Server implementation.
fiddle
For getting sequential numbers as required for part of output (This method eliminates values to type for n numbers):
declare #site as int
set #site = 1
while #site<=200
begin
insert into ##table
values (#site)
set #site=#site+1
end
Final output[post above step]:
select * from ##table
select v.id,m.name from ##table as v
left outer join [source_table] m
on m.id=v.id
Suppose your table that has values 1,2,3,4,5 is named list_of_values, and suppose the table that contain some values but has the name column as some_values, you can do:
SELECT B.id,A.name
FROM [list_of_values] AS B
LEFT JOIN [some_values] AS A
ON B.ID = A.ID

Reshape SQL date

following problem
I am using the command to join Names with project numbers
SELECT DDR_namen.vorname_nachname, DDR_Erfinder_final.pubnr
FROM DDR_namen
RIGHT JOIN DDR_Erfinder_final
ON DDR_namen.vorname_nachname=DDR_Erfinder_final.vorname_nachname
which gives me someting like this (small example)
vorname_nachname Pubnr (ID)
A. Heinrich 100
B. Müller 100
B. Müller 101
B. Müller 105
C. Krüger 120
C. Krüger 100
Now I want to reshape the data, so that the rows are unique and the ID are combined into a new collumn. Like this.
vorname_nachname Pubnr (ID)
A. Heinrich 100
B. Müller 100;101;105
C. Krüger 120;100
Anybody and ideas?
Try grouping like
select vorname_nachname,
(
SELECT STUFF((SELECT ';' + CAST(Pubnr AS VARCHAR(MAX))
FROM TestTable
WHERE vorname_nachname = t1.vorname_nachname
FOR XML PATH('') ), 1, 1, '')
) AS Pubnr
from TestTable t1
group by vorname_nachname
SQL Fiddle Demo : http://sqlfiddle.com/#!3/d0916/1
UPDATE : For JOIN you can use CTE like below :
;WITH CTE AS
(
SELECT DDR_namen.vorname_nachname, DDR_Erfinder_final.pubnr
FROM DDR_namen
RIGHT JOIN DDR_Erfinder_final
ON DDR_namen.vorname_nachname=DDR_Erfinder_final.vorname_nachname
)
select vorname_nachname,
(
SELECT STUFF((SELECT ';' + CAST(Pubnr AS VARCHAR(MAX))
FROM CTE
WHERE vorname_nachname = t1.vorname_nachname
FOR XML PATH('') ), 1, 1, '')
) AS Pubnr
from CTE t1
group by vorname_nachname

SQL 2005 Merge / concatenate multiple rows to one column

We have a bit of a SQL quandry. Say I have a results that look like this...
61E77D90-D53D-4E2E-A09E-9D6F012EB59C | A
61E77D90-D53D-4E2E-A09E-9D6F012EB59C | B
61E77D90-D53D-4E2E-A09E-9D6F012EB59C | C
61E77D90-D53D-4E2E-A09E-9D6F012EB59C | D
7ce953ca-a55b-4c55-a52c-9d6f012ea903 | E
7ce953ca-a55b-4c55-a52c-9d6f012ea903 | F
is there a way I can group these results within SQL to return as
61E77D90-D53D-4E2E-A09E-9D6F012EB59C | A B C D
7ce953ca-a55b-4c55-a52c-9d6f012ea903 | E F
Any ideas people?
Many thanks
Dave
try this:
set nocount on;
declare #t table (id char(36), x char(1))
insert into #t (id, x)
select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'A' union
select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'B' union
select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'C' union
select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'D' union
select '7ce953ca-a55b-4c55-a52c-9d6f012ea903' , 'E' union
select '7ce953ca-a55b-4c55-a52c-9d6f012ea903' , 'F'
set nocount off
SELECT p1.id,
stuff(
(SELECT
' ' + x
FROM #t p2
WHERE p2.id=p1.id
ORDER BY id, x
FOR XML PATH('')
)
,1,1, ''
) AS YourValues
FROM #t p1
GROUP BY id
OUTPUT:
id YourValues
------------------------------------ --------------
61E77D90-D53D-4E2E-A09E-9D6F012EB59C A B C D
7ce953ca-a55b-4c55-a52c-9d6f012ea903 E F
(2 row(s) affected)
EDIT
based on OP's comment about this needing to run for an existing query, try this:
;WITH YourBugQuery AS
(
--replace this with your own query
select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' AS ColID , 'A' AS ColX
union select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'B'
union select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'C'
union select '61E77D90-D53D-4E2E-A09E-9D6F012EB59C' , 'D'
union select '7ce953ca-a55b-4c55-a52c-9d6f012ea903' , 'E'
union select '7ce953ca-a55b-4c55-a52c-9d6f012ea903' , 'F'
)
SELECT p1.ColID,
stuff(
(SELECT
' ' + ColX
FROM YourBugQuery p2
WHERE p2.ColID=p1.ColID
ORDER BY ColID, ColX
FOR XML PATH('')
)
,1,1, ''
) AS YourValues
FROM YourBugQuery p1
GROUP BY ColID
this has the same results set as displayed above.
I prefer to define a custom user-defined aggregate. Here's an example of a UDA which will accomplish something very close to what you're asking.
Why use a user-defined aggregate instead of a nested SELECT? It's all about performance, and what you are willing to put up with. For a small amount of elements, you can most certainly get away with a nested SELECT, but for large "n", you'll notice that the query plan essentially runs the nested SELECT once for every row in the output list. This can be the kiss of death if you're talking about a large number of rows. With a UDA, it's possible to aggregate these values in a single pass.
The tradeoff, of course, is that the UDA requires you to use the CLR to deploy it, and that's something not a lot of people do often. In Oracle, this particular situation is a bit nicer as you can use PL/SQL directly to create your user-defined aggregate, but I digress...
Another way of doing it is to use the FOR XML PATH option
SELECT
[ID],
(
SELECT
[Value] + ' '
FROM
[YourTable] [YourTable2]
WHERE
[YourTable2].[ID] = [YourTable].[ID]
ORDER BY
[Value]
FOR XML PATH('')
) [Values]
FROM
[YourTable]
GROUP BY
[YourTable].[ID]