Transpose row into columns with If statement - sql

I am trying to transpose certain row values into columns. Below is what my table looks like:
ID Item Color Quantity
1 Shoe Red
1 Shirt 3
1 Hat Black
1 Socks 2
1 Pants Red
What I want the table to look like is this:
ID Shoe Shirt Hat Socks Pants
1 Red 3 Black 2 Red
Below is the code I am using:
select Max(Case When [Item] = 'Shoe' Then Color End) as Shoe
,Max(Case When [Item] = 'Shirt' Then Quantity End) as Shirt
,Max(Case When [Item] = 'Hat' Then Color End) as Hat
,Max(Case When [Item] = 'Socks' Then Quantity End) as Socks
,Max(Case When [Item] = 'Pants' Then Color End) as Pants
From Inventory
The problem with my code is sometimes the value for the row is in column Color and other times it's in column Quantity. I would like to be able to use only to column with a value. My real select statement has over 50 Max(Case) statements, so if there is someway I can do an overarching if statement and not have to do one for each line that would be great!

If you want a dynamic version.
In the sub-query below, you'll notice that we use CONCAT(), which will handle NULL values appropriately.
Declare #Filter varchar(100) = 'Shoe,Socks'
Declare #SQL varchar(max)
Select #SQL = 'Select ID,' + '['+Replace(#Filter,',','],[')+']' + '
From (
Select ID
,Item
,Value = concat(NullIf(Color,''''),NullIf(Quantity,0))
From Inventory
Where charindex('',''+Item+'','','','+#Filter+','')>0
) A
Pivot (max(Value) For Item in (' + '['+Replace(#Filter,',','],[')+']' + ') ) p'
Exec(#SQL);
EDIT:
Added NullIf() to the concat() to trap "empty" values.

How about using a common table expression (CTE) to find the non null value column then PIVOT on the Item if you know all of your Items up front. It should be noted that this only works if the other value is null, otherwise you would need to check for '' or 0.
;WITH CTE
AS
(
SELECT ID, Item, COALESCE(Color, CAST(Quantity AS VARCHAR(10))) AS Value
FROM Inventory
)
select * from CTE
pivot (max (Value) for Item in ([Shoe],[Shirt],[Hat],[Socks],[Pants])) AS c

You probably need a dictionary which maps an item to a value column. For example
select Max(Case When [Item] = 'Shoe' Then
Case col when 'Color' then Color else Quantity End
End) as Shoe
,Max(Case When [Item] = 'Shirt' Then
Case col when 'Color' then Color else Quantity End
End) as Shirt
,Max(Case When [Item] = 'Hat' Then
Case col when 'Color' then Color else Quantity End
End) as Hat
,Max(Case When [Item] = 'Socks' Then
Case col when 'Color' then Color else Quantity End
End) as Socks
,Max(Case When [Item] = 'Pants' Then
Case col when 'Color' then Color else Quantity End
End) as Pants
From (
select i.*, col
from Inventory i
join (
values ('Shoe', 'Color')
,('Shirt', 'Quantity')
,('Hat', 'Color')
,('Socks', 'Quantity')
,('Pants', 'Color')
) dict (itm, col) on dict.itm=t.item
) t

Related

SQL - Transpose different rows in columns

I have 1 table (I can not modify it before) from a bulk insert, and I need to transpose all the rows in "VALUES" column into single columns. Except for the "YEAR" (going from 0 to 50) and the "VALUES" columns, the other 3 columns are filled with a unique value (example: in year 0 size is 'L', color is 'red' and price is '10' and this size,color and price values are constant for all my 50 years).
The input table is like this:
I would like to have as output all the "VALUES" in columns called for example "VALUE_0" "VALUE_1" "VALUE_2" "VALUE_3" etc, where the numbers 0,1,2,3 stand for the year considered.
CREATE TABLE #CURVE(
YEAR INT,
SIZE VARCHAR(100),
COLOR VARCHAR(100),
PRICE VARCHAR(100),
VALUES FLOAT
)
The Output should be:
One option uses conditional aggregation:
select
size,
color,
price,
max(case when year = 0 then value end) value_0,
max(case when year = 1 then value end) value_1,
max(case when year = 2 then value end) value_2,
max(case when year = 3 then value end) value_3,
max(case when year = 4 then value end) value_4,
max(case when year = 5 then value end) value_5
from #curve
group by size, color, price
You can easily extend the select clause to handle more years.
This is a cross-database solution, that usually performs as good or better than vendor-specific implementations of pivot. On top of that (and for what it's worth), I find it easier to understand.
we need to know how many values do you expect in Values column for a year?
Otherwise the query looks like this:
SELECT [YEAR], [SIZE], [COLOR], [PRICE], [1] as [Value1], [2] as [Value2], [3] as [Value3]
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY [YEAR], [SIZE], [COLOR], [PRICE] ORDER BY (SELECT NULL)) ID
FROM #CURVE c
) t
PIVOT(MAX([Value]) FOR ID IN ([1], [2], [3]])) p

Transpose rows from split_string into columns

I'm stuck trying to transpose a set of rows into a table. In my stored procedure, I take a delimited string as input, and need to transpose it.
SELECT *
FROM string_split('123,4,1,0,0,5|324,2,0,0,0,4','|')
CROSS APPLY string_split(value,',')
From which I receive:
value value
123,4,1,0,0,5 123
123,4,1,0,0,5 4
123,4,1,0,0,5 1
123,4,1,0,0,5 0
123,4,1,0,0,5 0
123,4,1,0,0,5 5
324,2,0,0,0,4 324
324,2,0,0,0,4 2
324,2,0,0,0,4 0
324,2,0,0,0,4 0
324,2,0,0,0,4 0
324,2,0,0,0,4 4
The values delimited by | are client details. And within each client, there are six attributes, delimited by ,. I would like an output table of:
ClientId ClientTypeId AttrA AttrB AttrC AttrD
------------------------------------------------
123 4 0 0 0 5
324 2 0 0 0 4
What's the best way to go about this? I've been looking at PIVOT but can't make it work because it seems like I need row numbers, at least.
This answer assumes that row number function will "follow the order" of the string. If it does not you will need to write your own split that includes a row number in the resulting table. (This is asked on the official documentation page but there is no official answer given).
SELECT
MAX(CASE WHEN col = 1 THEN item ELSE null END) as ClientId,
MAX(CASE WHEN col = 2 THEN item ELSE null END) as ClientTypeId,
MAX(CASE WHEN col = 3 THEN item ELSE null END) as AttrA,
MAX(CASE WHEN col = 4 THEN item ELSE null END) as AttrB,
MAX(CASE WHEN col = 5 THEN item ELSE null END) as AttrC,
MAX(CASE WHEN col = 6 THEN item ELSE null END) as AttrD
FROM (
SELECT A.value as org, B.value as item,
ROW_NUMBER() OVER (partition by A.value) as col
FROM string_split('123,4,1,0,0,5|324,2,0,0,0,4','|') as A
CROSS APPLY string_split(A.value,',') as B
) X
GROUP BY org
You might get a message about nulls in aggregate function ignored. (I always forget which platforms care and which don't.) If you do you can replace the null with 0.
Note, this is not as fast and using a CTE to find the 5 commas in the string with CHARINDEX and then using SUBSTRING to extract the values. But I'm to lazy to write up that solution which I would need to test to get all the off by 1 issues right. Still, I suggest you do it that way if you have a big data set.
I know you already got it pretty much answered, but here you can find a PIVOT solution
select [ClientID],[ClientTypeId],[AttrA],[AttrB],[AttrC],[AttrD]
FROM
(
select case when ColumnRow = 1 then 'ClientID'
when ColumnRow = 2 then 'ClientTypeId'
when ColumnRow = 3 then 'AttrA'
when ColumnRow = 4 then 'AttrB'
when ColumnRow = 5 then 'AttrC'
when ColumnRow = 6 then 'AttrD' else null end as
ColumnRow,t.value,ColumnID from (
select ColumnID,z.value as stringsplit,b.value, cast(Row_number()
over(partition by z.value order by z.value) as
varchar(50)) as ColumnRow from (SELECT cast(Row_number() over(order by
a.value) as
varchar(50)) as ColumnID,
a.value
FROM string_split('123,4,1,0,0,5|324,2,0,0,0,4','|') a
)z
CROSS APPLY string_split(value,',') b
)t
) AS SOURCETABLE
PIVOT
(
MAX(value)
FOR ColumnRow IN ([ClientID],[ClientTypeId],[AttrA],[AttrB],[AttrC],
[AttrD])
)
AS
PivotTable

Grouping By different rows

I have returned rows which look like this:
2 - Eggs
3 - Bacon
4 - Bacon Smoked
I would like to group by '%Bacon%' so that my count is 2.
How can i do this is SQL?
I should see results like this:
Eggs - 1
Bacon - 2
How about the following (Demo):
SELECT 'Eggs' AS Category, COUNT(*) AS MyCount
FROM MyTable
WHERE MyField LIKE '%Eggs%'
UNION ALL
SELECT 'Bacon' AS Category, COUNT(*) AS MyCount
FROM MyTable
WHERE MyField LIKE '%Bacon%'
Not tested, but I think it should work
SELECT COUNT(*) as QTY, RS.FOOD_TYPE
FROM
(SELECT
Case patIndex ('%[ /-]%', LTrim (FOOD_TYPE))
When 0 Then LTrim (FOOD_TYPE)
Else substring (LTrim (FOOD_TYPE), 1, patIndex ('%[ /-]%', LTrim (FOOD_TYPE)) - 1)
End FOOD_TYPE
FROM YOUR_TABLE) RS
GROUP BY RS.FOOD_TYPE
Other solution:
SELECT
Eggs = SUM(CASE WHEN FoodColumn LIKE '%Eggs%' THEN 1 ELSE 0 END),
Bacon = SUM(CASE WHEN FoodColumn LIKE '%Bacon%' THEN 1 ELSE 0 END)
FROM Test
You can see demo here.
If you need to separate the result into two separate rows
SELECT *
FROM
(
SELECT
Eggs = SUM(CASE WHEN FoodColumn LIKE '%Eggs%' THEN 1 ELSE 0 END),
Bacon = SUM(CASE WHEN FoodColumn LIKE '%Bacon%' THEN 1 ELSE 0 END)
FROM Test
) AS Test
UNPIVOT
(
Quantity FOR Foods IN (Eggs, Bacon)
) AS Result
You can see demo here.
This is a very specific case.. can you provide more Data? Does this help in anyway?
with list (item) as (
select
item
from (
values
('Eggs'),
('Bacon'),
('Bacon Smoked')) list (item)
)
select
LEFT(item,
(case
when CHARINDEX(' ',item,1) = 0
then LEN(item)
else CHARINDEX(' ',item,1) end)
) filtered,
COUNT(*)
from list
group by
LEFT(item,
(case
when CHARINDEX(' ',item,1) = 0
then LEN(item)
else CHARINDEX(' ',item,1) end))
create table MyTable
(id int, FieldName varchar(50) )
insert into MyTable values (1, 'Eggs')
insert into MyTable values (2, 'Bacon')
insert into MyTable values (3, 'Bacon Smoked')
select count(FieldName), FieldName from (
select
case
when charindex('eggs', FieldName) > 0 then 'eggs'
when charindex('bacon', FieldName) > 0 then 'bacon'
end as FieldName
from MyTable) as myMyTablealias
group by FieldName
check it out

How to SELECT COUNT multiple values in one column

rather a newbie at SQL, so please be gentle....as I think this is a basic one.
I'm trying to write a query with multiple (13) counts, based off Column1. The 1st Count is the over-all total. And then the 12 others are filtered by Color. I can get my results by doing multiple Counts all in one query, but this gives me 13 rows of data. The goal here is to get everything on just one row. So, almost like each count would be its own column. Here is an example of the data model
Database = CARS, Table = TYPES, Column1 = LICENSE, Column2 = COLOR
SELECT COUNT (LICENSE) AS 'Total ALL Cars'
FROM CARS.TYPES WITH (NOLOCK)
SELECT COUNT (LICENSE) AS 'Total RED Cars'
FROM CARS.TYPES WITH (NOLOCK)
WHERE COLOR = 'RED'
And on & on & on for each remaining color. This works, but again, I'm trying to streamline it all into one row of data, IF possible. Thank you in advance
You simply need to include color in select statement and group by it to count cars of each color.
SELECT Color, Count(*)
FROM CARS.TYPES WITH(NOLOCK)
GROUP BY Color
or
SELECT COUNT(CASE WHEN Color = 'RED' THEN 1
ELSE NULL
END) AS RedCars
,COUNT(CASE WHEN Color = 'BLUE' THEN 1
ELSE NULL
END) AS BlueCars
,COUNT(*) AS AllCars
FROM CARS.TYPES WITH ( NOLOCK )
You can do this with a conditional SUM():
SELECT SUM(CASE WHEN Color = 'Red' THEN 1 END) AS 'Total Red Cars'
,SUM(CASE WHEN Color = 'Blue' THEN 1 END) AS 'Total Blue Cars'
FROM CARS.TYPES
If using MySQL you can simplify further:
SELECT SUM(Color = 'Red') AS 'Total Red Cars'
,SUM(Color = 'Blue') AS 'Total Blue Cars'
FROM CARS.TYPES
Or with PIVOT
SELECT RED + BLUE + GREEN AS total,
RED,
BLUE,
GREEN
FROM CARS.TYPES PIVOT (COUNT (LICENSE) FOR COLOR IN ([RED], [BLUE], [GREEN])) P
SELECT SUM(Al) AllCount, SUM(Red) RedCount, SUM(Green) GreenCount, ...
(
SELECT 1 AS Al
, CASE WHEN Color = 'Red' THEN 1 ELSE 0 END AS Red
, CASE WHEN Color = 'Green' THEN 1 ELSE 0 END AS Green
...
FROM CARS.Types
)

Select records based on column priority

First of all, the title of this question is horrible, but I didn't find a better way to describe my issue.
There's probably a very easy way to do this, but I couldn't figure it out. This is very similar to this question, but I'm running on sqlite3 (iOS) so I suspect my options are much more limited.
I have a table with product records. All records have an ID (note: I'm not talking about the row ID, but rather an identification number unique to each product). Some products may have two entries in the table (both with the same ID). The only difference would be in a special column (let's say column COLOUR can be either RED or GREEN).
What I want to do is create a list of unique products based on the value of COLOUR, with priority to GREEN if both GREEN and RED records exist for the same product.
In short, if I have the following case:
id PRODUCT ID COLOUR
1 1001 GREEN
2 1002 GREEN
3 1002 RED
4 1003 RED
I would like my SELECT to return the rows 1, 2 and 4. How can I achieve this?
My current approach is to have separate tables, and do the join manually, but obviously this is a very bad idea..
Note: I've tried to use the same approach from here:
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION ALL
SELECT *
FROM xx
WHERE id not in (SELECT distinct id
FROM xx
WHERE f_COLOUR = "GREEN");
But the result I'm getting is rows 1,2,3,4 instead of just 1,2,4. What am I missing?
Edit: One more question please: how can this be made to work with a subset of records, ie. if instead of the entire table I wanted to filter some records?
For example, if I had something like SELECT * FROM table WHERE productID LIKE "1%" ... how can I retrieve each unique product, but still respecting the colour priority (GREEN>RED)?
Your query is nearly correct. Just use PRODUCTID and not ID.
SELECT *
FROM xx
WHERE f_COLOUR = "GREEN"
UNION
SELECT *
FROM xx
WHERE PRODUCTID not in
(SELECT PRODUCTID
FROM xx
WHERE f_COLOUR = "GREEN");
SQLFiddle Demo
Try this
SELECT *
FROM xx
WHERE COLOUR = 'GREEN'
UNION
SELECT *
FROM xx WHERE P_Id not in
(SELECT P_Id
FROM Persons
WHERE COLOUR = 'GREEN');
See ALSO SQL FIDDLE DEMO
I just want to offer that you can do this with a group by:
select (case when sum(case when colour = 'Green' then 1 else 0 end) > 0
then max(case when colour = 'Green' then id end)
else max(case when colour = 'Red' then id end)
end) as id,
product_id
(case when sum(case when colour = 'Green' then 1 else 0 end) > 0 then 'Green'
else 'Red'
end) as colour
from t
group by product_id
You can have it like this
WITH PriorityTable
AS
(
SELECT T.*,
ROW_NUMBER() OVER (PARTITION BY T.ID
ORDER BY PT.ColorPriority ) PriorityColumn
FROM XX AS T
INNER JOIN (
SELECT 'RED' AS f_COLOUR , 1 AS ColorPriority
UNION
SELECT 'GREEN' AS f_COLOUR , 2 AS ColorPriority
) AS PT
ON T.f_COLOUR = PT.f_COLOUR
)
SELECT * FROM PriorityTable
WHERE PriorityColumn = 1