Can I pivot data and transform values into a check mark? - sql-server-2005

I've been looking for how to do this and I haven't been able to find an example
I have data that looks like this:
batchid wigitid
------- -------
538217 MT8
538217 MT6
538217 M89A
538218 MT8
538218 M89D
538218 M90
I want to flip the data so it looks like this:
BatchId MT8 MT6 M89A M89D M90
------- --- --- --- ---- ---
538217 * * *
538218 * * *
I can loop through the records via a cursor, however I believe that there might be a better way.

how about this.
Select *
From (
Select batchid, wigitid, '*' wigitname
From BATCHMAST
) As BMast
Pivot (MAX(wigitname) For WigitId In ([MT8],[MT6],[M89A],[M89D],[M90])) As Pvt
OR try below also if you dont' want null in result
Select batchid,
ISNULL([MT8],'') [MT8],
ISNULL([MT6],'')[MT6],
ISNULL([M89A],'')[M89A],
ISNULL([M89D],'')[M89D],
ISNULL([M90],'')[M90]
From (
Select batchid, wigitid, '*' wigitname
From BATCHMAST
) As BMast
Pivot (MAX(wigitname) For WigitId In ([MT8],[MT6],[M89A],[M89D],[M90])) As Pvt

Try the following code SQL Fiddle
SELECT
BatchID,
REPLACE(NULLIF(MT6,0), 1, '*') AS MT6,
REPLACE(NULLIF(MT8,0), 1, '*') AS MT8,
REPLACE(NULLIF(M89A,0), 1, '*') AS M89A,
REPLACE(NULLIF(M89D,0), 1, '*') AS M89D,
REPLACE(NULLIF(M90,0), 1, '*') AS M90
FROM
(
SELECT BatchID, WigitID FROM Information
) a
PIVOT
(
COUNT(WigitID) FOR WigitID IN (MT6, MT8, M89A, M89D, M90)
) b

Related

SQL get average of a list in sql select

We have this column in the table named "pricehistory"
1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17
that is an example data.
first is the timestamp than after ; is the price at this timestamp
But i want the average price from every timestamp in a select... is that possible?
I dont find any similiar examples somewhere and my tries to select doesnt work... i am not so good with sql
so i want average of all prices behind that ; and before ,
The , split the timestamp and prices
Some test data :
create table test ( id int not null, pricehistory text not null );
insert into test values ( 1, '1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17' );
insert into test values ( 2, '1634913731;42.42,1634916609;21.21' );
If your RDBMS has some splitting function
Then it's quite easy, just split and use AVG. Here is an example using PostgreSQL :
SELECT id, AVG(SUBSTRING(v, 12, 42)::decimal) AS average
FROM test
INNER JOIN LATERAL regexp_split_to_table(pricehistory, E',') t(v) ON TRUE
GROUP BY id;
Then you get:
id | average
----+----------------------
2 | 31.8150000000000000
1 | 238.5016666666666667
(2 rows)
Otherwise
You can use a CTE to split the values manually. This is a bit more involved. Here is an example using PostgreSQL again :
WITH RECURSIVE T AS (
SELECT id,
-- We get the last value ...
SUBSTRING(pricehistory, LENGTH(pricehistory) - STRPOS(REVERSE(pricehistory), ',') + 2) AS oneprice,
pricehistory AS remaining
FROM test
UNION ALL
-- ... as we get the other values from the recursive CTE.
SELECT id,
LEFT(remaining, STRPOS(remaining, ',') - 1),
SUBSTRING(remaining, STRPOS(remaining, ',') + 1)
FROM T
WHERE STRPOS(remaining, ',') > 0
)
SELECT id, AVG(SUBSTRING(oneprice, 12)::decimal) AS average
FROM T
GROUP BY id;
Then you get:
id | average
----+----------------------
2 | 31.8150000000000000
1 | 238.5016666666666667
(2 rows)
MySql >= 8.0
I used Recursive Common Table Expressions (cte) to split pricehistory string by ','. Then I split price from timestamp by ';', cast price as decimal(10,2) and group by id to get average price by id.
WITH RECURSIVE
cte AS (SELECT id,
SUBSTRING_INDEX(pricehistory, ',', 1) AS price,
CASE WHEN POSITION(',' IN pricehistory) > 0
THEN SUBSTR(pricehistory, POSITION(',' IN pricehistory) + 1)
ELSE NULL END AS rest
FROM t
UNION ALL
SELECT id,
SUBSTRING_INDEX(rest, ',', 1) AS price,
CASE WHEN POSITION(',' IN rest) > 0
THEN SUBSTR(rest, POSITION(',' IN rest) + 1)
ELSE NULL END AS rest
FROM cte
WHERE rest IS NOT NULL)
SELECT id, AVG(CAST(SUBSTR(price, POSITION(';' IN price) + 1) AS decimal(10,2))) AS price_average
FROM cte
GROUP BY id;
A similar way to do the same (using regular expressions functions):
WITH RECURSIVE
cte AS (SELECT Id, concat(pricehistory, ',') AS pricehistory FROM t),
unnest AS (SELECT id,
pricehistory,
1 AS i,
REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, 1) AS price
FROM cte
UNION ALL
SELECT id,
pricehistory,
i + 1,
REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, i + 1)
FROM unnest
WHERE REGEXP_SUBSTR(pricehistory, ';[0-9.]*,', 1, i + 1) IS NOT NULL)
SELECT id, AVG(CAST(SUBSTR(price, 2, LENGTH(price) - 2) AS decimal(10,2))) AS price_average
FROM unnest
GROUP BY id;
you don't write what DBMS you are using.
In MS SQL-SERVER you can write something like this.
Create a function to convert string to multiple rows, and then use that in the query.
CREATE or ALTER FUNCTION dbo.BreakStringIntoRows (#CommadelimitedString varchar(1000), #Separator VARCHAR(1))
RETURNS #Result TABLE (Column1 VARCHAR(max))
AS
BEGIN
DECLARE #IntLocation INT
WHILE (CHARINDEX(#Separator, #CommadelimitedString, 0) > 0)
BEGIN
SET #IntLocation = CHARINDEX(#Separator, #CommadelimitedString, 0)
INSERT INTO #Result (Column1)
--LTRIM and RTRIM to ensure blank spaces are removed
SELECT RTRIM(LTRIM(SUBSTRING(#CommadelimitedString, 0, #IntLocation)))
SET #CommadelimitedString = STUFF(#CommadelimitedString, 1, #IntLocation, '')
END
INSERT INTO #Result (Column1)
SELECT RTRIM(LTRIM(#CommadelimitedString))--LTRIM and RTRIM to ensure blank spaces are removed
RETURN
END
create table test1 ( id int not null, pricehistory varchar(max) not null );
insert into test1 values ( 1, '1634913730;48.38,1634916509;48.38,1635162352;37.96,1635177904;49.14,1635337722;1219.98,1635340811;27.17' );
insert into test1 values ( 2, '1634913731;42.42,1634916609;21.21' );
Select *,
(
Select avg(CAST(RTRIM(LTRIM(SUBSTRING(column1, 0, CHARINDEX(';', column1, 0)))) as decimal)) From dbo.BreakStringIntoRows(pricehistory, ',')
) as AVG
FRom test1
sample output:

Get unique values using STRING_AGG in SQL Server

The following query returns the results shown below:
SELECT
ProjectID, newID.value
FROM
[dbo].[Data] WITH(NOLOCK)
CROSS APPLY
STRING_SPLIT([bID],';') AS newID
WHERE
newID.value IN ('O95833', 'Q96NY7-2')
Results:
ProjectID value
---------------------
2 Q96NY7-2
2 O95833
2 O95833
2 Q96NY7-2
2 O95833
2 Q96NY7-2
4 Q96NY7-2
4 Q96NY7-2
Using the newly added STRING_AGG function (in SQL Server 2017) as it is shown in the following query I am able to get the result-set below.
SELECT
ProjectID,
STRING_AGG( newID.value, ',') WITHIN GROUP (ORDER BY newID.value) AS
NewField
FROM
[dbo].[Data] WITH(NOLOCK)
CROSS APPLY
STRING_SPLIT([bID],';') AS newID
WHERE
newID.value IN ('O95833', 'Q96NY7-2')
GROUP BY
ProjectID
ORDER BY
ProjectID
Results:
ProjectID NewField
-------------------------------------------------------------
2 O95833,O95833,O95833,Q96NY7-2,Q96NY7-2,Q96NY7-2
4 Q96NY7-2,Q96NY7-2
I would like my final output to have only unique elements as below:
ProjectID NewField
-------------------------------
2 O95833, Q96NY7-2
4 Q96NY7-2
Any suggestions about how to get this result? Please feel free to refine/redesign from scratch my query if needed.
Use the DISTINCT keyword in a subquery to remove duplicates before combining the results: SQL Fiddle
SELECT
ProjectID
,STRING_AGG(value, ',') WITHIN GROUP (ORDER BY value) AS
NewField
from (
select distinct ProjectId, newId.value
FROM [dbo].[Data] WITH(NOLOCK)
CROSS APPLY STRING_SPLIT([bID],';') AS newID
WHERE newID.value IN ( 'O95833' , 'Q96NY7-2' )
) x
GROUP BY ProjectID
ORDER BY ProjectID
You can use distinct in the subquery used for the apply:
SELECT d.ProjectID,
STRING_AGG( newID.value, ',') WITHIN GROUP (ORDER BY newID.value) AS
NewField
FROM [dbo].[Data] d CROSS APPLY
(select distinct value
from STRING_SPLIT(d.[bID], ';') AS newID
) newID
WHERE newID.value IN ( 'O95833' , 'Q96NY7-2' )
group by projectid;
This is a function that I wrote that answers the OP Title:
Improvements welcome!
CREATE OR ALTER FUNCTION [dbo].[fn_DistinctWords]
(
#String NVARCHAR(MAX)
)
RETURNS NVARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #Result NVARCHAR(MAX);
WITH MY_CTE AS ( SELECT Distinct(value) FROM STRING_SPLIT(#String, ' ') )
SELECT #Result = STRING_AGG(value, ' ') FROM MY_CTE
RETURN #Result
END
GO
Use like:
SELECT dbo.fn_DistinctWords('One Two Three Two One');
As #SeanLange pointed out in the comments, this is a terrible way to pull out the data, but if you had to, just make it 2 separate queries as follows:
SELECT
ProjectID
,STRING_AGG( val, ',') WITHIN GROUP (ORDER BY val) AS NewField
FROM
(
SELECT DISTINCT
ProjectID
,newID.value AS val
FROM
[dbo].[Data] WITH(NOLOCK)
CROSS APPLY STRING_SPLIT([bID],';') AS newID
WHERE
newID.value IN ('O95833' , 'Q96NY7-2')
) t
GROUP BY
ProjectID
That should do it.
Another possibility to get unique strings from STRING_AGG would be to perform these three steps after fetching the comma separated string:
Split the string (STRING_SPLIT)
Select DISTINCT from the splits
Apply STRING_AGG again to a select with a group on a single key
Example:
(select STRING_AGG(CAST(value as VARCHAR(MAX)), ',')
from (SELECT distinct 1 single_key, value
FROM STRING_SPLIT(STRING_AGG(CAST(customer_division as VARCHAR(MAX)), ','), ','))
q group by single_key) as customer_division
Here is my improvement on #ttugates to make it more generic:
CREATE OR ALTER FUNCTION [dbo].[fn_DistinctList]
(
#String NVARCHAR(MAX),
#Delimiter char(1)
)
RETURNS NVARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN
DECLARE #Result NVARCHAR(MAX);
WITH MY_CTE AS ( SELECT Distinct(value) FROM STRING_SPLIT(#String,
#Delimiter) )
SELECT #Result = STRING_AGG(value, #Delimiter) FROM MY_CTE
RETURN #Result
END
You can make a distinct view of the table, that holds the aggregate values, that is even simpler:
Create Table Test (field1 varchar(1), field2 varchar(1));
go
Create View DistinctTest as (Select distinct field1, field2 from test group by field1,field2);
go
insert into Test Select 'A', '1';
insert into Test Select 'A', '2';
insert into Test Select 'A', '2';
insert into Test Select 'A', '2';
insert into Test Select 'D', '1';
insert into Test Select 'D', '1';
select string_agg(field1, ',') from Test where field2 = '1'; /* duplicates: A,D,D */;
select string_agg(field1, ',') from DistinctTest where field2 = '1'; /* no duplicates: A,D */;
Oracle (since version 19c) suports listagg (DISTINCT ..., but Microsoft SQL Server not probably.

Getting csv from table without any grouping SQL

I have a table that contains only two columns say Name, DepartmentId as follows
CREATE TABLE #TempDepartment
(
Name NVARCHAR(50)
,DepartmentId INT
)
INSERT INTO #TempDepartment VALUES
('ABC',1)
,('ABC2',1)
,('DEF',2)
,('XYZ',3)
I am looking to retrieve list of distinct departmentId in comma seperated format. How can I achieve this in SQL? Basically there is no common group item, hence the confusion with either using STUFF or COALESCE.
Expected output is - 1,2,3
Try this query
Select Top 1 STUFF((SELECT ', ' + CAST(DepartmentId AS Varchar(20))
FROM #TempDepartment b Group By DepartmentId
FOR XML PATH('')), 1, 2, '') CSV
From #TempDepartment
Fiddle Output
O/P :
+---------+
| CSV |
+---------|
| 1, 2, 3 |
+---------+
Try this:
;WITH CTE AS
(
SELECT DISTINCT DepartmentId
FROM #TempDepartment
)
SELECT TOP (1)
STUFF((SELECT ',' + CAST(DepartmentId AS VARCHAR(10))
FROM CTE FOR XML PATH('')), 1, 1, '')
FROM CTE
Should produce the required output:
1,2,3

SQL query problem

Let´s say I have two tables, "Garden" and "Flowers". There is a 1:n-relationship between these tables, because in a garden can be many flowers. Is it possible to write an SQL query which returns a result with the following structure:
GardenName Flower1Name Flower2Name .... (as long as there are entries in flowers)
myGarden rose tulip
CREATE TABLE #Garden (Id INT, Name VARCHAR(20))
INSERT INTO #Garden
SELECT 1, 'myGarden' UNION ALL
SELECT 2, 'yourGarden'
CREATE TABLE #Flowers (GardenId INT, Flower VARCHAR(20))
INSERT INTO #Flowers
SELECT 1, 'rose' UNION ALL
SELECT 1, 'tulip' UNION ALL
SELECT 2, 'thistle'
DECLARE #ColList nvarchar(max)
SELECT #ColList = ISNULL(#ColList + ',','') + QUOTENAME('Flower' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS VARCHAR))
FROM #Flowers WHERE GardenId = (
SELECT TOP 1 GardenId
FROM #Flowers
ORDER BY COUNT(*) OVER (PARTITION BY GardenId) DESC
)
EXEC (N'
;WITH cte As
(
SELECT *, ''Flower'' + CAST(ROW_NUMBER() OVER (PARTITION BY GardenId ORDER BY (SELECT 0)) AS VARCHAR) RN
FROM #Flowers F
)
SELECT Name,' + #ColList + N'
FROM cte
JOIN #Garden g ON g.Id = GardenId
PIVOT (MAX(Flower) FOR RN IN (' + #ColList + N')) Pvt')
DROP TABLE #Garden
DROP TABLE #Flowers
Returns
Name Flower1 Flower2
-------------------- -------------------- --------------------
myGarden rose tulip
yourGarden thistle NULL
Look at using Pivot in SQL Server. Here is a good link that goes over how it works:
http://www.kodyaz.com/articles/t-sql-pivot-tables-in-sql-server-tutorial-with-examples.aspx
Ok, i think i got it working. Try this:
with temp as
(
select 'myGarden' as name, 'test1' as flower
union
select 'myGarden','test2'
union
select 'myGarden','test5'
union
select 'abeGarden','test4'
union
select 'abeGarden','test5'
union
select 'martinGarden', 'test2'
)
select* from temp
pivot
(
max(flower)
for flower in (test1,test2,test3,test4,test5)
) PivotTable
You could also make the values in the in clause dynamic. Since this is a CTE i can't in my example.
Dynamic SQL with a cursor is the only way I can think of, and it won't be pretty.
If you only want the results for one garden at a time this would give you the data:
select gardenName from tblGarden where gardenid = 1
Union ALL
select tblFLowers.flowerName from tblFlowers where gardenid = 1

Create a delimitted string from a query in DB2

I am trying to create a delimitted string from the results of a query in DB2 on the iSeries (AS/400). I've done this in T-SQL, but can't find a way to do it here.
Here is my code in T-SQL. I'm looking for an equivelant in DB2.
DECLARE #a VARCHAR(1000)
SELECT #a = COALESCE(#a + ', ' + [Description], [Description])
FROM AP.Checkbooks
SELECT #a
If the descriptions in my table look like this:
Desc 1
Desc 2
Desc 3
Then it will return this:
Desc 1, Desc 2, Desc 3
Essentially you're looking for the equivalent of MySQL's GROUP_CONCAT aggregate function in DB2. According to one thread I found, you can mimic this behaviour by going through the XMLAGG function:
create table t1 (num int, color varchar(10));
insert into t1 values (1,'red'), (1,'black'), (2,'red'), (2,'yellow'), (2,'green');
select num,
substr( xmlserialize( xmlagg( xmltext( concat( ', ', color ) ) ) as varchar( 1024 ) ), 3 )
from t1
group by num;
This would return
1 red,black
2 red,yellow,green
(or should, if I'm reading things correctly)
You can do this using common table expressions (CTEs) and recursion.
with
cte1 as
(select description, row_number() over() as row_nbr from checkbooks),
cte2 (list, cnt, cnt_max) AS
(SELECT VARCHAR('', 32000), 0, count(description) FROM cte1
UNION ALL
SELECT
-- No comma before the first description
case when cte2.list = '' THEN RTRIM(CHAR(cte1.description))
else cte2.list || ', ' || RTRIM(CHAR(cte1.description)) end,
cte2.cnt + 1,
cte2.cnt_max
FROM cte1,cte2
WHERE cte1.row_nbr = cte2.cnt + 1 AND cte2.cnt < cte2.cnt_max ),
cte3 as
(select list from cte2
where cte2.cnt = cte2.cnt_max fetch first 1 row only)
select list from cte3;
I'm trying to do this in OLEDB and from what I understand you can't do this because you can't do anything fancy in SQL for OLEDB like declare variables or create a table. So I guess there is no way.
If you are running DB2 9.7 or higher, you can use LISTAGG function. Have a look here:
http://pic.dhe.ibm.com/infocenter/db2luw/v9r7/index.jsp?topic=%2Fcom.ibm.db2.luw.sql.ref.doc%2Fdoc%2Fr0058709.html