If I have a table containing:
game powerup used
memory 1 12
memory 2 10
bejeweled 2 88
bejeweled 3 54
...where the (number of) different possible values of powerup are unknown, is there a way to SELECT this as something like
game 1_used 2_used 3_used
memory 12 10
bejeweled 88 54
I'm interested in pure SQL or PostgreSQL answers.
Note: This is an academical question, I don't have a use case at the moment. Answer it for your enjoyment or for the good of the site, not my personal benefit :)
Try this one:
SELECT game
,MAX(CASE WHEN powerup = 1 THEN used ELSE NULL END) AS used1
,MAX(CASE WHEN powerup = 2 THEN used ELSE NULL END) AS used2
,MAX(CASE WHEN powerup = 3 THEN used ELSE NULL END) AS used3
FROM Table1
GROUP BY game;
Output:
╔═══════════╦════════╦═══════╦════════╗
║ GAME ║ USED1 ║ USED2 ║ USED3 ║
╠═══════════╬════════╬═══════╬════════╣
║ bejeweled ║ (null) ║ 88 ║ 54 ║
║ memory ║ 12 ║ 10 ║ (null) ║
╚═══════════╩════════╩═══════╩════════╝
See this SQLFiddle
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE meuk
( game varchar
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
, PRIMARY KEY (game,powerup)
);
INSERT INTO meuk(game, powerup, used) VALUES
( 'memory' , 1,12)
,( 'memory' , 2,10)
,( 'bejeweled' , 2,88)
,( 'bejeweled' , 3,54)
;
-- using a self-join as
-- a poor man's pivot.
WITH dom AS (
SELECT DISTINCT game AS game
FROM meuk
)
SELECT m0.game AS game
, m1.used AS used_1
, m2.used AS used_2
, m3.used AS used_3
FROM dom m0
LEFT JOIN meuk m1 ON m1.game = m0.game AND m1.powerup = 1
LEFT JOIN meuk m2 ON m2.game = m0.game AND m2.powerup = 2
LEFT JOIN meuk m3 ON m3.game = m0.game AND m3.powerup = 3
;
RESULTS:
game | used_1 | used_2 | used_3
-----------+--------+--------+--------
bejeweled | | 88 | 54
memory | 12 | 10 |
(2 rows)
CREATE TABLE test_1
( game varchar(10)
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
);
INSERT INTO test_1(game, powerup, used)
SELECT 'memory' , 1,12 UNION ALL
SELECT 'memory' , 2,10 UNION ALL
SELECT 'bejeweled' , 2,88 UNION ALL
SELECT 'bejeweled' , 3,54
select * from test_1
DECLARE #COLUMNS varchar(max)
SELECT #COLUMNS = COALESCE(#COLUMNS+'],[' ,'') + CAST(powerup as varchar)
FROM test_1
GROUP BY powerup
SET #COLUMNS = '[' + #COLUMNS + ']'
EXECUTE ('select * from test_1
pivot (SUM(used) for powerup IN ('+ #COLUMNS +')) as used_t')
Related
I have 3 columns in a table, in which 2 columns have string separated by a '|' pipe. Both these columns values depend on each other.
For an example: I have data in the table like this :
ID product quantity
1 A|B|C 1|2|3
2 X|Y|Z 7|8|9
I would like to change it to something like this :
ID product quantity
1 A 1
1 B 2
1 C 3
2 X 7
2 Y 8
2 Z 9
As i am working with SSMS, i don't have any other choice except SQL. I try to use cross apply but i am not getting right result. For 1 row i receive 9 rows instead of getting 3.
could anyone suggest me which method should i use?
Thank you in advance!!
JACK
This is rather tricky, because you need for the values to match up. The following takes a recursive CTE approach:
with cte as (
select id,
left(product, charindex('|', product + '|') - 1) as product,
left(quantity, charindex('|', quantity + '|') - 1) as quantity,
substring(product, charindex('|', product + '|') + 1, 1000) as products,
substring(quantity, charindex('|', quantity + '|') + 1, 1000) as quantities
from t
union all
select id,
left(products, charindex('|', products + '|') - 1) as product,
left(quantities, charindex('|', quantities + '|') - 1) as quantity,
substring(products, charindex('|', products + '|') + 1, 1000) as products,
substring(quantities, charindex('|', quantities + '|') + 1, 1000) as quantities
from cte
where products <> '' and quantities <> ''
)
select id, product, quantity
from cte;
Here is a little Rextester.
Test Data
CREATE TABLE #t (ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #t VALUES
(1 ,'A|B|C' , '1|2|3'),
(2 ,'X|Y|Z' , '7|8|9');
Query
WITH Products AS (
SELECT ID
, Product_Split.a.value('.', 'VARCHAR(100)') Products
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(product, '|', '</X><X>')
+ '</X>' AS XML) AS Product_Data
FROM #t
) AS t
CROSS APPLY Product_Data.nodes ('/X') AS Product_Split(a)
),
Quantities AS (
SELECT ID
, Quantity_Split.a.value('.', 'VARCHAR(100)') Quantity
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) rn
FROM (
SELECT ID
,Cast ('<X>'
+ Replace(quantity, '|', '</X><X>')
+ '</X>' AS XML) AS Quantity_Data
FROM #t
) AS t
CROSS APPLY Quantity_Data.nodes ('/X') AS Quantity_Split(a)
)
SELECT t.ID
, P.Products
, Q.Quantity
FROM #t t
LEFT JOIN Products P ON t.ID = p.ID
LEFT JOIN Quantities Q ON Q.ID = t.ID
AND Q.rn = p.rn
Result Set
╔════╦══════════╦══════════╗
║ ID ║ Products ║ Quantity ║
╠════╬══════════╬══════════╣
║ 1 ║ A ║ 1 ║
║ 1 ║ B ║ 2 ║
║ 1 ║ C ║ 3 ║
║ 2 ║ X ║ 7 ║
║ 2 ║ Y ║ 8 ║
║ 2 ║ Z ║ 9 ║
╚════╩══════════╩══════════╝
splitting strings is easy, there are tons of examples. The tricky part here is to connect the fragments via their position. My suggestion uses XMLs abilities to target an element by its position:
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9');
--This is the query
WITH CastedToXML AS
(
SELECT *
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
the result
+----+------+-------------------+--------------------+
| ID | Nmbr | ProductAtPosition | QuantityAtPosition |
+----+------+-------------------+--------------------+
| 1 | 1 | A | 1 |
+----+------+-------------------+--------------------+
| 1 | 2 | B | 2 |
+----+------+-------------------+--------------------+
| 1 | 3 | C | 3 |
+----+------+-------------------+--------------------+
| 2 | 1 | X | 7 |
+----+------+-------------------+--------------------+
| 2 | 2 | Y | 8 |
+----+------+-------------------+--------------------+
| 2 | 3 | Z | 9 |
+----+------+-------------------+--------------------+
Some explanation:
the cast to xml transfers your A|B|C to
<x>A</x>
<x>B</x>
<x>C</x>
This list is joined with a number set created on the fly using the count of <x> as the TOP limit.
Now it is easy to pick the <x> out of your XML by the position.
Try it out!
UPDATE: Non-unique IDs
DECLARE #tbl TABLE(ID INT, product VARCHAR(100) , quantity VARCHAR(100) )
INSERT INTO #tbl VALUES
(1 ,'A|B|C' , '1|2|3')
,(2 ,'X|Y|Z' , '7|8|9')
,(3 ,'a|b|c' , '7|8|9')
,(2 ,'D|e|f' , '7|8|9')
;
--This is the query
WITH CastedToXML AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY ID) AS RowIndex
,CAST('<x>' + REPLACE(product,'|','</x><x>') + '</x>' AS XML) AS ProductXml
,CAST('<x>' + REPLACE(quantity,'|','</x><x>') + '</x>' AS XML) AS QuantityXml
FROM #tbl
)
SELECT *
,ProductXml.value('/x[sql:column("Nmbr")][1]','nvarchar(10)') AS ProductAtPosition
,QuantityXml.value('/x[sql:column("Nmbr")][1]','int') AS QuantityAtPosition
FROM CastedToXML
--Create a set of running numbers (spt_values is just a pre-filled table with many rows)
CROSS APPLY (SELECT TOP(CastedToXML.ProductXml.value('count(/x)','int'))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM master..spt_values) AS Tally(Nmbr);
I have a table having data like this
id | Name | Age
---------------
1 | a,b | 10
I want to split that data and merge with the same table like this
id | Name | Age
---------------
1 | a | 10
1 | b | 10
I have a split function.
Test Data
DECLARE #TABLE TABLE(id INT, Name VARCHAR(10), Age INT)
INSERT INTO #TABLE VALUES
(1 ,'a',10), (1 ,'b',10),
(2 ,'d',20), (2 ,'f',20)
Query
SELECT t.ID
,STUFF(( SELECT ',' + Name
FROM #TABLE
WHERE ID = t.ID AND Age = t.Age
FOR XML PATH(''),TYPE)
.value('.','NVARCHAR(MAX)'),1,1,'') AS Name
,t.Age
FROM #TABLE t
GROUP BY t.id, t.Age
Result
╔════╦══════╦═════╗
║ ID ║ Name ║ Age ║
╠════╬══════╬═════╣
║ 1 ║ a,b ║ 10 ║
║ 2 ║ d,f ║ 20 ║
╚════╩══════╩═════╝
It seems to me that OP has the data concatenated and needs to split it. Here's one way to do it (fiddle: http://sqlfiddle.com/#!3/6b2a1/15)
WITH cteSplitCol
AS
(
SELECT id,
Name,
Age,
SUBSTRING(Name, 1, 1) AS Character,
1 AS CharPos,
MAX(LEN(Name)) OVER () AS MaxLen
FROM Table1
UNION ALL
SELECT id,
Name,
Age,
SUBSTRING(Name, CharPos + 1, 1) AS Character,
CharPos + 1,
MaxLen
FROM cteSplitCol
WHERE CharPos + 1 <= MaxLen
)
SELECT id,
Character AS Name,
Age
FROM cteSplitCol
WHERE Character <> ',';
First of all, I am new to SQL. Here is the sample (for both table1 and table2, I have created a SNO as primary key and it's also identity column)
Table1:
PID PNAME PartID
--- ----- ------
0 Length 1
1 Breadth 1
2 Height 1
0 Area 2
1 Volume 2
Table2:
SampleID PID Pvalue PartID ModifiedDate Operator
-------- --- ------ ------ ------------ --------
0 0 10 1 10-Mar-14 Test
0 1 10 1 10-Mar-14 Test
0 2 Fail 1 10-Mar-14 Test
1 0 20 1 12-Mar-14 Test
1 1 Fail 1 12-Mar-14 Test
1 2 Fail 1 12-Mar-14 Test
0 0 10 2 13-Mar-14 Test1
0 1 10 2 13-Mar-14 Test1
Depending upon the PartID, I must get the following results
PARTID: 1
PNAME 0 1
------------ --------- ---------
Length 10 20
Breadth 10 Fail
Height Fail Fail
ModifiedDate 10-Mar-14 12-Mar-14
Operator Test Test
PARTID: 2
PNAME 0
------------ ---------
Area 10
Volume 10
ModifiedDate 13-Mar-14
Operator Test1
How to achieve the desired output as mentioned above in SQL Server 2008?
You can use PIVOT to get the result but you will also need to unpivot the ModifiedDate and Operator columns so you can display them in a single column with the PName. Your final result will need a dynamic solution but it would be much easier to write this static first, then convert to dynamic sql.
The basic syntax will be:
select pname, [0], [1]
from
(
select t2.sampleid, pname = c.col, c.value
from table1 t1
inner join table2 t2
on t1.partid = t2.partid
and t1.pid = t2.pid
cross apply
(
select Pname, pvalue union all
select 'ModifiedDate', convert(varchar(10), ModifiedDate, 120) union all
select 'Operator', Operator
) c (col, value)
where t1.partid = 1
) d
pivot
(
max(value)
for sampleid in ([0], [1])
) p;
See SQL Fiddle with Demo. You'll see that I used CROSS APPLY to convert the 3 columns PName, ModifiedDate and Operator into a single column. This is necessary so you can easily get to the values for each SampleId. The above version is a static version meaning you are hard-coding the values for the final columns, but if you want to have this adjust based on the PartId, you will need to use dynamic SQL:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX),
#partid int,
#paramdef nvarchar(max)
set #partid = 1
set #paramdef = '#partid int'
select #cols = STUFF((SELECT ',' + QUOTENAME(sampleid)
from Table2
where partid = #partid
group by sampleid
order by sampleid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT pname,' + #cols + '
from
(
select t2.sampleid, pname = c.col, c.value
from table1 t1
inner join table2 t2
on t1.partid = t2.partid
and t1.pid = t2.pid
cross apply
(
select Pname, pvalue union all
select ''ModifiedDate'', convert(varchar(10), ModifiedDate, 120) union all
select ''Operator'', Operator
) c (col, value)
where t1.partid = #partid
) x
pivot
(
max(value)
for sampleid in (' + #cols + ')
) p '
exec sp_executesql #query, #paramdef, #partid = #partid;
See SQL Fiddle with Demo. Both give a result:
| PNAME | 0 | 1 |
|--------------|------------|------------|
| Breadth | 10 | Fail |
| Height | Fail | Fail |
| Length | 10 | 20 |
| ModifiedDate | 2014-03-10 | 2014-03-12 |
| Operator | Test | Test |
I'm having some problems replacing my ROLLUP NULL with a string value because my column data type is an Integer.
SELECT CASE
WHEN GROUPING(Column1) = 1 THEN 'Total'
ELSE Column1
END Column1, SUM(Column2) AS MySum
FROM MyTable
GROUP BY Column1 WITH ROLLUP;
I can put a numeric value in:
WHEN GROUPING(Column1) = 1 THEN '9999'
but I can't figure out how to convert to varchar if value is NULL and then replace with 'Total'.
Test Data
DECLARE #MyTable TABLE (Column1 INT,Column2 INT)
INSERT INTO #MyTable VALUES
(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)
SELECT CASE
WHEN GROUPING(Column1) = 1 THEN 'Total'
ELSE CAST(Column1 AS VARCHAR(10)) --<-- Cast as Varchar
END Column1
, SUM(Column2) AS MySum
FROM #MyTable
GROUP BY Column1
WITH ROLLUP;
Result Set
╔═════════╦═══════╗
║ Column1 ║ MySum ║
╠═════════╬═══════╣
║ 1 ║ 6 ║
║ 2 ║ 6 ║
║ 3 ║ 6 ║
║ Total ║ 18 ║
╚═════════╩═══════╝
Note
The reason you couldnt do what you were trying to do is because when you use a CASE statement in each case the returned datatype should be the same.
In above query I have just CAST the colum1 to varchar and it worked.
+-----+-----+------+----------+
| id | num | text | combo |
+-----+-----+------+----------+
| 126 | 1 | 12 | 12,58,94 |
| 126 | 2 | 58 | |
| 126 | 3 | 94 | |
| 130 | 1 | 28 | 28,45,64 |
| 130 | 2 | 45 | |
| 130 | 3 | 64 | |
+-----+-----+------+----------+
I have this table, where the column "combo" is currently null. I am trying to update it as such with data from "text" (from min value to max value in "num" and for each distinct id)
Any help formulating this query would be greatly appreciated, pardon my poor phrasing.
Test Data
CREATE TABLE #MyTable(id INT,num INT,text INT,combo VARCHAR(100))
GO
INSERT INTO #MyTable
VALUES
(126,1,12,NULL),
(126,2,58,NULL),
(126,3,94,NULL),
(130,1,28,NULL),
(130,2,45,NULL),
(130,3,64,NULL)
GO
Query
;With UpdateRecord
AS
(
SELECT * , rn = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [text])
FROM #MyTable
),
UpdateValues
AS
(
SELECT ID, Num, STUFF(List.Numbers, 1 ,2 , '') Vals
, rn2 = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY [text])
FROM #MyTable t CROSS APPLY
(
SELECT ', ' + CAST([text] AS VARCHAR) [text()]
FROM #MyTable
WHERE id = t.id
FOR XML PATH('')
)List(Numbers)
)
UPDATE UpdateRecord
SET combo = (SELECT TOP 1 Vals
FROM UpdateValues
WHERE id = UpdateRecord.id AND rn = UpdateRecord.rn
AND rn = 1)
Result
╔═════════╦═════╦══════╦════════════════╗
║ id ║ num ║ text ║ combo ║
╠═════════╬═════╬══════╬════════════════╣
║ 126 ║ 1 ║ 12 ║ 12, 58, 94 ║
║ 126 ║ 2 ║ 58 ║ NULL ║
║ 126 ║ 3 ║ 94 ║ NULL ║
║ 130 ║ 1 ║ 28 ║ 28, 45, 64 ║
║ 130 ║ 2 ║ 45 ║ NULL ║
║ 130 ║ 3 ║ 64 ║ NULL ║
╚═════════╩═════╩══════╩════════════════╝
You can use a recursive cte:
declare #SourceTable TABLE
(
id int, num int,textvalue varchar(255), combo varchar(255)
)
INSERT INTO #SourceTable(id, num, textvalue) VALUES
(126, 1, 12),(126, 2, 58),(126, 3, 94),(130, 1, 28),(130, 2, 45),(130, 3, 64)
Create a recursive cte that concatenates the values:
;with cte_ConcatenatedText(id, textvalue, num) AS
(
SELECT id, textvalue, num FROM #SourceTable WHERE num = 1
UNION ALL
SELECT st.id, cast(cte.textvalue + ',' + st.textvalue as varchar(255)), st.num
FROM #SourceTable st
INNER JOIN cte_ConcatenatedText cte ON cte.id = st.id and cte.num = st.num -1
)
After that you can update the source table:
UPDATE #SourceTable SET combo = cte.textvalue
FROM #SourceTable st
INNER JOIN
(SELECT id, textvalue,num FROM cte_ConcatenatedText) cte
ON cte.id = st.id and st.num = 1
INNER JOIN
(SELECT id, MAX(num) AS num FROM #SourceTable group by id) maxNum
on maxNum.id = cte.id and maxNum.num = cte.num
SELECT * FROM #SourceTable
Result:
id num textvalue combo
126 1 12 12,58,94
126 2 58 NULL
126 3 94 NULL
130 1 28 28,45,64
130 2 45 NULL
130 3 64 NULL