Multipivot issue in Snowflake - sql

Unable to use multi column pivot in Snowflake. Single column pivot is working fine.
Below is my Query:
SELECT * FROM ( SELECT DISTINCT geo_lkp.MSA_CD , geo_lkp.MSA_NM, geo_lkp.CSA_CD ,
geo_lkp.CSA_NM
FROM T_GEO_LKP geo_lkp , T_DIM_STATE_PROV state_lkp
WHERE geo_lkp.state_prov_nm = state_lkp.state_prov_cd
AND UPPER(geo_lkp.PSTL_CD) = '12345' AND state_lkp.ISO_3_CHAR_CNTRY_CD = 'USA' )
UNPIVOT( (LOCATION_CODE, LOCATION_NAME) FOR LOCATION_TYPE
IN ( (CSA_CD,CSA_NM) AS 'csa',
(MSA_CD,MSA_NM) AS 'msa') )
ORDER BY LOCATION_NAME
trying to convert below table data :
MSA_CD
MSA_NM
CSA_CD
CSA_NM
10580
Albany
104
Albany
to
LOCATION_TYPE
LOCATION_CD
LOCATION_NM
msa
10580
Albany
csa
104
Albany

I recommend using a lateral join and values:
select v.*
from t cross join lateral
(values ('msa', msa_cd, msa_nm),
('csa', csa_cd, csa_nm)
) v(location_type, location_cd, location_nm)

From snowflake support we came to know that multi column UNPIVOT is not supported right now. We have to do work around. One can be as mentioned by #gordon Linoff.
Another solution can be:
with location_data as (
SELECT DISTINCT geo_lkp.MSA_CD , geo_lkp.MSA_NM, geo_lkp.CSA_CD ,
geo_lkp.CSA_NM
FROM T_GEO_LKP geo_lkp , T_DIM_STATE_PROV state_lkp
WHERE geo_lkp.state_prov_nm = state_lkp.state_prov_cd
AND UPPER(geo_lkp.PSTL_CD) = '12345' AND state_lkp.ISO_3_CHAR_CNTRY_CD = 'USA'
)
select location_type, location_code, location_name from
(
select 'csa' as location_type, csa_cd as location_code, csa_nm as location_name from location_data
union all
select 'msa', msa_cd, msa_nm from location_data
) where location_code is not null order by location_name;

Related

Select columns maximum and minimum value for all records

I have a table as below; I want to get the column names having maximum and minimum value except population column (ofcourse it will have maximum value) for all records.
State Population age_below_18 age_18_to_50 age_50_above
1 1000 250 600 150
2 4200 400 300 3500
Result :
State Population Maximum_group Minimum_group Max_value Min_value
1 1000 age_18_to_50 age_50_above 600 150
2 4200 age_50_above age_18_to_50 3500 300
Assuming none of the values are NULL, you can use greatest() and least():
select state, population,
(case when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_below_18'
when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_18_to_50'
when age_below_18 = greatest(age_below_18, age_18_to_50, age_50_above)
then 'age_50_above'
end) as maximum_group,
(case when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_below_18'
when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_18_to_50'
when age_below_18 = least(age_below_18, age_18_to_50, age_50_above)
then 'age_50_above'
end) as minimum_group,
greatest(age_below_18, age_18_to_50, age_50_above) as maximum_value,
least(age_below_18, age_18_to_50, age_50_above) as minimum_value
from t;
If your result set is actually being generated by a query, there is likely a better approach.
An alternative method "unpivots" the data and then reaggregates:
select state, population,
max(which) over (dense_rank first_value order by val desc) as maximum_group,
max(which) over (dense_rank first_value order by val asc) as minimum_group,
max(val) as maximum_value,
min(val) as minimum_value
from ((select state, population, 'age_below_18' as which, age_below_18 as val
from t
) union all
(select state, population, 'age_18_to_50' as which, age_18_to_50 as val
from t
) union all
(select state, population, 'age_50_above' as which, age_50_above as val
from t
)
) t
group by state, population;
This approach would have less performance than the first, although it is perhaps easier to implement as the number of values increases. However, Oracle 12C supports lateral joins, where a similar approach would have competitive performance.
with CTE as (
select T.*
--step2: rank value
,RANK() OVER (PARTITION BY "State", "Population" order by "value") "rk"
from (
--step1: union merge three column to on column
select
"State", "Population",
'age_below_18' as GroupName,
"age_below_18" as "value"
from TestTable
union all
select
"State", "Population",
'age_18_to_50' as GroupName,
"age_18_to_50" as "value"
from TestTable
union all
select
"State", "Population",
'age_50_above' as GroupName,
"age_50_above" as "value"
from TestTable
) T
)
select T1."State", T1."Population"
,T3.GroupName Maximum_group
,T4.GroupName Minimum_group
,T3."value" Max_value
,T4."value" Min_value
--step3: max rank get maxvalue,min rank get minvalue
from (select "State", "Population",max( "rk") as Max_rank from CTE group by "State", "Population") T1
left join (select "State", "Population",min( "rk") as Min_rank from CTE group by "State", "Population") T2
on T1."State" = T2."State" and T1."Population" = T2."Population"
left join CTE T3 on T3."State" = T1."State" and T3."Population" = T1."Population" and T1.Max_rank = T3."rk"
left join CTE T4 on T4."State" = T2."State" and T4."Population" = T2."Population" and T2.Min_rank = T4."rk"
SQL Fiddle DEMO LINK
Hope it help you :)
Another option: use a combination of UNPIVOT(), which "rotates columns into rows" (see: documentation) and analytic functions, which "compute an aggregate value based on a group of rows" (documentation here) eg
Test data
select * from T ;
STATE POPULATION YOUNGERTHAN18 BETWEEN18AND50 OVER50
1 1000 250 600 150
2 4200 400 300 3500
UNPIVOT
select *
from T
unpivot (
quantity for agegroup in (
youngerthan18 as 'youngest'
, between18and50 as 'middleaged'
, over50 as 'oldest'
)
);
-- result
STATE POPULATION AGEGROUP QUANTITY
1 1000 youngest 250
1 1000 middleaged 600
1 1000 oldest 150
2 4200 youngest 400
2 4200 middleaged 300
2 4200 oldest 3500
Include Analytic Functions
select distinct
state
, population
, max( quantity ) over ( partition by state ) maxq
, min( quantity ) over ( partition by state ) minq
, first_value ( agegroup ) over ( partition by state order by quantity desc ) biggest_group
, first_value ( agegroup ) over ( partition by state order by quantity ) smallest_group
from T
unpivot (
quantity for agegroup in (
youngerthan18 as 'youngest'
, between18and50 as 'middleaged'
, over50 as 'oldest'
)
)
;
-- result
STATE POPULATION MAXQ MINQ BIGGEST_GROUP SMALLEST_GROUP
1 1000 600 150 middleaged oldest
2 4200 3500 300 oldest middleaged
Example tested w/ Oracle 11g (see dbfiddle) and Oracle 12c.
Caution: {1} column (headings) need adjusting (according to your requirements). {2} If there are NULLs in your original table, you should adjust the query eg by using NVL().
An advantage of the described approach is: the code will remain rather clear, even if more 'categories' are used. Eg when working with 11 age groups, the query may look something like ...
select distinct
state
, population
, max( quantity ) over ( partition by state ) maxq
, min( quantity ) over ( partition by state ) minq
, first_value ( agegroup ) over ( partition by state order by quantity desc ) biggest_group
, first_value ( agegroup ) over ( partition by state order by quantity ) smallest_group
from T
unpivot (
quantity for agegroup in (
y10 as 'youngerthan10'
, b10_20 as 'between10and20'
, b20_30 as 'between20and30'
, b30_40 as 'between30and40'
, b40_50 as 'between40and50'
, b50_60 as 'between50and60'
, b60_70 as 'between60and70'
, b70_80 as 'between70and80'
, b80_90 as 'between80and90'
, b90_100 as 'between90and100'
, o100 as 'over100'
)
)
order by state
;
See dbfiddle.

sql count new id that did not exists before for each month

I have the follow set of data
enter image description here
how can I write the sql to gives the result on right side?
that is the counting of unique id that did appeared previously for each month.
After long time of reading and reading his question, Ssiu wanted to ask the following:
So here is the test data in MS SQL: at that time he didn't clarify on postgresql
create table tmp1 (
ddate datetime
, iid int
)
insert into tmp1 values
('2017-11-01',1)
,('2017-11-02',2)
,('2017-11-03',3)
,('2017-11-04',4)
,('2017-11-05',5)
,('2017-11-06',5)
,('2017-11-07',5)
,('2017-12-01',1)
,('2017-12-02',2)
,('2017-12-03',3)
,('2017-12-04',6)
,('2017-12-05',7)
,('2018-01-01',1)
,('2018-01-02',2)
,('2018-01-03',3)
,('2018-01-04',4)
,('2018-01-05',8)
Disclaimer: The following is not the best approach for this problem. It is not applicable for more months, however it can give Ssiu a clue.
with cte(mmonth, iid) as (
select distinct convert(varchar(7), ddate, 120) mmonth
, iid
from tmp1
)
, cte_201711 as (
select * from cte where mmonth = '2017-11'
)
, cte_201712 as (
select * from cte where mmonth = '2017-12'
)
, cte_201801 as (
select * from cte where mmonth = '2018-01'
)
, cte_cnt201712 as(
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
)
, cte_cnt201801 as (
select cte_201711.mmonth as mm201711
, cte_201711.iid as id201711
, cte_201712.mmonth as mm201712
, cte_201712.iid as id201712
, cte_201801.mmonth as mm201801
, cte_201801.iid as id201801
from cte_201711
full outer join cte_201712
on cte_201712.iid = cte_201711.iid
full outer join cte_201801
on cte_201801.iid = cte_201712.iid
or cte_201801.iid = cte_201711.iid
)
--select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
select '2017-12' mmonth, count(*) Ssiu
from cte_cnt201712
where mm201711 is null
union all
select '2018-01' mmonth, count(*) Ssiu
from cte_cnt201801
where mm201711 is null
and mm201712 is null
Note the data for the cte_cnt201801 CTE:
select * from cte_cnt201801 order by isnull(mm201711,'z'), isnull(mm201712,'z')
So the result for the above query is:

Syntax error in FROM clause - MS ACCESS

I am working with a tool that would extract some data from an Access Database. So basically, i am working on a query to get this data.
Below is the code i am currently working on.
I am getting an error: Syntax error in FROM clause
I can't seem to find where the query is going wrong. I would appreciate any help! Thank youu.
EDIT: putting my actual query
SELECT table_freq.*, IIF(table_freq.txn_ctr > (table_ave_freq.ave_freq * 3), "T", "F") as suspicious_flag
FROM
(
SELECT tbl_TransactionHistory.client_num, tbl_TransactionHistory.client_name,
tbl_TransactionHistory.transaction_date, Count(tbl_TransactionHistory.client_num) AS txn_ctr
FROM tbl_TransactionHistory
GROUP BY tbl_TransactionHistory.client_num, tbl_TransactionHistory.client_name,
tbl_TransactionHistory.transaction_date
) AS table_freq
INNER JOIN
(
SELECT table_total_freq.client_num, total_txn_ctr as TotalTransactionFrequency, total_no_days as TotalTransactionDays,
(table_total_freq.total_txn_ctr)/(table_no_of_days.total_no_days) AS ave_freq
FROM
(
(
SELECT client_num, SUM(txn_ctr) AS total_txn_ctr
FROM
(
SELECT client_num, client_name, transaction_date, COUNT(client_num) AS txn_ctr
FROM tbl_TransactionHistory
GROUP BY client_num, client_name, transaction_date
) AS tabFreq
GROUP BY client_num
) AS table_total_freq
INNER JOIN
(
SELECT client_num, COUNT(txn_date) as total_no_days
FROM
(
SELECT DISTINCT(transaction_date) as txn_date, client_num
FROM tbl_TransactionHistory
ORDER BY client_num
) AS table1
GROUP BY client_num
) AS table_no_of_days
ON table_total_freq.client_num = table_no_of_days.client_num
)
) AS table_ave_freq
ON table_freq.client_num = table_ave_freq.client_num

Splitting rows to columns in oracle

I have data in a table which looks like:
I want to split its data and make it look like the following through a sql query in Oracle (without using pivot):
How can it be done?? is there any other way of doing so without using pivot?
You need to use a pivot query here to get the output you want:
SELECT Name,
MIN(CASE WHEN ID_Type = 'PAN' THEN ID_No ELSE NULL END) AS PAN,
MIN(CASE WHEN ID_Type = 'DL' THEN ID_No ELSE NULL END) AS DL,
MIN(CASE WHEN ID_Type = 'Passport' THEN ID_No ELSE NULL END) AS Passport
FROM yourTable
GROUP BY Name
You could also try using Oracle's built in PIVOT() function if you are running version 11g or later.
Since you mention without using PIVOT function, you can try to
use SQL within group for moving rows onto one line and listagg to display multiple column values in a single column.
In Oracle 11g, we can use the listagg built-in function :
select
deptno,
listagg (ename, ',')
WITHIN GROUP
(ORDER BY ename) enames
FROM
emp
GROUP BY
deptno
Which should give you the below result:
DEPTNO ENAMES
------ --------------------------------------------------
10 CLARK,KING,MILLER
20 ADAMS,FORD,JONES,SCOTT,SMITH
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD
You can find all the solution(s) to this problem here:
http://www.dba-oracle.com/t_converting_rows_columns.htm
For Oracle 11g and above, you could use PIVOT.
For pre-11g release, you could use MAX and CASE.
A common misconception, about PIVOT better in terms of performance than the old way of MAX and DECODE. But, under the hood PIVOT is same MAX + CASE. You can check it in 12c where Oracle added EXPAND_SQL_TEXT procedure to DBMS_UTILITY package.
For example,
SQL> variable c clob
SQL> begin
2 dbms_utility.expand_sql_text(Q'[with choice_tbl as (
3 select 'Jones' person,1 choice_nbr,'Yellow' color from dual union all
4 select 'Jones',2,'Green' from dual union all
5 select 'Jones',3,'Blue' from dual union all
6 select 'Smith',1,'Orange' from dual
7 )
8 select *
9 from choice_tbl
10 pivot(
11 max(color)
12 for choice_nbr in (1 choice_nbr1,2 choice_nbr2,3 choice_nbr3)
13 )]',:c);
14 end;
15 /
PL/SQL procedure successfully completed.
Now let's see what Oracle actually does internally:
SQL> set long 100000
SQL> print c
C
--------------------------------------------------------------------------------
SELECT "A1"."PERSON" "PERSON",
"A1"."CHOICE_NBR1" "CHOICE_NBR1",
"A1"."CHOICE_NBR2" "CHOICE_NBR2",
"A1"."CHOICE_NBR3" "CHOICE_NBR3"
FROM (
SELECT "A2"."PERSON" "PERSON",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=1) THEN "A2"."COLOR" END ) "CHOICE_NBR1",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=2) THEN "A2"."COLOR" END ) "CHOICE_NBR2",
MAX(CASE WHEN ("A2"."CHOICE_NBR"=3) THEN "A2"."COLOR" END ) "CHOICE_NBR3"
FROM (
(SELECT 'Jones' "PERSON",1 "CHOICE_NBR",'Yellow' "COLOR" FROM "SYS"."DUAL" "A7") UNION ALL
(SELECT 'Jones' "'JONES'",2 "2",'Green' "'GREEN'" FROM "SYS"."DUAL" "A6") UNION ALL
(SELECT 'Jones' "'JONES'",3 "3",'Blue' "'BLUE'" FROM "SYS"."DUAL" "A5") UNION ALL
(SELECT 'Smith' "'SMITH'",1 "1",'Orange' "'ORANGE'" FROM "SYS"."DUAL" "A4")
) "A2"
GROUP BY "A2"."PERSON"
) "A1"
SQL>
Oracle internally interprets the PIVOT as MAX + CASE.
You're able to create a non-pivot query by understanding what the pivot query will do:
select *
from yourTable
pivot
(
max (id_no)
for (id_type) in ('PAN' as pan, 'DL' as dl, 'Passport' as passport)
)
What the pivot does is GROUP BY all columns not specified inside the PIVOT clause (actually, just the name column), selecting new columns in a subquery fashion based on the aggregations before the FOR clause for each value specified inside the IN clause and discarding those columns specified inside the PIVOT clause.
When I say "subquery fashion" I'm refering to one way to achieve the result got with PIVOT. Actually, I don't know how this works behind the scenes. This subquery fashion would be like this:
select <aggregation>
from <yourTable>
where 1=1
and <FORclauseColumns> = <INclauseValue>
and <subqueryTableColumns> = <PIVOTgroupedColumns>
Now you identified how you can create a query without the PIVOT clause:
select
name,
(select max(id_no) from yourTable where name = t.name and id_type = 'PAN') as pan,
(select max(id_no) from yourTable where name = t.name and id_type = 'DL') as dl,
(select max(id_no) from yourTable where name = t.name and id_type = 'Passport') as passport
from yourTable t
group by name
You can use CTE's to break the data down and then join them back together to get what you want:
WITH NAMES AS (SELECT DISTINCT NAME
FROM YOURTABLE),
PAN AS (SELECT NAME, ID_NO AS PAN
FROM YOURTABLE
WHERE ID_TYPE = 'PAN'),
DL AS (SELECT NAME, ID_NO AS DL
FROM YOURTABLE
WHERE ID_TYPE = 'DL'),
PASSPORT AS (SELECT NAME, ID_NO AS "Passport"
FROM YOURTABLE
WHERE ID_TYPE = 'Passport')
SELECT n.NAME, p.PAN, d.DL, t."Passport"
FROM NAMES n
LEFT OUTER JOIN PAN p
ON p.NAME = n.NAME
LEFT OUTER JOIN DL d
ON d.NAME = p.NAME
LEFT OUTER JOIN PASSPORT t
ON t.NAME = p.NAME'
Replace YOURTABLE with the actual name of your table of interest.
Best of luck.

SQL query top 2 columns of joined table?

I am having no luck attempting to get the top (x number) of rows from a joined table. I want the top 2 resources (ordered by name) which in this case should be Katie and Simon and regardless of what I've tried, I can't seem to get it right. You can see below what I've commented out - and what looks like it should work (but doesn't). I cannot use a union. Any ideas?
select distinct
RTRESOURCE.RNAME as Resource,
RTTASK.TASK as taskname, SUM(distinct SOTRAN.QTY2BILL) AS quantitytobill from SOTRAN AS SOTRAN INNER JOIN RTTASK AS RTTASK ON sotran.taskid = rttask.taskid
left outer JOIN RTRESOURCE AS RTRESOURCE ON rtresource.keyno=sotran.resid
WHERE sotran.phantom<>'y' and sotran.pgroup = 'L' and sotran.timesheet = 'y' and sotran.taskid >0 AND RTRESOURCE.KEYNO in ('193','159','200') AND ( SOTRAN.ADDDATE>='8/15/2015 12:00:00 AM' AND SOTRAN.ADDDATE<'9/3/2015 11:59:59 PM' )
//and RTRESOURCE.RNAME in ( select distinct top 2 RTRESOURCE.RNAME from RTRESOURCE order by RTRESOURCE.RNAME)
//and ( select count(*) from RTRESOURCE RTRESOURCE2 where RTRESOURCE2.RNAME = RTRESOURCE.RNAME ) <= 2
GROUP BY RTRESOURCE.rname,RTTASK.task,RTTASK.taskid,RTTASK.mdsstring ORDER BY Resource,taskname
You should provide a schema.
But lets assume your query work. You create a CTE.
WITH youQuery as (
SELECT *
FROM < you big join query>
), maxBill as (
SELECT Resource, Max(quantitytobill) as Bill
FROM yourQuery
)
SELECT top 2 *
FROM maxBill
ORDER BY Bill
IF you want top 2 alphabetical
WITH youQuery as (
SELECT *
FROM < you big join query>
), Names as (
SELECT distinct Resource
FROM yourQuery
Order by Resource
)
SELECT top 2 *
FROM Names