I'm looking for an efficient way to convert rows to columns in the SQL server, I heard that PIVOT is not very fast, and I need to deal with a lot of records.
I tried following on this Efficiently convert rows to columns in sql server but still not solved with my below example
This is my example: (updated)
-----------------------------------------------
| Id | Value | ColumnName | Submission_Id |
-----------------------------------------------
| 1 | John | FirstName | 1 |
| 2 | 2.4 | Amount | 1 |
| 3 | ZH1E4A | PostalCode | 1 |
| 4 | Fork | LastName | 1 |
| 5 | 857685 | AccountNumber | 1 |
| 6 | Donny | FirstName | 2 |
| 7 | 2.7 | Amount | 2 |
| 8 | ZH1E4C | PostalCode | 2 |
| 9 | Yen | LastName | 2 |
| 10 | 857686 | AccountNumber | 2 |
-----------------------------------------------
This is my expected result:
---------------------------------------------------------------------
| FirstName |Amount| PostalCode | LastName | AccountNumber |
---------------------------------------------------------------------
| John | 2.4 | ZH1E4A | Fork | 857685 |
| Donny | 2.7 | ZH1E4C | Yen | 857686 |
---------------------------------------------------------------------
How can I build the result?
You need to group them by their associative key, which you informed:
SELECT
MAX(CASE ColumnName WHEN 'FirstName' THEN Value END) AS FirstName,
MAX(CASE ColumnName WHEN 'Amount' THEN Value END) AS Amount,
MAX(CASE ColumnName WHEN 'PostalCode' THEN Value END) AS PostalCode,
MAX(CASE ColumnName WHEN 'LastName' THEN Value END) AS LastName,
MAX(CASE ColumnName WHEN 'AccountNumber' THEN Value END) AS AccountNumber
FROM table
GROUP BY submission_id
;
GROUP BY enforces that there is single row for each unique submission_id, MAX selects the most descendant value of the expression for that particular group key (it is assumed to be singular so aggregate type should not matter), and finally CASE is filtering the Value by ColumnName.
WITH
indata(Id,Value,ColumnName) AS (
SELECT 1,'John' ,'FirstName'
UNION ALL SELECT 2,'2.4' ,'Amount'
UNION ALL SELECT 3,'ZH1E4A' ,'PostalCode'
UNION ALL SELECT 4,'Fork' ,'LastName'
UNION ALL SELECT 5,'857685' ,'AccountNumber'
UNION ALL SELECT 6,'Donny' ,'FirstName'
UNION ALL SELECT 7,'2.7' ,'Amount'
UNION ALL SELECT 8,'ZH1E4C' ,'PostalCode'
UNION ALL SELECT 9,'Yen' ,'LastName'
UNION ALL SELECT 10,'857686','AccountNumber'
)
,
-- need to get a grouping column, one that
-- changes every time we encounter a 'FirstName
-- add a counter that is at 1 for FirstName
-- otherwise at 0, and build a running sum...
w_session_id AS (
SELECT
SUM(CASE ColumnName WHEN 'FirstName' THEN 1 END)
OVER(ORDER BY id) AS sessid
, *
FROM indata
)
-- now un-pivot manually
SELECT
sessid AS id
, MAX(CASE ColumnName WHEN 'FirstName' THEN value END) AS FirstName
, MAX(CASE ColumnName WHEN 'Amount' THEN value END) AS Amount
, MAX(CASE ColumnName WHEN 'PostalCode' THEN value END) AS PostalCode
, MAX(CASE ColumnName WHEN 'LastName' THEN value END) AS LastName
, MAX(CASE ColumnName WHEN 'AccountNumber' THEN value END) AS AccountNumber
FROM w_session_id
GROUP BY sessid;
-- out id | FirstName | Amount | PostalCode | LastName | AccountNumber
-- out ----+-----------+--------+------------+----------+---------------
-- out 1 | John | 2.4 | ZH1E4A | Fork | 857685
-- out 2 | Donny | 2.7 | ZH1E4C | Yen | 857686
Related
I'm trying to write a SQL query for this report to group by date and also get the count of all unique values. The problem I have is that I do not know how many unique values I will have ahead of time.
Sample Table:
+--------+--------+
| Date | Name |
+--------+--------+
| 1/1/18 | John |
| 1/1/18 | John |
| 1/1/18 | Sylvia |
| 1/2/18 | Sylvia |
+--------+--------+
This is what I tried but it requires me to know that John and Sylvia exist in the table. What is the workaround if there were 50,000 unique names without having to type out all the CASE statement.
SELECT
date,
SUM(CASE WHEN name='John' THEN 1 ELSE 0 END) AS John,
SUM(CASE WHEN name='Sylvia' THEN 1 ELSE 0 END) AS Sylvia
FROM myTable
GROUP BY date;
Expected output:
+--------+------+--------+-----+
| Date | John | Sylvia | ... |
+--------+------+--------+-----+
| 1/1/18 | 2 | 1 | ... |
| 1/2/18 | 0 | 1 | ... |
+--------+------+--------+-----+
A simple
SELECT date, name, count(*) FROM myTable GROUP BY date, name;
should work
You can achieve this using Dynamic PIVOT.
Declare #sql nvarchar(max)
set #sql = 'select *
from (select *,count(name)cnt from #mytable group by name,date)A
PIVOT
( sum(cnt)
FOR name
in ( '+stuff(( select distinct ', '+ name from #mytable group by name for xml path('')),1,1,'')+')
)as PIVOTTABLE'
EXECUTE sp_executesql #sql;
This link may help you MySQL Select Query to generate dynamic column Result
I have the table structure as shown below. The database is MariaDB.
+-----------+----------+--------------+-----------------+
| id_object | name | value_double | value_timestamp |
+-----------+----------+--------------+-----------------+
| 1 | price | 1589 | null |
| 1 | payment | 1590 | null |
| 1 | date | null | 2012-04-17 |
| 2 | price | 1589 | null |
| 2 | payment | 1590 | null |
| 2 | date | null | 2012-04-17 |
| 3 | price | 1589 | null |
| 3 | payment | 1590 | null |
| 3 | date | null | 2012-09-25 |
| ... | ... | ... | .. |
+-----------+----------+--------------+-----------------+
1) I need to get the duplicates by three entries: price & payment & date;
For example: the record with id_object=2 is duplicate because price, payment and date are the same as values of the record with id_object=1. Record with id_object = 3 is not the duplicate because the date is different (2012-09-25 != 2012-04-17)
2) I should remove the duplicates except one copy of them.
I thought to do three select operations and join each select on id_object. I can get the duplicates by one entry (price | payment | date). I faced the problem doing the joins
SELECT `id_object`,`name`,{P.`value_double` | P.`value_timestamp`}
FROM record P
INNER JOIN(
SELECT {value_double | value_timestamp}
FROM record
WHERE name = {required_entry}
GROUP BY {value_double | value_timestamp}
HAVING COUNT(id_object) > 1
)temp ON {P.value_double = temp.value_double | P.value_timestamp = temp.value_timestamp}
WHERE name = {required_entry}
Can someone help and show the pure (better) solution?
Though less efficient than certain alternatives, I find an approach along these lines easier to read...
SELECT MIN(id_object) id_object
, price
, payment
, date
FROM
( SELECT id_object
, MAX(CASE WHEN name = 'price' THEN value_double END) price
, MAX(CASE WHEN name = 'payment' THEN value_double END) payment
, MAX(CASE WHEN name = 'date' THEN value_timestamp END) date
FROM eav
GROUP
BY id_object
) x
GROUP
BY price
, payment
, date;
I would just group_concat() the values together and do the test that way:
select t.*
from t join
(select min(id_object) id_object
from (select id_object,
group_concat(name, ':', coalesce(value_double, ''), ':', coalesce(value_timestamp, '') order by name) pairs
from t
where name in ('price', 'payment', 'date')
group by id_object
) tt
group by pairs
) tt
on t.id_object = tt.id_object;
To actually delete the ones that are not the minimum id for each group of related values:
delete t
from t left join
(select min(id) as id
from (select id, group_concat(name, ':', coalesce(value_double, ''), ':', coalesce(value_timestamp, '' order by name) as pairs,
from t
where name in ('price', 'payment', 'date')
group by id
) tt
group by pairs
) tt
on t.id = tt.id
where tt.id is null;
Actually I really don't know the appropriate title that will makes it unique as a question. Believe me, I tried my best to search about inner join, union, distinct just to make my query done.
I only have one table and it looks like this:
ID | ITEM | MESSAGE_INFO | PARENT_ID | IS_CLOSED | IS_APPROVAL
1 | A123 | test 1 | null | 1 | 1
2 | A123 | reply to.. | 1 | null | null
3 | A123 |another reply.| 1 | null | null
4 | B456 | test 2 | null | null | 1
5 | A123 | new test 1 | 1 | null | 1
6 | C789 | test 3 | null | 2 | 1
7 | C789 | reply to 3 | 6 | null | null
Note:
Message from the original author will have 1 in IS_APPROVAL
column and the PARENT_ID is null it means that this is the original message that was sent. IS_CLOSED will contain 1 if the conversation is still open, 2 if the original author can no longer reply to it, null it means the receiver didn't open the message yet.
PARENT_ID will contain the ID where the message is replying to.
Message reply to the original author will have null in IS_CLOSED column
Now what I want to do is I want to get the most recent message from the original author for each item. So the expected result is like this:
ID | ITEM | MESSAGE_INFO | PARENT_ID | IS_CLOSED | IS_APPROVAL
5 | A123 | new test 1 | 1 | null | 1
4 | B456 | test 2 | null | null | 1
I tried this query:
SELECT *
FROM TABLE
WHERE IS_APPROVAL = 1
AND (
IS_CLOSED IS NULL
OR IS_CLOSED < 2
)
GROUP BY ITEM
ORDER BY ID DESC;
But the result I'm getting is this:
ID | ITEM | MESSAGE_INFO | PARENT_ID | IS_CLOSED | IS_APPROVAL
1 | A123 | test 1 | null | 1 | 1
4 | B456 | test 2 | null | null | 1
This should do the trick:
SELECT tab.* FROM tab
INNER JOIN (SELECT MAX(ID) as ID FROM tab WHERE IS_APPROVAL = 1 AND (IS_CLOSED IS NULL OR IS_CLOSED<2) GROUP BY ITEM) ids
ON tab.ID = ids.ID;
It will first determine the highest ID for each item group (as a measure of recency) and then perform a join on itsself.
Is this real table structure ?
declare #t table(ID int,ITEM varchar(50),MESSAGE_INFO varchar(50)
, PARENT_ID int, IS_CLOSED int,IS_APPROVAL int)
insert into #t VALUES
(1 ,'A123',' test 1 ',null ,1 , 1 )
,(2 ,'A123',' reply to.. ', 1 ,null ,null )
,(3 ,'A123','another reply.', 1 ,null ,null )
,(4 ,'B456',' test 2 ', null ,null , 1 )
,(5 ,'A123',' new test 1 ', 1 ,null , 1 )
,(6 ,'C789',' test 3 ',null , 2 , 1 )
,(7 ,'C789',' reply to 3 ', 6 ,null ,null )
;With CTE as
(
select *,ROW_NUMBER()over(partition by item order by id desc)rn
from #t
where IS_APPROVAL = 1 AND (IS_CLOSED IS NULL OR IS_CLOSED<2)
)
select * from cte where rn=1
I have a table with three columns:
1. store name
2. data type (sales, return)
3. qty
---------------------------
| Stores | Data | Qty |
---------------------------
| HM | Sales | 15 |
| RD | Sales | 10 |
| HM | Return | 4 |
| RD | Return | 2 |
I want to select all store names, sales qty, return qty as following
--------------------------
| Store | Sales | Return |
--------------------------
| HM | 15 | 4 |
| RD | 10 | 2 |
Here's what I've tried:
SELECT store,
CASE `data`
WHEN 'Sales' THEN SUM(qty)
ELSE NULL
END as `Sales`,
CASE `data`
WHEN 'Return' THEN SUM(qty)
ELSE NULL
END as `Return`
FROM `full_report`
GROUP BY store
Result: I get wrong sales qty and Null for return qty!
You can use conditional aggregation . . . mixing case with sum():
select fr.store,
SUM(case when fr.data = 'Sales' then fr.qty else 0 end) as Sales,
SUM(case when fr.data = 'Return' then fr.qty else 0 end) as Returns
from full_report fr
group by fr.store;
I have a schema in Mysql database:
CREATE TABLE test
(
ID int,
Country varchar(50),
category varchar(10)
);
INSERT INTO test VALUES (1,'USA','A');
INSERT INTO test VALUES (2,'USA','A');
INSERT INTO test VALUES (3,'USA','B');
INSERT INTO test VALUES (4,'Canada','A');
with this query :
SELECT country,count(category),category FROM test GROUP BY country,category;
I get this result :
+---------+-------+----------+
| Country | count | category |
+---------+-------+----------+
| Canada | 1 | A |
| USA | 2 | A |
| USA | 1 | B |
+---------+-------+----------+
but I want get like this result :
+---------+---+---+
| Country | A | B |
+---------+---+---+
| Canada | 1 | 0 |
| USA | 2 | 1 |
+---------+---+---+
Any advice will be nice. Thanks
here is my SQL Fiddle
SELECT country,
sum(case when category = 'A' then 1 else 0 end) as A,
sum(case when category = 'B' then 1 else 0 end) as B
FROM test
GROUP BY country;
SQLFiddle demo