Changing multiple rows to columns (Pivotable) in a table from mysql [duplicate] - mysql

Hi i have the following mysql data
INSERT INTO `monthly` (`id`, `year`, `stat_id`, `cat_id`, `January`, `February`, `March`, `April`, `May`, `June`, `July`, `August`, `September`, `October`, `November`, `December`) VALUES
(1, '2017', '12', '25', '1', '3', '1', '1', '3', '4', '4', '2', '4', '', '', ''),
and i would like it to be convert to be like this
INSERT INTO `monthlydata` (`id`, `year`, `monthName`, `stat_id`, `cat_id`, `data`) VALUES
(1, '2017', 'January', '12', '25', '1'),
(2, '2017', 'February', '12', '25', '3'),
(3, '2017', 'March', '12', '25', '1'),
(4, '2017', 'April', '12', '25', '1'),
(5, '2017', 'May', '12', '25', '3'),
(6, '2017', 'June', '12', '25', '4'),
(7, '2017', 'July', '12', '25', '4'),
(8, '2017', 'August', '12', '25', '2'),
(9, '2017', 'September', '12', '25', '4'),
(10, '2017', 'October', '12', '25', ''),
(11, '2017', 'November', '12', '25', ''),
(12, '2017', 'December', '12', '25', ''),
is there an easier way to do this using mysql/php

You need to UNPIVOT your data. MySQL doesn't have a built in function to do that so you'll need to use multiple queries.
INSERT INTO `monthlydata` (`id`, `year`, `monthName`, `stat_id`, `cat_id`, `data`) VALUES
SELECT id, year, 'January', stat_id, cat_id, January
FROM monthly WHERE monthName = 'January'
UNION ALL
SELECT id, year, 'February', stat_id, cat_id, February
FROM monthly WHERE monthName = 'February'
UNION ALL
SELECT id, year, 'March', stat_id, cat_id, March
FROM monthly WHERE monthName = 'March'
.....
ID column here might cause issues. Depending on how you have defined it. If it is auto generated then you can remove it from the INSERT and let it be auto generated. Since you'll have rows for all months with same ID, you need to handle that scenario.

Related

SQL query to get number of clients with last statement equal connected

I need to make a SQL query
table 'records' structure:
contact_id(integer),
client_id(integer),
worker_id(integer),
statement_status(varchar),
contact_ts(timestamp)
It has to show the following:
current date
number of clients which last statement_status was 'interested'
number of clients which last statement_status was 'not_interested' and previus status was 'not_present'
Could somebody help?
sample data:
contact_id client_id contact_ts worker_id statement_status
'1', '181', '2017-09-24 03:38:31.000000', '107', 'voicemail'
'2', '72', '2017-09-23 09:32:38.000000', '10', 'not_interested'
'3', '277', '2017-09-22 07:06:16.000000', '119', 'interested'
'4', '36', '2017-09-21 04:39:57.000000', '118', 'not_present'
'5', '33', '2017-09-20 04:12:12.000000', '161', 'voicemail'
'6', '244', '2017-09-19 02:26:30.000000', '13', 'not_interested'
'7', '346', '2017-09-18 02:30:35.000000', '255', 'interested'
'8', '128', '2017-09-17 06:20:13.000000', '52', 'not_present'
'9', '33', '2017-09-16 08:58:02.000000', '188', 'not_present'
'10', '352', '2017-09-15 08:18:40.000000', '324', 'not_interested'
'11', '334', '2017-09-14 04:27:40.000000', '373', 'interested'
'12', '2', '2017-09-13 08:44:40.000000', '40', 'not_present'
'13', '33', '2017-09-12 03:46:16.000000', '252', 'voicemail'
'14', '366', '2017-09-11 04:31:22.000000', '78', 'not_interested'
'15', '184', '2017-09-10 06:08:01.000000', '289', 'interested'
'16', '184', '2017-09-09 05:45:56.000000', '124', 'not_present'
'17', '102', '2017-09-08 07:09:30.000000', '215', 'voicemail'
'18', '140', '2017-09-07 08:09:18.000000', '196', 'not_interested'
'19', '315', '2017-09-06 05:13:40.000000', '242', 'interested'
'20', '268', '2017-09-05 07:41:40.000000', '351', 'not_present'
'21', '89', '2017-09-04 05:32:05.000000', '232', 'voicemail'
desired output:
Time, interested, not-interested
2017-09-10 06:08:01, 5, 5
I tried something with sub queries, but it obviously doesn't work:
SELECT
GETDATE()
,(select count(*)
from record a
where (select statement_status
from record
where client_id == a.client_id
order by a.contact_ts
limit 1) == "interested"
group by a.contact_id)
,(select count(*)
from record a
where (select (select statement_status
from record
where client_id == a.client_id
order by a.contact_ts
limit 2) order by a.contact_ts desc limit 1) == "interested"
and
(select statement_status
from record
where client_id == a.client_id
order by a.contact_ts
limit 1) == "interested"
group by a.contact_id)
from record b;
How should I use the inner selects?
I must write a poem, because most of my post is a code.
So maybe something from "Dead man"?
“Don't let the sun burn a hole in your ass, William Blake. Rise now, and drive your cart and plough over the bones of the dead!”
;)
Try something like this:
WITH status AS (
SELECT DISTINCT client_id,
first_value(statement_status) OVER w1 AS last_status,
nth_value(statement_status, 2) OVER w1 AS prev_status
FROM records
WINDOW w1 AS (PARTITION BY client_id ORDER BY contact_ts DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
)
SELECT CURRENT_DATE(),
SUM(last_status = 'interested') AS interesed,
SUM(last_status = 'not_interested' AND prev_status = 'not_present') AS not_interested
FROM status

Why does this sql RANK OVER() command give me all Rank 1

In the following query, I'm attempting to rank rows by the highest computedWorstDerogLevel and then lowest RecentDerogMonths, but it's ranking them all as 1.
SELECT
strTransID,
strRateCode as PreviousHighRateCode,
strRateStatusCode as TradelineRateCode,
dateReported,
datePreviousHigh,
CASE
WHEN
strRateStatusCode IN ('2', '3', '4', '5', '7', '8', '9', 'F', 'G', 'H', 'M', 'Z', '#', '$')
AND
(
strRateStatusCode IN ('F', 'G', 'H', 'M', 'Z', '#', '$')
OR strRateStatusCode >= strRateCode
)
THEN
strRateStatusCode
ELSE
strRateCode
END AS WorstDerogLevel
,
DateDiff(month,
CASE
WHEN
(
dateReported >= datePreviousHigh
OR datePreviousHigh is null
)
AND strRateStatusCode IN ('2', '3', '4', '5', '7', '8', '9', 'F', 'G', 'H', 'M', 'Z', '#', '$')
AND
(
strRateStatusCode IN ('F', 'G', 'H', 'M', 'Z', '#', '$')
OR strRateStatusCode >= strRateCode
)
THEN
dateReported
ELSE
datePreviousHigh
END,
dateTransDate) as RecentDerogMonths
,
RANK() OVER
(
PARTITION BY
DateDiff(month,
CASE
WHEN
(
dateReported >= datePreviousHigh
OR datePreviousHigh is null
)
AND strRateStatusCode IN ('2', '3', '4', '5', '7', '8', '9', 'F', 'G', 'H', 'M', 'Z', '#', '$')
AND
(
strRateStatusCode IN ('F', 'G', 'H', 'M', 'Z', '#', '$')
OR strRateStatusCode >= CstrRateCode
)
THEN
dateReported
ELSE
datePreviousHigh
END,
dateTransDate)
ORDER BY
CASE
WHEN
strRateStatusCode IN ('2', '3', '4', '5', '7', '8', '9', 'F', 'G', 'H', 'M', 'Z', '#', '$')
AND
(
strRateStatusCode IN ('F', 'G', 'H', 'M', 'Z', '#', '$')
OR strRateStatusCode >= strRateCode
)
THEN
strRateStatusCode
ELSE
strRateCode
END desc,
DateDiff(month,
CASE
WHEN
(
dateReported >= datePreviousHigh
OR datePreviousHigh is null
)
AND strRateStatusCode IN ('2', '3', '4', '5', '7', '8', '9', 'F', 'G', 'H', 'M', 'Z', '#', '$')
AND
(
strRateStatusCode IN ('F', 'G', 'H', 'M', 'Z', '#', '$')
OR strRateStatusCode >= strRateCode
)
THEN
dateReported
ELSE
datePreviousHigh
END,
dateTransDate) asc
) AS Rank
FROM
[Customers]
WHERE strTransID = '279579407'
Here is my result set
Just debug your query by adding two column PartitionField and OrderField with the expression you have on the RANK() OVER
There two cases where every row have RANK = 1
each rows belong to a different partition, so you have size 1 partitions.
all the rows in the partition have the same value, all are rank 1.

Result in row mysql

i have this data on my table
ID DATE TIME STATE SIP
'1', '2017-10-31', '10:24:51', 'R', '237'
'2', '2017-10-31', '10:41:35', 'U', '237'
'3', '2017-10-31', '10:45:32', 'R', '611'
'4', '2017-10-31', '10:45:40', 'U', '611'
'5', '2017-10-31', '10:46:03', 'R', '258'
'6', '2017-10-31', '11:10:51', 'R', '237'
'7', '2017-10-31', '11:17:03', 'R', '611'
'8', '2017-10-31', '11:32:21', 'U', '611'
'9', '2017-10-31', '11:32:37', 'R', '611'
'10', '2017-10-31', '11:53:06', 'R', '258'
'11', '2017-10-31', '12:01:24', 'R', '252'
'12', '2017-10-31', '12:01:36', 'U', '611'
'13', '2017-10-31', '12:01:43', 'R', '617'
'14', '2017-10-31', '12:01:57', 'U', '258'
'15', '2017-10-31', '12:02:24', 'R', '611'
'16', '2017-10-31', '12:02:39', 'R', '258'
'17', '2017-10-31', '12:29:09', 'U', '611'
'18', '2017-10-31', '12:31:30', 'R', '611'
'19', '2017-10-31', '12:55:58', 'R', '237'
'20', '2017-10-31', '12:59:58', 'U', '611'
'21', '2017-10-31', '13:00:16', 'U', '252'
'22', '2017-10-31', '13:00:17', 'U', '237'
This is my recordset,
i would like to calculate how much time the SIP stay register on system.
state R=Registered U=Unregistered but i don't have a regular login and logout
i would like have:
SIP DATE LOGIN LOGOUT
237 2017-10-31 10:24:51 10:41:35
611 2017-10-31 10:45:32 10:45:40
258 2017-10-31 10:46:03 12:01:57
and calculate the total time stay connected
thank you for the time you have dedicated to me
Following solution will work:
select sip,
min(time) as login,
(case when max(time)=min(time) then 'lost'
else max(time)
end
) as logout
from SIPregistration
where time between '10:00:00' and '11:00:00'
and state in('R','U')
group by sip;
You can put the text after THEN with whatever you want to display in case of there is no logout by an sip.
Click here for the DEMO
Looks like you want result overlap your range.
First create the SIP ranges:
SELECT s1.sip,
s1.time as login
s2.time as logout
COALESCE(s2.time, NOW()) as not_null_logout
FROM SIPregistration s1
LEFT JOIN SIPregistration s2 -- I assume there are some SIP without logout
ON s1.soip =s2.sip
AND s1.time > s2.time
-- WHERE s2.time IS NOT NULL -- optional
Now found out if the SIP range overlap with your time window
SELECT s1.sip,
s1.time as login
s2.time as logout
FROM SIPregistration s1
LEFT JOIN SIPregistration s2
ON s1.soip =s2.sip
AND s1.time > s2.time
WHERE s1.time < '11:00:00'
AND s2.time > '10:00:00' -- sip without logout wont show becasue s2.time is NULL.
The final query should look like this:
SELECT sip, `date`
min(time) as login,
IF(max(time) != min(time), max(time), 'Not Logged Yet In This Range') as logout,
IF(max(time) != min(time), TIMEDIFF(max(time), min(time)), 'Not Logged Yet In This Range') as connected
From words
WHERE time between '10:00:00' and '11:00:00'
GROUP BY sip;
You can change the text in if clause with what you want.
We just check if the max of time equal the min in the specific range you have in where clause
IF(expression ,expr_true, expr_false);
DATEDIFF(interval, date1, date2); the interval not required

Rank by calculated variable in MySQL

The following table is for practice only. I will use the code on a much larger table.
SELECT *
FROM price_practice;
gives
id company dt price
'16', 'Amex', '2015-07-01', '5.00'
'17', 'Amex', '2015-07-02', '5.10'
'18', 'Amex', '2015-07-03', '5.00'
'19', 'Amex', '2015-07-06', '5.88'
'20', 'Amex', '2015-07-07', '4.21'
'21', 'Citi', '2015-07-01', '1.00'
'22', 'Citi', '2015-07-02', '1.10'
'23', 'Citi', '2015-07-03', '1.00'
'24', 'Citi', '2015-07-06', '0.88'
'25', 'Citi', '2015-07-07', '1.01'
'26', 'Amex', '2015-07-08', '5.23'
'27', 'Amex', '2015-07-09', '5.35'
'28', 'Amex', '2015-07-10', '5.55'
'29', 'Amex', '2015-07-13', '5.88'
'30', 'Amex', '2015-07-14', '6.01'
'31', 'Citi', '2015-07-08', '0.95'
'32', 'Citi', '2015-07-09', '0.83'
'33', 'Citi', '2015-07-10', '0.79'
'34', 'Citi', '2015-07-13', '0.72'
'35', 'Citi', '2015-07-14', '0.59'
The following snippet calculates the percentage change in price from one date to the next.
SELECT x.id, x.company, x.dt, x.price, (x.price - y.price)/y.price AS 'Change'
FROM
(
SELECT a.id AS aid, MAX(b.id) AS aPrevid
FROM price_practice a
INNER JOIN price_practice b
WHERE a.id > b.id
AND a.company = b.company
GROUP BY a.id
) Sub1
INNER JOIN price_practice x ON Sub1.aid = x.id
INNER JOIN price_practice y ON Sub1.aPrevid = y.id
ORDER BY x.id DESC
As intended, it returns
id company dt price Change
'35', 'Citi', '2015-07-14', '0.59', '-0.180556'
'34', 'Citi', '2015-07-13', '0.72', '-0.088608'
'33', 'Citi', '2015-07-10', '0.79', '-0.048193'
'32', 'Citi', '2015-07-09', '0.83', '-0.126316'
'31', 'Citi', '2015-07-08', '0.95', '-0.059406'
'30', 'Amex', '2015-07-14', '6.01', '0.022109'
'29', 'Amex', '2015-07-13', '5.88', '0.059459'
'28', 'Amex', '2015-07-10', '5.55', '0.037383'
'27', 'Amex', '2015-07-09', '5.35', '0.022945'
'26', 'Amex', '2015-07-08', '5.23', '0.242280'
'25', 'Citi', '2015-07-07', '1.01', '0.147727'
'24', 'Citi', '2015-07-06', '0.88', '-0.120000'
'23', 'Citi', '2015-07-03', '1.00', '-0.090909'
'22', 'Citi', '2015-07-02', '1.10', '0.100000'
'20', 'Amex', '2015-07-07', '4.21', '-0.284014'
'19', 'Amex', '2015-07-06', '5.88', '0.176000'
'18', 'Amex', '2015-07-03', '5.00', '-0.019608'
'17', 'Amex', '2015-07-02', '5.10', '0.020000'
The following snippet does something entirely different: it ranks observations by price for every company seperately.
SELECT (
CASE company
WHEN #curType
THEN #curRow := #curRow + 1
ELSE #curRow := 1 AND #curType := company END
) + 1 AS rank,
id,
company,
dt,
price
FROM price_practice,
(SELECT #curRow := 0, #curType := '') r
ORDER BY company DESC, price DESC;
As intended, it returns
rank id company dt price
'1', '22', 'Citi', '2015-07-02', '1.10'
'2', '25', 'Citi', '2015-07-07', '1.01'
'3', '23', 'Citi', '2015-07-03', '1.00'
'4', '21', 'Citi', '2015-07-01', '1.00'
'5', '31', 'Citi', '2015-07-08', '0.95'
'6', '24', 'Citi', '2015-07-06', '0.88'
'7', '32', 'Citi', '2015-07-09', '0.83'
'8', '33', 'Citi', '2015-07-10', '0.79'
'9', '34', 'Citi', '2015-07-13', '0.72'
'10', '35', 'Citi', '2015-07-14', '0.59'
'1', '30', 'Amex', '2015-07-14', '6.01'
'2', '19', 'Amex', '2015-07-06', '5.88'
'3', '29', 'Amex', '2015-07-13', '5.88'
'4', '28', 'Amex', '2015-07-10', '5.55'
'5', '27', 'Amex', '2015-07-09', '5.35'
'6', '26', 'Amex', '2015-07-08', '5.23'
'7', '17', 'Amex', '2015-07-02', '5.10'
'8', '18', 'Amex', '2015-07-03', '5.00'
'9', '16', 'Amex', '2015-07-01', '5.00'
'10', '20', 'Amex', '2015-07-07', '4.21'
The question is:
How do I rank observations by percentage change?
I imagine you can save the percentage change data in a new column and then rank it, but I suspect this is not the best method. I will do many similar calculations (eg weekly % change, variance etc), and I have around 3,000,000 observations, so the table would grow big quickly. If this is the only way to do it, I will, but I think combining the two snippets above to calculate percentage change and rank in one go would be better. Or what do you think?
As I'm sure you can tell from my question, I'm a beginner at MySQL. Any advise on how to proceed is appreciated!

Row Number with a mySQL Join

I have the following Query that works correctly:
SELECT #row_num := IF(#prev_value=concat(o.CITY, o.keyword_text) ,#row_num+1, 1) AS POSITION
,o.idBUSINESS
,o.KEYWORD_TEXT
,o.CITY
, o.BID_AMOUNT
,#prev_value := concat(o.CITY, o.keyword_text)
FROM (SELECT #row_num := 1) x,
(SELECT #prev_value := '') y,
(SELECT #prev_value1 := '') z,
elevated_business_queue o
ORDER BY o.CITY , o.KEYWORD_TEXT, o.BID_AMOUNT DESC
This query returns:
# POSITION, idBUSINESS, KEYWORD_TEXT, CITY, BID_AMOUNT, #prev_value := concat(o.CITY, o.keyword_text)
'1', '7', '2', 'New Jersey', '3.50', 'New Jersey2'
'2', '5', '2', 'New Jersey', '2.50', 'New Jersey2'
'3', '1', '2', 'New Jersey', '2.50', 'New Jersey2'
'1', '5', '1', 'New York', '2.50', 'New York1'
'2', '7', '1', 'New York', '2.30', 'New York1'
'3', '1', '1', 'New York', '1.50', 'New York1'
'1', '9', '2', 'New York', '7.50', 'New York2'
'2', '1', '2', 'New York', '4.50', 'New York2'
'3', '5', '2', 'New York', '3.50', 'New York2'
'4', '7', '2', 'New York', '2.50', 'New York2'
This data is correct. Now, I want to join the elevated_business_queue with another table. I am doing it as follows:
SELECT #row_num := IF(#prev_value=concat(o.CITY, o.keyword_text) ,#row_num+1, 1) AS POSITION
,o.idBUSINESS
,o.KEYWORD_TEXT
,o.CITY
, o.BID_AMOUNT
,#prev_value := concat(o.CITY, o.keyword_text)
FROM (SELECT #row_num := 1) x,
(SELECT #prev_value := '') y,
(SELECT #prev_value1 := '') z,
elevated_business_queue o
INNER JOIN funds_balance fb ON fb.idBUSINESS = o.idBUSINESS
WHERE fb.PREMIUM_POSITIONS_CREDIT >= (o.BID_AMOUNT + ROUND((12.36/100)*o.BID_AMOUNT, 2))
ORDER BY o.CITY , o.KEYWORD_TEXT, o.BID_AMOUNT DESC
However, when I join, my POSITION gets messed up. I am now getting:
# POSITION, idBUSINESS, KEYWORD_TEXT, CITY, BID_AMOUNT, #prev_value := concat(o.CITY, o.keyword_text)
'2', '7', '2', 'New Jersey', '3.50', 'New Jersey2'
'1', '1', '2', 'New Jersey', '2.50', 'New Jersey2'
'1', '5', '2', 'New Jersey', '2.50', 'New Jersey2'
'2', '5', '1', 'New York', '2.50', 'New York1'
'3', '7', '1', 'New York', '2.30', 'New York1'
'1', '1', '1', 'New York', '1.50', 'New York1'
'1', '1', '2', 'New York', '4.50', 'New York2'
'2', '5', '2', 'New York', '3.50', 'New York2'
'1', '7', '2', 'New York', '2.50', 'New York2'
Can someone please help.
--------------UPDATE----------------------
I tried with the following query but the POSITION is still off:
SELECT T1.*,fb.* FROM
(SELECT #row_num := IF(#prev_value=concat(o.CITY, o.keyword_text) ,#row_num+1, 1) AS POSITION
,o.idBUSINESS
,o.KEYWORD_TEXT
,o.CITY
,o.BID_AMOUNT
,#prev_value := concat(o.CITY, o.keyword_text)
FROM (SELECT #row_num := 1) x,
(SELECT #prev_value := '') y,
(SELECT #prev_value1 := '') z,
elevated_business_queue o
ORDER BY o.CITY , o.KEYWORD_TEXT, o.BID_AMOUNT DESC)T1
INNER JOIN funds_balance fb ON fb.idBUSINESS = T1.idBUSINESS
WHERE fb.PREMIUM_POSITIONS_CREDIT >= (T1.BID_AMOUNT + ROUND((12.36/100)*T1.BID_AMOUNT, 2))
ORDER BY T1.CITY ,T1.KEYWORD_TEXT, T1.BID_AMOUNT DESC;
I now get the following result-set:
# POSITION, idBUSINESS, KEYWORD_TEXT, CITY, BID_AMOUNT, #prev_value := concat(o.CITY, o.keyword_text), idBUSINESS, PREMIUM_POSITIONS_CREDIT
'1', '7', '2', 'New Jersey', '3.50', 'New Jersey2', '7', '17.30'
'3', '1', '2', 'New Jersey', '2.50', 'New Jersey2', '1', '12.31'
'2', '5', '2', 'New Jersey', '2.50', 'New Jersey2', '5', '15.19'
'1', '5', '1', 'New York', '2.50', 'New York1', '5', '15.19'
'2', '7', '1', 'New York', '2.30', 'New York1', '7', '17.30'
'3', '1', '1', 'New York', '1.50', 'New York1', '1', '12.31'
'2', '1', '2', 'New York', '4.50', 'New York2', '1', '12.31'
'3', '5', '2', 'New York', '3.50', 'New York2', '5', '15.19'
'4', '7', '2', 'New York', '2.50', 'New York2', '7', '17.30'
There is no POSITION=1 for KEYWORD_TEXT=2 in New York. The following row should have a position of 1:
'2', '1', '2', 'New York', '4.50', 'New York2', '1', '12.31'
Perhaps you could replace your row_num calculation with a window function like this:
row_number() over(partition by o.CITY , o.KEYWORD_TEXT order by o.BID_AMOUNT DESC)
Here you're misplacing the order by columns. If you require ordering by KEYWORD_TEXT then by BID_AMOUNT and CITY (if yes then)
Change order by as:
ORDER BY KEYWORD_TEXT, BID_AMOUNT CITY