Unable to pivot multiple columns in snowflake.
This works:
--DROP TABLE "PUBLIC".MONTHLY_SALES
create or replace table monthly_sales(empid int, amount int, month text)
as select * from values
(1, 10000, 'JAN'),
(1, 400, 'JAN'),
(2, 4500, 'JAN'),
(2, 35000, 'JAN'),
(1, 5000, 'FEB'),
(1, 3000, 'FEB'),
(2, 200, 'FEB'),
(2, 90500, 'FEB'),
(1, 6000, 'MAR'),
(1, 5000, 'MAR'),
(2, 2500, 'MAR'),
(2, 9500, 'MAR'),
(1, 8000, 'APR'),
(1, 10000, 'APR'),
(2, 800, 'APR'),
(2, 4500, 'APR');
SELECT * FROM monthly_sales
pivot(
sum(amount)
for month in ('JAN', 'FEB', 'MAR', 'APR')
) AS p;
But I receive an error when adding an additional aggregate
SELECT * FROM monthly_sales
pivot(
sum(amount)
, count(amount)
for month in ('JAN', 'FEB', 'MAR', 'APR')
) AS p;
QL Error [1003] [42000]: SQL compilation error:
syntax error line 4 at position 5 unexpected ','.
syntax error line 4 at position 12 unexpected '('.
syntax error line 5 at position 45 unexpected ')'.
Any guidance is appreciated
Felipe is right, you can only use one aggregate in PIVOT
But based on what you are trying to achieve, this query might be helpful.
SELECT 'SUM' RECORD_TYPE, * FROM (SELECT * FROM monthly_sales
pivot(
SUM(amount)
for month in ('JAN', 'FEB', 'MAR', 'APR')
) AS p)
UNION ALL
SELECT 'COUNT', * FROM (SELECT * FROM monthly_sales
pivot(
count(amount)
for month in ('JAN', 'FEB', 'MAR', 'APR')
) AS p)
Output:
Row RECORD_TYPE EMPID 'JAN' 'FEB' 'MAR' 'APR'
1 SUM 1 10400 8000 11000 18000
2 SUM 2 39500 90700 12000 5300
3 COUNT 1 2 2 2 2
4 COUNT 2 2 2 2 2
According to the documentation of PIVOT in Snowflake, you can only get one aggregate:
SELECT ...
FROM ...
PIVOT ( <aggregate_function> ( <pivot_column> )
FOR <value_column> IN ( <pivot_value_1> [ , <pivot_value_2> ... ] ) )
[ ... ]
https://docs.snowflake.com/en/sql-reference/constructs/pivot.html
If you post a different question asking with sample input and output, we can try to work out a solution for this.
As for this question, I don't know how I would represent two different aggregates in a relational table. But at least we can answer why you are getting a syntax error: Multiple aggregates is not a supported syntax by PIVOT.
Related
Trying to convert a row-by-row percentage calculation query to dynamic by using window function over a partition by column. Not sure this is the right way please suggest.
create table qdetails
(
qcode int,
qcode_detail_01 int,
qcode_detail_02 int
);
insert into qdetails(qcode, qcode_detail_01, qcode_detail_02)
values (25, 999, 56),
(95, 999, 67),
(96, 999, 68),
(21, 888, 56),
(22, 888, 67),
(26, 888, 68);
create table qmaster
(
qcode int,
qtype text,
qvalue int
);
insert into qmaster (qcode, qtype, qvalue)
values
(25, 'XYZ', 25),
(95, 'XYZ', 34),
(96, 'XYZ', 17),
(99, 'XYZ', 6),
(91, 'XYZ', 4),
(92, 'XYZ', 14),
(21, 'ABC', 7),
(22, 'ABC', 23),
(23, 'ABC', 11),
(24, 'ABC', 6),
(24, 'ABC', 4),
(26, 'ABC', 14);
For these table structures I have the following code; I'm trying to do this in a single query without repeating this for each row. This is on SQLite but that should not matter.
select
a.qcode_detail_01,
b.qtype,
a.qcode,
b.qvalue,
/* calculating % for one qtype at a time manually*/
sum(cast(b.qvalue as float))/(select sum(cast(b.qvalue as float)) from qmaster b where b.qtype = "XYZ") as 'Percentage'
from
qdetails a,
qmaster b
where
b.qtype = "XYZ"
and a.qcode = b.qcode
group by
a.qcode, a.qcode_detail_01;
/*repeat for ABC*/
select
a.qcode_detail_01,
b.qtype,
a.qcode,
b.qvalue,
/* calculating % for one qtype at a time manually*/
sum(cast(b.qvalue as float))/(select sum(cast(b.qvalue as float)) from qmaster b where b.qtype = "ABC") as 'Percentage'
from
qdetails a,
qmaster b
where
b.qtype = "ABC"
and a.qcode = b.qcode
group by
a.qcode, a.qcode_detail_01;
The part that is not working is this:
/*Avoid Repetition by doing this dynamically using Window Function */
select
a.qcode_detail_01,
b.qtype,
a.qcode,
b.qvalue,
cast(b.qvalue as float)/sum(cast(b.qvalue as float)) OVER (PARTITION BY b.qtype) as 'Percentage'
from qdetails a,
qmaster b
and a.qcode = b.qcode
group by a.qcode,a.qcode_detail_01;
Here is a SQLFiddle for the same; please advise.
For the sample data above, i'm looking to get the following result with a single query
qcode_detail_01 qtype qcode qvalue Percentage
999 XYZ 25 25 0.25
999 XYZ 95 34 0.34
999 XYZ 96 17 0.17
888 ABC 21 7 0.1076923076923077
888 ABC 22 23 0.35384615384615387
888 ABC 26 14 0.2153846153846154
The simplest way to do this is to join a CTE that returns the total values for each qtype:
WITH cte AS (SELECT qtype, SUM(qvalue) total_value FROM qmaster GROUP BY qtype)
SELECT d.qcode_detail_01, m.qtype, d.qcode, m.qvalue,
SUM(CAST(m.qvalue AS FLOAT)) / c.total_value Percentage
FROM qdetails d
INNER JOIN qmaster m ON d.qcode = m.qcode
INNER JOIN cte c ON c.qtype = m.qtype
GROUP BY d.qcode, d.qcode_detail_01, m.qtype
ORDER BY d.qcode_detail_01;
Another way to do it, with a LEFT join and window function SUM():
SELECT *
FROM (
SELECT DISTINCT d.qcode_detail_01, m.qtype, d.qcode, m.qvalue,
SUM(CAST(m.qvalue AS FLOAT)) /
SUM(SUM(CAST(m.qvalue AS FLOAT))) OVER (PARTITION BY m.qtype) AS Percentage
FROM qmaster m LEFT JOIN qdetails d
ON d.qcode = m.qcode
GROUP BY m.qcode, d.qcode_detail_01, m.qtype
)
WHERE qcode_detail_01 IS NOT NULL;
See the demo.
Also, use proper joins with ON clauses.
I have data in a table which looks like the below data set.
I want to get a group of those items whose price sum is less than 10000.
CREATE TABLE Table1
(slno int, item varchar(10), price int);
INSERT INTO Table1
(slno, item, price)
VALUES
(1, 'item1', 1000),
(2, 'item2', 2000),
(3, 'item3', 3000),
(4, 'item4', 4000),
(5, 'item5', 5000),
(6, 'item6', 6000),
(7, 'item7', 10000),
(8, 'item8', 2000),
(9, 'item9', 8000),
(10, 'item10', 2500),
(11, 'item11', 9000),
(12, 'item12', 1000),
(13, 'item13', 2500),
(14, 'item14', 2500),
(15, 'item15', 2500);
My sql query looks like this:
SELECT slno, item,price
FROM
(
SELECT slno, item,price
(
SELECT SUM(price)
FROM Table1
WHERE slno<= t.slno
) total
FROM Table1 t
) q
WHERE total <= 1000
ORDER BY item
It's not giving the expected result though, it's giving only one set of records:
(1, 'item1', 1000),
(2, 'item2', 2000),
(3, 'item3', 3000),
(4, 'item4', 4000)
whereas I need it to give me something like this:
1ST SET
(1, 'item1', 1000),
(2, 'item2', 2000),
(3, 'item3', 3000),
(4, 'item4', 4000)
2ND SET
(7, 'item7', 10000),
#GordonLinoff
This type of operation requires a recursive CTE. In this case, you can assign a group to each row using such logic. The following assumes that slno has no gaps as in your example data:
with cte as (
select slno, item, price, 1 as grp, price as running_price
from table1
where slno = 1
union all
select t1.slno, t1.item, t1.price,
(case when t1.price + cte.running_price > 10000 then grp + 1 else grp end),
(case when t1.price + cte.running_price > 10000 then t1.price else cte.running_price + t1.price end)
from cte join
table1 t1
on t1.slno = cte.slno + 1
)
select *
from cte
order by slno;
Here is a db<>fiddle.
I've got a table with almost 10 million views and would to run this query on the latest million or hundred thousand or so.
Here's a SQL fiddle with example data and input/output: http://sqlfiddle.com/#!9/340a41
Is this even possible?
CREATE TABLE object (`id` int, `name` varchar(7), `value` int);
INSERT INTO object (`id`, `name`, `value`)
VALUES
(1, 'a', 1),
(2, 'b', 2),
(3, 'c', 100),
(4, 'a', 1),
(5, 'b', 2),
(6, 'c', 200),
(7, 'a', 2),
(8, 'b', 2),
(9, 'c', 300),
(10, 'a', 2),
(11, 'b', 2),
(12, 'a', 2),
(13, 'b', 2),
(14, 'c', 400)
;
-- Want:
-- name, max(id), count(id)
-- 'a', 4, 2
-- 'b', 14, 5
-- 'a', 12, 3
If you want the latest and the id is implemented sequentially, then you can do this using limit or top. In SQL Server:
select top 100000 o.*
from object o
order by id desc;
In MySQL, you would use limit:
select o.*
from object o
order by id desc
limit 100000
select name, count(id) cnt, max(id) max_id, max(value) max_v
from
(select
top 1000000 -- MS SQL Server
id,name,value
from myTable
limit 1000000 --mySQL
order by id desc)
group by name
remove line which doesn't match your server.
I'm on SQL Server 2014 and I have a list of building ID's with various totals columns. I would like to add a row of totals for the building.
Can the following desired output be done in SQL? If so, what's the best way? I need to learn how to this for various projects. I don't need LevelID totals, just for the other columns, and I'm not sure if we can add the text 'BuildingTotal' in the totals row.
Desired Output:
SQL Fiddle:
Here is the code for a test table in case SQL Fiddle doesn't work.
CREATE TABLE EMPLOYEES
(
BuildingID Numeric,
LevelID Numeric,
FemaleEmp Numeric,
MaleEmp Numeric,
TotalEmpByLevel Numeric
)
INSERT INTO EMPLOYEES
(BuildingID, LevelID, FemaleEmp, MaleEmp, TotalEmpByLevel)
VALUES
(111, 1,91, 89, 180),
(111, 2,98, 94, 192),
(111, 3,94, 113 , 207),
(111, 4,110, 119, 229),
(111, 5,107, 9, 203),
(113, 1,53, 4, 101),
(113, 2,51, 5, 106),
(113, 3,68, 5, 119),
(113, 4,58, 6, 118),
(113, 5,57, 6, 117),
(114, 1,25, 3, 56 ),
(114, 2,26, 3, 63 ),
(114, 3,32, 2, 61 ),
(114, 4,27, 3, 58 ),
(114, 5,26, 2, 49 ),
(116, 1,84, 102 , 186),
(116, 2,83, 92, 175),
(116, 3,89, 87, 176),
(116, 4,71, 91, 162),
(116, 5,87, 72, 159)
Thank you for your help!
One method is to use grouping sets or with rollup. This requires a little trick, though. You need an aggregation query:
select BuildingId, LevelId, sum(FemaleEmp) as FemaleEmp,
sum(MaleEmp) as MaleEmp, sum(TotalEmpByLevel) as TotalEmpByLevel
from employees
group by grouping sets((BuildingId, LevelId), (BuildingId));
You can add the word Total to the lines. This is a bit tricky, because the types are different:
select (case when grouping(LevelId) = 1 then 'Building Total' else cast(BuildingId as varchar(255)) end) as BuildingId,
LevelId, sum(FemaleEmp) as FemaleEmp,
sum(MaleEmp) as MaleEmp, sum(TotalEmpByLevel) as TotalEmpByLevel
from employees
group by grouping sets((BuildingId, LevelId), (BuildingId));
Try Group by with rollup
This is link with example
I have following table (master_group) structure :
code name under
1 National Sales Manager 1
2 regional sales manager 1
3 area sales manager 2
4 sales manager 3
How do I get the ultimate parent of a particular row like :
code name under ultimateparent
1 National Sales Manager 1 1
2 regional sales manager 1 1
3 area sales manager 2 1
4 sales manager 3 1
With recursive cte going from top to childs:
with cte as(
select *, code as ultimate from t where code = under
union all
select t.*, c.ultimate from t
join cte c on c.code = t.under
where t.code <> t.under
)
select * from cte
For data:
create table t (code int, name varchar(100), under int)
insert into t values
(1, 'National Sales Manager', 1),
(2, 'regional sales manager', 1),
(3, 'area sales manager', 2),
(4, 'sales manager', 3),
(5, 'a', 5),
(6, 'b', 5),
(7, 'c', 5),
(8, 'd', 7),
(9, 'e', 7),
(10, 'f', 9),
(11, 'g', 9)
it generates the output:
code name under ultimate
1 National Sales Manager 1 1
5 a 5 5
6 b 5 5
7 c 5 5
8 d 7 5
9 e 7 5
10 f 9 5
11 g 9 5
2 regional sales manager 1 1
3 area sales manager 2 1
4 sales manager 3 1
Fiddle http://sqlfiddle.com/#!6/17c12e/1
You can use a recursive CTE to walk the tree and then choose the highest level for each code:
with cte as (
select mg.code, mg.name as name, mg.under as under, mg.under as parent, 1 as lev
from master_group mg
union all
select mg.code, mg.name, mg.under, cte.under as parent, cte.lev + 1
from master_group mg join
cte
on mg.under = cte.code
where cte.under is not null and cte.under <> mg.code
)
select code, name, under, parent as ultimateparent
from (select cte.*, max(lev) over (partition by cte.code) as maxlev
from cte
) t
where lev = maxlev;
Here is a SQL Fiddle.
I would put NULL as under (in my example ParentId) when it's the top record. With this assumption here's a solution
;
WITH Result AS
(
SELECT Id, ParentId, Name, Id as [Top] FROM
sample
where ParentId IS NULL
UNION ALL
SELECT s.Id, s.ParentId, s.Name, [Top]
FROM sample s INNER JOIN Result R ON s.ParentId = R.Id
)
http://sqlfiddle.com/#!6/13b9d/14
I suggest you to use a recursive function like this:
CREATE FUNCTION dbo.parentID (#code int)
RETURNS int AS
BEGIN
DECLARE #ResultVar int
SELECT #ResultVar = (SELECT under FROM master_group WHERE code = #code)
IF #ResultVar <> #code
BEGIN
SELECT #ResultVar = dbo.parentID(#ResultVar)
END
RETURN #ResultVar
END
GO
An use it like this:
SELECT *,
dbo.parentId(code) AS ultimateparent
FROM master_group
I'm going to shamelessly steal the data setup from another answer and demonstrate how you'd do this with hierarchyid:
create table t (code int, name varchar(100), under int)
insert into t values
(1, 'National Sales Manager', null),
(2, 'regional sales manager', 1),
(3, 'area sales manager', 2),
(4, 'sales manager', 3),
(5, 'a', null),
(6, 'b', 5),
(7, 'c', 5),
(8, 'd', 7),
(9, 'e', 7),
(10, 'f', 9),
(11, 'g', 9);
with cte as (
select code, name, under as parentCode, code as ultimateParent, cast('/' + cast(code as varchar) + '/' as nvarchar(max)) as h
from t
where under is null
union all
select child.code, child.name, child.under as ParentCode, parent.ultimateParentCode, cast(parent.h + cast(child.code as varchar) + '/' as nvarchar(max))
from t as child
join cte as parent
on child.under = parent.code
), hier as (
select code, name, parentCode, ultimateParentCode, cast(h as hierarchyid) as h
from cte
)
select code, name, parentCode, ultimateParentCode, h.ToString(), h.GetAncestor(h.GetLevel()-1).ToString()
from hier
Keep in mind, the recursive CTE need only be done once (or on data changes). The point that I'm making is that once you have a hierarchyid calculated (which you can store in row, btw), it's easy to answer the question you're posing with method calls on the hierarchyid (and possibly a join if you want to get back the progenitor's info).