How can I get this pivot kind of output in sql - mysql

Consider my source table as given below i.e customer.
How can i get the required output as shown using sql (oracle or mysql)
customer :
customer id Purchase_id cashback
123 abc111 5
123 abc112 5
123 abc113 2
345 abc311 0
345 abc312 2
678 abc611 4
678 abc612 3
678 abc613 5
Output Needed:
ID purchare_id_1 purchare_id_2 purchare_id_3 cashback_1 cashback_2 cashback_3
123 abc111 abc112 abc113 5 5 2
345 abc311 abc312 0 2
678 abc611 abc612 abc613 4 3 5
DML and DDL:
create table cust_table (
customer_id int, Purchase_id varchar(100), cashback int
);
insert into cust_table values
(123 , 'abc111' , 5),
(123 , 'abc112' , 5),
(123 , 'abc113' , 2),
( 345 , 'abc311' , 0),
(345 , 'abc312' , 2),
(678 , 'abc611' , 4),
(678 , 'abc612' , 3),
(678 , 'abc613' , 5);
commit;
PS:
Data might be not static, it can change.

WITH
cte AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY Purchase_id) rn
FROM cust_table )
SELECT customer_id,
MAX(CASE WHEN rn=1 THEN Purchase_id END) purchase_1,
MAX(CASE WHEN rn=2 THEN Purchase_id END) purchase_2,
MAX(CASE WHEN rn=3 THEN Purchase_id END) purchase_3,
MAX(CASE WHEN rn=1 THEN cashback END) cashback_1,
MAX(CASE WHEN rn=2 THEN cashback END) cashback_2,
MAX(CASE WHEN rn=3 THEN cashback END) cashback_3
FROM cte
GROUP BY customer_id
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ec2de721d7089a82a5f7ae669ce2d19e

In MySQL: You can use concat along with group by to see partial result like as below
select customer_id, group_concat(`Purchase_id` separator ',') as `Purchase_idX`,
group_concat(`cashback` separator ',') as `cashbackX` from cust_table
group by customer_id;
If you want the exact result run the below query:
select
SUBSTRING_INDEX(AA, ',', 1) as purchare_id_1,
CASE
WHEN LOCATE(',', AA, 2) = 0 THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(AA, ',', 2), ',', -1)
END AS purchare_id_2,
CASE
WHEN LOCATE(',', AA, LOCATE(',', AA, 1)+1) = 0 THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(AA, ',', 3), ',', -1)
END AS cashback_1,
SUBSTRING_INDEX(BB, ',', 1) as purchare_id_1,
CASE
WHEN LOCATE(',', BB, 2) = 0 THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(BB, ',', 2), ',', -1)
END AS cashback_2,
CASE
WHEN LOCATE(',', BB, LOCATE(',', BB, 1)+1) = 0 THEN NULL
ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(BB, ',', 3), ',', -1)
END AS cashback_3
from
( select customer_id, group_concat(`Purchase_id` separator ',') as AA, group_concat(`cashback` separator ',') as BB,
group_concat(`cashback` separator ',') as `cashbackX` from cust_table
group by customer_id) as TB

Related

How to parse <first_value> aggregate in a group by statement [SNOWFLAKE] SQL

How do you rewrite this code correctly in Snowflake?
select account_code, date,
sum(box_revenue_recognition_amount) as box_revenue_recognition_amount
, sum(case when box_flg = 1 then box_sku_quantity end) as box_sku_quantity
, sum(box_revenue_recognition_refund_amount) as box_revenue_recognition_refund_amount
, sum(box_discount_amount) as box_discount_amount
, sum(box_shipping_amount) as box_shipping_amount
, sum(box_cogs) as box_cogs
, max(invoice_number) as invoice_number
, max(order_number) as order_number
, min(box_refund_date) as box_refund_date
, first (case when order_season_rank = 1 then box_type end) as box_type
, first (case when order_season_rank = 1 then box_order_season end) as box_order_season
, first (case when order_season_rank = 1 then box_product_name end) as box_product_name
, first (case when order_season_rank = 1 then box_coupon_code end) as box_coupon_code
, first (case when order_season_rank = 1 then revenue_recognition_reason end) as revenue_recognition_reason
from dedupe_sub_user_day
group by account_code, date
I have tried to apply window rule has explained in first_value Snowflake documentation to no avail with the SQLCompilation Error: ... is not a valid group by expression
select account_code, date,
first_value(case when order_season_rank = 1 then box_type end) over (order by box_type ) as box_type
first_value(case when order_season_rank = 1 then box_order_season end) over (order by box_order_season ) as box_order_season,
first_value(case when order_season_rank = 1 then box_product_name end) over (order by box_product_name ) as box_product_name,
first_value(case when order_season_rank = 1 then box_coupon_code end) over (order by box_coupon_code ) as box_coupon_code,
first_value(case when order_season_rank = 1 then revenue_recognition_reason end) over (order by revenue_recognition_reason ) as revenue_recognition_reason
, sum(box_revenue_recognition_amount) as box_revenue_recognition_amount
, sum(case when box_flg = 1 then box_sku_quantity end) as box_sku_quantity
, sum(box_revenue_recognition_refund_amount) as box_revenue_recognition_refund_amount
, sum(box_discount_amount) as box_discount_amount
, sum(box_shipping_amount) as box_shipping_amount
, sum(box_cogs) as box_cogs
, max(invoice_number) as invoice_number
, max(order_number) as order_number
, min(box_refund_date) as box_refund_date
from dedupe_sub_user_day
group by 1,2
First_value is not an aggregate function. But an window function, thus you get an error when you use it in relation to a GROUP BY. If you want to use it with a group up put an ANY_VALUE around it.
here is some data I will use below in a CTE:
with data(id, seq, val) as (
select * from values
(1, 1, 10),
(1, 2, 11),
(1, 3, 12),
(1, 4, 13),
(2, 1, 20),
(2, 2, 21),
(2, 3, 22)
)
So to show FIRST_VALUE is a window function we can just use it
select *
,first_value(val)over(partition by id order by seq) as first_val
from data
ID
SEQ
VAL
FIRST_VAL
1
1
10
10
1
2
11
10
1
3
12
10
1
4
13
10
2
1
20
20
2
2
21
20
2
3
22
20
So if we GROUP BY id, to avoid an error we have to wrap the FIRST_VALUE by an aggregate value, as given the are all equal, ANY_VALUE is a good pick, and it seems it needs to be in another layer of SQL:
select id
,count(*) as count
,any_value(first_val) as first_val
from (
select *
,first_value(val)over(partition by id order by seq) as first_val
from data
)
group by 1
order by 1;
ID |COUNT |FIRST_VAL
1 |4 |10
2 |3 |20
now MAX can be fun to use where used in relation to ROW_NUMBER() to pick the best value:
select id
,count(*) as count
,max(first_val) as first_val
from (
select *
,row_number() over (partition by id order by seq) as rn
,iff(rn=1, val, null) as first_val
from data
)
group by 1
order by 1;
but this is almost more complex than the ANY_VALUE solution, but I feel the performance would be better, but if they have the same magnitude of performance, I would always choose readable to you and your team, over a smaller performance difference.
With the way you've written your case statement, it leads me to believe that there is only one row with order_season_rank = 1 when grouping by account_code and date.
If that is true, then you can use several of Snowflake's aggregate functions and you will get what you want. Rather than trying to get the first value, you could use min, max, any_value, mode (or really any aggregate function that will ignore nulls) to return the only non-null value in the aggregation.
first() this link suggests first is only supported by MS ACCESS however you've tagged the question with MYSQL, Snowflake. Could you confirm the DBMS's you are using?
by moving the first_value() function outside the aggregation it seems to work fine

SQL group by school name

school_name
class
medium
total
srk
1
english
13
srk
2
english
14
srk
3
english
15
srk
1
french
16
srk
2
french
16
srk
3
french
18
vrk
1
english
17
vrk
1
french
18
I want that output by
school_name
class1eng
class1french
class2eng
class2french
class3english
class3french
[output needed][ otput required
output
You’re looking for multiple select statements along with appropriate cases to satisfy.
This should work for you
Select
school_name,
Sum(Case when (class=1 and medium=‘English’) then total else 0 end) as class1english,
Sum(Case when (class=1 and medium=‘French’) then total else 0 end) as class1french,
Sum(Case when (class=2 and medium=‘English’) then total else 0 end) as class2english,
Sum(Case when (class=2 and medium=‘French’) then total else 0 end) as class2french,
Sum(Case when (class=3 and medium=‘English’) then total else 0 end) as class3english,
Sum(Case when (class=3 and medium=‘French’) then total else 0 end) as class3french
From
table_name
Group by
school_name
Seems to be a simple ask, assumed you also want to order your results. Please check below query if that helps
SELECT school_name, class, medium, SUM(total) AS Total
FROM <Table Name>
GROUP BY school_name, class, medium
This solution is for general purpose, complex, but functional.
I've made it for myself as exercise and challenge.
/* --------------- TABLE --------------- */
CREATE TABLE schools_tab
(school VARCHAR(9), class INT, subj VARCHAR(9), total INT);
INSERT INTO schools_tab VALUES
('srk', 1, 'english', 13),
('srk', 2, 'english', 14),
('srk', 3, 'english', 15),
('srk', 1, 'french', 16),
('srk', 2, 'french', 16),
('srk', 3, 'french', 18),
('vrk', 1, 'english', 17),
('vrk', 1, 'french', 18);
/* -------------- DYNAMIC QUERY --------------- */
SET #sql=NULL;
WITH cte AS (
SELECT school, class, subj, ROW_NUMBER() OVER (PARTITION BY school) AS idx, DENSE_RANK() OVER (ORDER BY school) AS ids
FROM (SELECT DISTINCT school FROM schools_tab) A LEFT JOIN (SELECT DISTINCT class, subj FROM schools_tab) B ON (1=1)
), cte2 AS (
SELECT A.ids, A.idx, A.school, A.class, A.subj, COALESCE(B.total, 0) AS total
FROM cte A LEFT JOIN schools_tab B ON (A.school=B.school AND A.class=B.class AND A.subj=B.subj)
), cte3 AS (
SELECT DISTINCT class, subj
FROM schools_tab
ORDER BY class, subject
)
SELECT CONCAT('WITH RECURSIVE cte AS (
SELECT school, class, subj, ROW_NUMBER() OVER (PARTITION BY school) AS idx, DENSE_RANK() OVER (ORDER BY school) AS ids
FROM (SELECT DISTINCT school FROM schools_tab) A LEFT JOIN (SELECT DISTINCT class, subj FROM schools_tab) B ON (1=1)
), cte2 AS (
SELECT A.ids, A.idx, A.school, A.class, A.subj, COALESCE(B.total, 0) AS total
FROM cte A LEFT JOIN schools_tab B ON (A.school=B.school AND A.class=B.class AND A.subj=B.subj)
), ctx AS ('
'SELECT (SELECT MAX(ids) FROM cte2) AS n,',
GROUP_CONCAT(DISTINCT CONCAT( '(SELECT total FROM cte2 WHERE idx=',idx,' AND ids=n) AS class',class,subj ) ORDER BY class, subj),
' UNION ALL SELECT n-1 AS n,',
GROUP_CONCAT(DISTINCT CONCAT( '(SELECT total FROM cte2 WHERE idx=',idx,' AND ids=n) AS class',class,subj ) ORDER BY class, subj),
' FROM ctx WHERE n>0',
') SELECT DISTINCT SUBSTRING_INDEX(SUBSTRING_INDEX(''srk,vrk'', '','', n+1), '','', -1) AS school,',
GROUP_CONCAT(DISTINCT CONCAT('class',class,subj)),
' FROM ctx ORDER BY school'
) INTO #sql
FROM cte2;
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;

Insert new row for binary systems with SQL

I have the following table
(cl1 , cl2)
---- ----
(a , 1)
(a , 2)
(b , 2)
(c , 1)
(c , 2)
each a , b ,c can take two values (1 or 2 or both).
My question is :
How to insert a new row (with 0 on cl2) for all the cl1 that have only 1 or 2 and NOT the both in the example. I would like to insert the following row :
----
(b , 0)
----
I'm sure there are better ways, but here is one way to do it using group by and a having clause to enforce your rules (I'm assuming Oracle syntax):
insert into tbl (cl1, cl2)
(select cl1, 0
from tbl
group by cl1
having count(case when cl2 in (1, 2) then 'X' end) != 0 -- contains 1 or 2
and (count(case when cl2 = 1 then 'X' end) = 0 -- but not both
or count(case when cl2 = 2 then 'X' end) = 0)
)
EDIT
A much simpler way:
insert into tbl (cl1, cl2)
(select cl1, 0
from tbl
where cl2 in (1, 2)
group by cl1
having count(distinct cl2) = 1
)
I am assuming that the BD is Oracle. Hope the below snippet helps.
SELECT B.CL1,
0
FROM
(SELECT A.CL1,
CASE
WHEN WMSYS.WM_CONCAT(A.CL2) LIKE '%1%'
AND WMSYS.WM_CONCAT(A.CL2) LIKE '%2%'
THEN 'both'
ELSE 'one'
END rnk
FROM
(SELECT 'a' cl1,1 cl2 FROM dual
UNION ALL
SELECT 'a' cl1,2 cl2 FROM dual
UNION ALL
SELECT 'b' cl1,2 cl2 FROM dual
UNION ALL
SELECT 'c' cl1,1 cl2 FROM dual
UNION ALL
SELECT 'c' cl1,2 cl2 FROM dual
)A
GROUP BY A.CL1
)B
WHERE B.rnk = 'one';
CREATE TABLE TestTable (cl1 VARCHAR(2), cl2 INT);
INSERT INTO TestTable (cl1, cl2) VALUES ('a', 1), ('a', 2), ('b', 1), ('c', 1), ('c', 2);
INSERT INTO TestTable (cl1, cl2)
SELECT cl1, 0
FROM TestTable
WHERE cl1 NOT IN (
SELECT cl1
FROM TestTable
WHERE cl2 IN (1, 2)
GROUP BY cl1
HAVING COUNT(DISTINCT cl2) = 2
);
MySQL Demo: http://rextester.com/XWHGF50183
The below block returns the cl1 those have the cl2 is 1 and 2. Based on the result using NOT IN you can achieve the result.
SELECT cl1
FROM TestTable
WHERE cl2 IN (1, 2)
GROUP BY cl1
HAVING COUNT(DISTINCT cl2) = 2
Help from this answer
Here you go:
insert into [YOUR TABLE NAME]
select cl1,0 from [YOUR TABLE NAME]
group by cl1 having count(distinct cl2)<> 2
;

MySql transpose multiple rows into column - the most optimized way for large data?

i want to transpose multiple rows to column.
This my table with data (about 20 mil.rows)
PHONE SERVICE
0000000 service1
0000000 service2
0000000 service3
1111111 service1
1111111 service4
2222222 service5
and I would like to get the following output:
PHONE SC1 SC2 SC3 SC4 SC5
0000000 service1 service2 service3 NULL NULL
1111111 service1 service4 NULL NULL NULL
2222222 service5 NULL NULL NULL NULL
etc..
Anybody know the fastest to do this (for about 20mil records)? Thanks very much!
This should work well, make sure you have an index on phone.
SELECT phone,
SUBSTRING_INDEX(services, ',', 1) SC1,
if(service_count >= 2, SUBSTRING_INDEX(SUBSTRING_INDEX(services, ',', 2), ',', -1), NULL) SC2,
if(service_count >= 3, SUBSTRING_INDEX(SUBSTRING_INDEX(services, ',', 3), ',', -1), NULL) SC3,
if(service_count >= 4, SUBSTRING_INDEX(SUBSTRING_INDEX(services, ',', 4), ',', -1), NULL) SC4,
if(service_count >= 5, SUBSTRING_INDEX(SUBSTRING_INDEX(services, ',', 5), ',', -1), NULL) SC5
FROM (SELECT phone, GROUP_CONCAT(service) AS services, COUNT(*) as service_count
FROM phones
GROUP BY phone) AS x
DEMO
You can use variables to number the services within each group and use conditional aggregation to pivot your rows into columns.
select phone,
max(case when rn = 1 then service end) sc1,
max(case when rn = 2 then service end) sc2,
max(case when rn = 3 then service end) sc3,
max(case when rn = 4 then service end) sc4,
max(case when rn = 5 then service end) sc5
from (
select phone,service,
#rowNum := if(#prevPhone = phone,#rowNum+1,1) rn,
#prevPhone := phone
from mytable
cross join (select #prevPhone := null, #rowNum := 1) c
order by phone, service
) t1 group by phone

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