Comma Separated Value to Multiple BIT Columns - sql-server-2008

Hy , I have multiple values and i need a list of checks
ex:
1,2,4,
3,4, should be :
day1 day2 day3 day4
_1____1____0____1
_0____0____1____1
one method is
CAST(CASE WHEN PATINDEX('1,', [day]) > 0 THEN 1 ELSE 0 END AS BIT) as [day1],
CAST(CASE WHEN PATINDEX('2,', [day]) > 0 THEN 1 ELSE 0 END AS BIT) as [day2],
CAST(CASE WHEN PATINDEX('3,', [day]) > 0 THEN 1 ELSE 0 END AS BIT) as [day3],
CAST(CASE WHEN PATINDEX('4,', [day]) > 0 THEN 1 ELSE 0 END AS BIT) as [day4]
please help me with a better method because i have multiple columns
thanks

You can use a string split function of your choice with pivot.
declare #T table
(
ID int identity,
day varchar(20)
)
insert into #T values
('1,2,4,'),
('3,4,')
select isnull(P.[1], 0) as day1,
isnull(P.[2], 0) as day2,
isnull(P.[3], 0) as day3,
isnull(P.[4], 0) as day4
from
(
select T.ID, S.s, 1 as x
from #T as T
cross apply dbo.Split(',', T.day) as S
) as T
pivot (min(T.x) for T.s in ([1],[2],[3],[4])) as p
Result:
day1 day2 day3 day4
1 1 0 1
0 0 1 1

You could use the split function outlined here and then pivot the values into columns.
OR!
This answer looks like the same thing you're trying to do.

Use a second table to store valid days and then query on it. See this fiddle.
EDIT
See updated fiddle, or code below:
create table test (day1 varchar(8), day2 varchar(8), day3 varchar(8), day4 varchar(8));
insert into test (day1, day2, day3, day4) values ('day1', 'day2', 'day3', 'day4');
create table valid_days (valid_day_no int);
insert into valid_days values (1), (2), (4);
select cast (case when exists(select 1 from valid_days where valid_day_no = substring(day1, 4, len(day1))) then 1 else 0 end as bit) day1,
cast (case when exists(select 1 from valid_days where valid_day_no = substring(day2, 4, len(day2))) then 1 else 0 end as bit) day2,
cast (case when exists(select 1 from valid_days where valid_day_no = substring(day3, 4, len(day3))) then 1 else 0 end as bit) day3,
cast (case when exists(select 1 from valid_days where valid_day_no = substring(day1, 4, len(day4))) then 1 else 0 end as bit) day4
from test;
Note that SQL is a fixed column language, you need to use a programming language to dynamically build variable length column queries.

Related

MySQL How to count columns containing specified column value

I have the table xyz
ID day1 day2 day3 day4
1 A A P P
2 A A A P
3 A A A A
4 A P P P
I want to be able to query this and return the ID with the number of columns that have A as their value in that row. So the result would look like this:
ID Count
1 2
2 3
3 4
4 1
Here is a trick you can use involving string concatenation:
SELECT
ID,
CHAR_LENGTH(CONCAT(day1, day2, day3, day4)) -
CHAR_LENGTH(REPLACE(CONCAT(day1, day2, day3, day4), 'A', '')) AS Count
FROM yourTable
ORDER BY ID;
Demo
If you want to count other letters as well, just duplicate the logic I have for the letter A, e.g. for L we can try:
CHAR_LENGTH(CONCAT(day1, day2, day3, day4)) -
CHAR_LENGTH(REPLACE(CONCAT(day1, day2, day3, day4), 'L', ''))
This is simple to accomplish applying case to all columns and usmming them up! Try below:
select id,
case day1 when 'A' then 1 else 0 end +
case day2 when 'A' then 1 else 0 end +
case day3 when 'A' then 1 else 0 end +
case day4 when 'A' then 1 else 0 end `CountA`,
case day1 when 'L' then 1 else 0 end +
case day2 when 'L' then 1 else 0 end +
case day3 when 'L' then 1 else 0 end +
case day4 when 'L' then 1 else 0 end `CountL`,
case day1 when 'P' then 1 else 0 end +
case day2 when 'P' then 1 else 0 end +
case day3 when 'P' then 1 else 0 end +
case day4 when 'P' then 1 else 0 end `CountP`
from Tbl

mysql joining the same table

Given a table named RECORD in mysql with following structure:
rid(pk & AI) patientid(fk) recordTYPE(varchar) recordValue(varchar) recordTimestamp(timestamp)
1 1 temperature(℃) 37.2 2015-08-11 18:10:04
2 1 weight(kg) 65.0 2015-08-11 18:20:08
3 1 heartbeat(bpm) 66 2015-08-11 18:30:08
4 1 temperature(℃) 36.8 2015-08-11 18:32:08
You can see that for the same date, there can be multiple records for one particular type of record. e.g. temperature in the sample data :
rid patientid recordTYPE value recordtimestamp
1 1 temperature(℃) 37.2 2015-08-11 18:10:04
4 1 temperature(℃) 36.8 2015-08-11 18:32:08
In this case, we should choose the latest record. i.e. the record with rid = 4 and value = 36.8 .
Now given an input date e.g. '2015-8-11', I want to do a query to obtain something like:
date patientid temperature(℃) weight(kg) heartbeat(bpm)
2015-08-11 1 36.8 65.0 66
2015-08-11 2 36.5 80.3 70
2015-08-11 3 35.5 90.5 80
..........................................................
..........................................................
2015-08-11 4 35.5 null null
Fig. 2
In addition, you can see that for a particular date, there may not be any records of some types. In this case, the value in that column is null.
I tried the following query:
SELECT max(recordTimestamp), patientid, recordTYPE, recordValue
FROM RECORD
WHERE date(recordTimestamp) = '2015-08-11'
GROUP BY patientid, recordTYPE
The result is something like:
date patientid recordTYPE recordValue
2015-08-11 1 temperature(℃) 36.8
2015-08-11 1 weight(kg) 65.0
2015-08-11 1 heartbeat(bpm) 66
2015-08-11 2 temperature(℃) 36.5
2015-08-11 2 weight(kg) 80.3
2015-08-11 2 heartbeat(bpm) 70
2015-08-11 4 temperature(℃) 35.5
Fig. 4
The questions are:
Given this table RECORD, what is the proper mysql statement (in terms
of performance such as retrieval speed) to produce the desired result set (i.e. Fig.2)?
Will it be better (in terms of facilitating query and scalability such as adding new types of record) if the db design is changed?
e.g. Create one table for each type of record instead of putting all types of record in one table.
Any suggestion is appreciated as I'm a db novice...... Thank you.
You can try this:-
SELECT MAX(rid), patientid, recordTYPE, MAX(recordValue), recordTimestamp
FROM YOUR_TABLE
WHERE recordTimestamp = '2015/08/11'
GROUP BY patientid, recordTYPE, recordTimestamp;
Here's one way to do it. SQL Fiddle Demo
Sadly MySQL doesn't support the row_number() over (partition by ...) syntax which would have simplified this a lot.
Instead I've made excessive use of a trick discussed here: https://stackoverflow.com/a/3470355/361842
select `date`
, `patientId`
, max(case when `tRank`=1 then `temperature(℃)` else null end) `temperature(℃)`
, max(case when `wRank`=1 then `weight(kg)` else null end) `weight(kg)`
, max(case when `hRank`=1 then `heartbeat(bpm)` else null end) `heartbeat(bpm)`
from
(
select case when #p = `patientId` and #d = cast(`recordTimestamp` as date) then #x := 1 else #x := 0 end
, case when #x = 0 then #t := 0 end
, case when #x = 0 then #w := 0 end
, case when #x = 0 then #h := 0 end
, case `recordType` when 'temperature(℃)' then case #x when 1 then #t := #t + 1 else #t := 1 end else null end as `tRank`
, case `recordType` when 'weight(kg)' then case #x when 1 then #w := #w + 1 else #t := 1 end else null end as `wRank`
, case `recordType` when 'heartbeat(bpm)' then case #x when 1 then #h := #h + 1 else #t := 1 end else null end as `hRank`
, case `recordType` when 'temperature(℃)' then `recordValue` else null end as `temperature(℃)`
, case `recordType` when 'weight(kg)' then `recordValue` else null end as `weight(kg)`
, case `recordType` when 'heartbeat(bpm)' then `recordValue` else null end as `heartbeat(bpm)`
, #d := cast(`recordTimestamp` as date) as `date`
, #p := `patientId` as `patientId`
from `Record`
cross join
(
SELECT #t := 0
, #w := 0
, #h := 0
, #p := 0
, #x := 0
, #d := cast(null as date)
) x
order by `patientId`, `recordTimestamp` desc
) y
group by `date`, `patientId`
order by `date`, `patientId`
Breakdown
This says that if this is the last temperature of the day for the current grouping's partientId/date combo then return it; otherwise return null. It then takes the max of the matching values (which given all but 1 are null, gives us the one we're after).
, max(case when `tRank`=1 then `temperature(℃)` else null end)
How tRank = 1 means the last temperature of the day for a patientId/date combo is explained later.
This line says that if this record has the same patientId and date as the previous record then set x to 1; if it's a new combo set it to 0.
select case when #p = `patientId` and #d = cast(`recordTimestamp` as date) then #x := 1 else #x := 0 end
The next lines say that if we have a new patiendIt/date combo, reset the t, w and h markers to say "the next value you receive will be the one we're after".
, case when #x = 0 then #t := 0 end
The next lines split the data by recordType; returning null if this record isn't their record type, or returning a number saying what how many of this type of record we've now seen for the patientId/date combo.
, case `recordType` when 'temperature(℃)' then case #x when 1 then #t := #t + 1 else #t := 1 end else null end as `tRank`
This is similar to the above; except instead of returning a combo-counter it returns the value of the current record (or null if this is a different record type).
, case `recordType` when 'temperature(℃)' then `recordValue` else null end as `temperature(℃)`
We then record the current record's date and patientId values, so we can compare them with the next record on the next iteration.
, #d := cast(`recordTimestamp` as date) as `date`
, #p := `patientId` as `patientId`
The cross join and following subquery is just used to initialise our variables.
The (first) order by is used to ensure that comparing current and previous records is enough to tell if we're looking ata different combo (i.e. if all combos are grouped then any change is easy to spot; if the combos keep alternating we'd need to keep track of every combo we'd seen before).
recordTimestamp is sorted descending so that the first record we see on a new combo will be the last record that day; the one we're after.
The group by is used to ensure we get 1 result per combo; and the last order by just to make our output ordered.

Arrange column with both numeric and hyphenated values

I'm trying to arrange a column with values like:
6-3
11-1
3
8-5
5
6-2
1
7
11-4
8-12
2
I want them to be arranged like this:
1
2
3
5
7
6-2
6-3
8-5
8-12
11-1
11-4
I have this query now:
select column_name from database_name.dbo.table_name
order by
(case when (BOX_NO not like '%-%')
then -1 when (BOX_NO like '%-%')
then cast(SUBSTRING(reverse(BOX_NO), LEN(BOX_NO)-(CHARINDEX('-', reverse(BOX_NO))-2), 0) as int) end),
(case when (column_name not like '%-%')
then cast(column_name as int)
when (column_name like '%-%')
then cast(SUBSTRING(column_name, LEN(column_name)-(CHARINDEX('-', reverse(column_name))-2), 8000) as int) end)
I'm going crazy. If anyone could help me that would be nice. Thanks!
Woopsy daisies! Everything's fine now. The first Case was wrong. There shouldn't have been a second reverse. And the search should start at 8000 and not 0. My bad!
(case when (column_name not like '%-%') then 0 when (column_name like '%-%') then cast(SUBSTRING(reverse(column_name), LEN(column_name)-(CHARINDEX('-', column_name)-2), 8000) as int) end),
declare #test table ( value varchar(10))
insert #test values ('6-3'),
('11-1'),
('3'),
('8-5'),
('5'),
('6-2'),
('1'),
('7'),
('11-4'),
('8-12'),
('2')
select
T2.value,
case when CHARINDEX('-', T1.value) = 0 then cast(T1.value as int) else cast(LEFT(T1.value, CHARINDEX('-', T1.value) - 1) as int) end,
case when CHARINDEX('-', T1.value) = 0 then 0 else cast(substring(T1.value, CHARINDEX('-', T1.value) + 1, 10) as int) end
from #test T1, #test T2
where T1.value = T2.value
order by 2, 3

MySql: is it possible to 'SUM IF' or to 'COUNT IF'?

I have a column 'hour'
I have a column 'kind' (it can be 1,2 or 3)
I'd like to do something like:
SELECT count(id), SUM(hour) as totHour, SUM( IF ( kind = 1, 1, 0 ) ) as countKindOne
or
SELECT count(id), SUM(hour) as totHour, COUNT( IF ( kind = 1 ) ) as countKindOne
But mysql tell me I've an error... what's the error!?
Please see this stackoverflow topic: MySQL SUM IF field b = field a
.. I'm not able to reply this ...
You can use a CASE statement:
SELECT count(id),
SUM(hour) as totHour,
SUM(case when kind = 1 then 1 else 0 end) as countKindOne
you want something like:
SELECT count(id), SUM(hour) as totHour, SUM(kind=1) as countKindOne;
Note that your second example was close, but the IF() function always takes three arguments, so it would have had to be COUNT(IF(kind=1,1,NULL)). I prefer the SUM() syntax shown above because it's concise.
You can also use SUM + IF which is shorter than SUM + CASE:
SELECT
count(id)
, SUM(IF(kind=1, 1, 0)) AS countKindOne
, SUM(CASE WHEN kind=2 THEN 1 ELSE 0 END) AS countKindTwo
There is a slight difference between the top answers, namely SUM(case when kind = 1 then 1 else 0 end) and SUM(kind=1).
When all values in column kind happen to be NULL, the result of SUM(case when kind = 1 then 1 else 0 end) is 0, whereas the result of SUM(kind=1) is NULL.
An example (http://sqlfiddle.com/#!9/b23807/2):
Schema:
CREATE TABLE Table1
(`first_col` int, `second_col` int)
;
INSERT INTO Table1
(`first_col`, `second_col`)
VALUES
(1, NULL),
(1, NULL),
(NULL, NULL)
;
Query results:
SELECT SUM(first_col=1) FROM Table1;
-- Result: 2
SELECT SUM(first_col=2) FROM Table1;
-- Result: 0
SELECT SUM(second_col=1) FROM Table1;
-- Result: NULL
SELECT SUM(CASE WHEN second_col=1 THEN 1 ELSE 0 END) FROM Table1;
-- Result: 0
From MYSQL I solved the problem like this:
SUM(CASE WHEN used = 1 THEN 1 ELSE 0 END) as amount_one,
Hope this helps :D
It is worth noting that you can build upon Gavin Toweys answer by using multiple fields from across your query such as
SUM(table.field = 1 AND table2.field = 2)
You can also use this syntax for COUNT and I am sure other functions as well.

return a set of counts of values in a column by ID

I have a table like this:
ID Type
----------
1 sent
1 sent
1 open
1 bounce
1 click
2 sent
2 sent
2 open
2 open
2 click
I want a query to return results like this:
ID sent open bounce click
1 2 1 1 1
2 2 2 0 1
Just can't work out how to do it. Thanks.
try PIVOT
SELECT ID,[sent],[open],[bounce],[click]
FROM your_table
PIVOT (COUNT([Type])
FOR [Type] in ([sent],[open],[bounce],[click]))p
SQL Fiddle Demo
Select Id,
count(case When type='sent' then 1 else 0 end) as sent,
count(case when type='open' then 1 else 0 end) as open
From table
Group by Id
If that won't give you the exact answer then try count (distinct case....) :)
You can get such result by using PIVOT or GROUP BY, you can even get results if you have variable values in Type column:
Test data:
CREATE TABLE #t(ID INT, Type VARCHAR(100))
INSERT #t
VALUES
(1, 'sent'),
(1, 'sent'),
(1, 'open'),
(1, 'bounce'),
(1, 'click'),
(2, 'sent'),
(2, 'sent'),
(2, 'open'),
(2, 'open'),
(2, 'click')
PIVOT approach:
SELECT pvt.*
FROM #t
PIVOT
(
COUNT(Type) FOR Type IN ([sent], [open], [bounce], [click])
) pvt
If there are other possible values for Type and you don't know them in advance use dynamic PIVOT:
DECLARE #cols NVARCHAR(1000) = STUFF(
(
SELECT DISTINCT ',[' + Type + ']'
FROM #t
FOR XML PATH('')
), 1, 1, '')
DECLARE #query NVARCHAR(2000) =
'
SELECT pvt.*
FROM #t
PIVOT
(
COUNT(Type) FOR Type IN ('+#cols+')
) pvt
'
EXEC(#query)
If you have known fixed values for Type, you can also use:
SELECT ID,
COUNT(CASE WHEN Type = 'sent' THEN 1 END) [sent],
COUNT(CASE WHEN Type = 'open' THEN 1 END) [open],
COUNT(CASE WHEN Type = 'bounce' THEN 1 END) bounce,
COUNT(CASE WHEN Type = 'click' THEN 1 END) click
FROM #t
GROUP BY ID