Recursive CTE must not omit column names - sql

ERROR: Recursive CTE must not omit column names.
I'm trying to write my first recursive query, using Postgresql. I'm getting an error message that the query must not omit column names - I am declaring column names, and can't figure this out. Any pointers on what I'm doing wrong would be much appreciated.
with recursive account_hierarchy as (
select company,entry_no,parent_entry_no, description from fin_bc_raw.r_gl_account_category
where parent_entry_no =0
union all
select company,entry_no,parent_entry_no,description from fin_Bc_raw.r_gl_account_category lvl2
left join account_hierarchy on lvl2.company=account_hierarchy.company and lvl2.parent_entry_no = account_hierarchy.entry_no
)
select company,entry_no,parent_entry_no, description
from account_hierarchy

First you must list the columns in the cte header (see the manual) because these columns are referenced in the recursive part of the recursive cte itself.
Then you should prefix the column names with the table alias in the recursive part because different joined tables share the same column names.
Then you should use arrays if you want to build multilevel hierarchies in your recursive cte.
Then you should check that there is no infinite loop by introcing a check in the recursive part of the recursive cte.
Finally you will have to filter the resulting rows of the recursive cte so that to select the relevant ones only.
As an example (not tested !) :
with recursive account_hierarchy (company, entry_no, description, parent_entry_no, parent_hierarchy_company, parent_hierarchy_no, parent_hierarchy_description )
as (
-- start from the leaf companies with no children companies
select parent.company
, parent.entry_no
, parent.description
, parent.parent_entry_no
, array[parent.company]
, array[parent.entry_no]
, array[parent.description]
from fin_bc_raw.r_gl_account_category AS parent
left join fin_bc_raw.r_gl_account_category AS children
on children.parent_entry_no = parent.entry_no
where children.parent_entry_no IS NULL
union all
-- concatenate the parent companies to the already seleccted children companies
select h.company
, h.entry_no
, h.description
, lv12.parent_entry_no
, lv12.company || h.parent_hierarchy_company
, lv12.entry_no || h.parent_hierarchy_no
, lv12.description || h.parent_hierarchy_description
from fin_Bc_raw.r_gl_account_category lvl2
inner join account_hierarchy h
on h.parent_entry_no = lv12.entry_no
where NOT h.parent_hierarchy_no #> array[lv12.entry_no] -- avoid inifinite loops
)
select DISTINCT ON (entry_no)
company, entry_no, description
, parent_hierarchy_company, parent_hierarchy_no, parent_hierarchy_description
from account_hierarchy
ORDER BY entry_no, array_length(parent_hierarchy_no, 1) DESC

Related

Rewrite this loop as a recursive CTE?

Link to demo code and data: http://sqlfiddle.com/#!18/5811b/9
This while loop iterates over data and inserts data based on a merge condition, which is (roughly) "find the MergeRecordId in the earliest row that matches the ActionId, ParentActionId and Category, then insert relevant rows that have this Merge Id, using the current row's Merge Id". It ignores rows that have no "predecessors". Idx and OriginalIdx are helper columns.
Is it possible to rewrite this using a recursive CTE? Or should I be using a different technique?
This is currently what I have, but it obviously doesn't work because it doesn't match the earliest item (iRank = 1 over the Date):
; WITH cte AS
(
SELECT Idx
, ActionId
, ParentActionId
, Category
, ActionDateUpdated
, MergeRecordId
, OriginalIdx
FROM #Example
UNION ALL
SELECT -ex1.Idx
, ex2.ActionId
, ex2.ParentActionId
, ex2.Category
, ex1.ActionDateUpdated
, ex1.MergeRecordId
, ex2.Idx
FROM #Example ex1
JOIN cte ex2
ON ex1.Category = ex2.Category
AND ex1.ActionDateUpdated > ex2.ActionDateUpdated
WHERE EXISTS
(
SELECT 1
FROM #Example ex3
WHERE ex1.ActionId = ex3.ActionId
AND ex1.ParentActionId = ex3.ParentActionId
AND ex1.Category = ex3.Category
AND ex1.ActionDateUpdated > ex3.ActionDateUpdated
)
)
SELECT *
FROM cte ORDER BY ABS(idx), ABS(OriginalIdx);
Under usual circumstances it should be trivial to get the required MergeRecordId using a sub-query, but you can't use sub-queries containing CTEs in a recursive CTE. Without this filter it makes the query useless with large datasets.
(Another quirk is the deletion of the current row in the loop, but I'm not too concerned about that.)

Pivot in SQL without Aggregate function

I have a scenario Where I have a table like
Table View
and What Output I want is
If your argument is "I will only ever have one value or no values, therefore I don't want an aggregate", realise that there are several aggregates that, if they're only passed a single value to aggregate, will return that value back as their result. MIN and MAX come to mind. SUM also works for numeric data.
Therefore the solution to specifying a PIVOT without an aggregate is instead to specify such a "pass through" aggregate here.
Basically, PIVOT internally works a lot the same as GROUP BY. Except the grouping columns are all columns in the current result set other than the column mentioned in the aggregate part of the PIVOT specification. And just as with the rules for the SELECT clause when GROUP BY is used1, every column either needs to be a grouping column or contained in an aggregate.
1Grumble, grumble, older mysql grumble. Although the defaults are more sensible from 5.7.5 up.
Try this:
Demo
with cte1 as
(
select 'Web' as platformname,'abc' as productname,'A' as grade
union all
select 'Web' ,'cde' ,'B'
union all
select 'IOS' ,'xyz' ,'C'
union all
select 'MAX' ,'cde' ,'D'
)
select productname,[Web], [IOS], [Android],[Universal],[Mac],[Win32]
from cte1 t
pivot
(
max(grade)
for platformname in ([Web], [IOS], [Android],[Universal],[Mac],[Win32])
) p
You can "pivot" such data using joins:
select p.productname,
t_win32.grade as win32,
t_universal.grade as universal,
. . .
from products p left join -- assume you have such a table
t t_win32
on t_win32.product_name = p.productname and t_win32.platform = 'Win32' left join
t t_universal
on t_universal.product_name = p.productname and t_universal.platform = 'Universal' left join
. . .
If you don't have a table products, use a derived table instead:
from (select distinct product_name from t) p left join
. . .

Oracle how to keep order in a hierarchical view when using 'in' condition clause

In Oracle, given a hierarchical view V powered by connect by, which may represent a hierarchical query already order siblings by some logic.
if do the query against V like this:
select * from V where key_column in (any subquery may return key_columns in a different order)
how can we keep the original order in view V?
I know I can add a rownum column to the view, then order by it, but I am looking a method in sql, I don't want to change view's structure.
the view:
CREATE or replace VIEW
CWE_DICT_TREE_VIEW
AS
SELECT
SUBSTR (SYS_CONNECT_BY_PATH (a.node_id, '.'), 2) AS id_path,
SUBSTR (SYS_CONNECT_BY_PATH (a.entry_key, '.'), 2) AS key_path,
getdictname(parent_node) AS parent_entry_value,
LEVEL AS node_level,
CONNECT_BY_ISLEAF AS node_is_leaf,
a.*
FROM
cwe_dict a START WITH a.parent_node IS NULL CONNECT BY PRIOR a.node_id = a.parent_node ORDER
SIBLINGS BY a.inline_sort_no;
the query:
select NODE_ID , PARENT_NODE , ENTRY_VALUE , NODE_IS_LEAF , INLINE_SORT_NO , NODE_LEVEL , ENTRY_KEY
from CWE_DICT_TREE_VIEW where NODE_ID in (
with link(NODE_ID,PARENT_NODE) AS (
select NODE_ID,PARENT_NODE from CWE_DICT_TREE_VIEW WHERE ( ENTRY_VALUE like '%农%') or( ENTRY_KEY like '%农%' )
union all
select P2.NODE_ID, P2.PARENT_NODE from LINK P1 inner join CWE_DICT_TREE_VIEW P2 on P2.NODE_ID = P1.PARENT_NODE
)
select distinct NODE_ID from LINK
)
How can we keep the original order in view V?
You have a profound misunderstanding about tables and relational databases. Tables represent unordered sets. There is no "original order" in a table. The only ordering is the ordering that you specify in a query.
Tables often have an "id" type of primary key that provides the "natural ordering". Simply include this column in the order by. Say it is key_column:
select *
from V
where key_column in (any subquery may return key_columns in a different order)
order by key_column;

SQL Server CTE use IDs from single column with EXCEPT?

Having received kindness the other day from someone whose eyes were less bleary than mine I thought I'd give it another shot. Thanks in advance for your assistance.
I have a single SQL Server (2012) table named Contacts. That table has four columns I am currently concerned with. The table has a total of 71,454 rows. There are two types of records in the table; Companies and Employees. Both use the same column, named (Client ID), for their primary key. The existence of a Company Name is what differentiates between Company and Employee data. Employees have no associated Company Name. There are 29,021 Companies leaving 42,433 Employees.
There may be 0-n number of Employees associated with any one Company. I am attempting to create output that will reflect the relationship between Companies and Clients, if there are any. I would like to use the Company ID (Client ID column) as my anchor data set.
Not sure my definition is correct but the thought was to create a CTE of the known Companies by virtue of a given Company Name. Then, use the remaining Client IDs but use the EXCEPT clause to filter the already-retrieved Client IDs out of the result set.
Here the code I currently have;
;
WITH cte ( BaseID, Client_id, Company_name,
First_name, Last_name, [level] )
AS ( SELECT Client_id AS BaseID ,
Client_id ,
Company_name ,
First_name ,
Last_name ,
1
FROM dbo.Conv_client_clean
WHERE ( COMPANY_NAME IS NOT NULL
OR COMPANY_NAME != ''
)
UNION ALL
SELECT c.BaseID ,
children.Client_id ,
children.Company_name ,
children.First_name ,
children.Last_name ,
cte.[level] + 1
FROM dbo.Conv_client_clean children
INNER JOIN cte c ON c.Client_id = children.CLIENT_ID
EXCEPT
SELECT children.Client_id
FROM cte
)
SELECT BaseID ,
Client_id ,
Company_name ,
first_name ,
Last_name ,
[Level]
FROM cte
OPTION ( MAXRECURSION 0 );
In this instance I receive the following error;
Msg 252, Level 16, State 1, Line 3
Recursive common table expression 'cte' does not contain a top-level UNION ALL operator.
Any suggestions?
Thanks!
In the recursion cte query, you cannot have more set operations(union, except, union all,intersect) after the the one Union ALL which is refers the cte itself. I think what you can try is change the query as below and check
...
UNION ALL
SELECT c.BaseID ,
children.Client_id ,
children.Company_name ,
children.First_name ,
children.Last_name ,
cte.[level] + 1
FROM dbo.Conv_client_clean children
WHERE children.Client_id NOT IN (SELECT Client_id FROM cte)
As mentioned to Kiran I was able to concoct an 'old fashioned' approach what is good enough for now.
Thank you everyone for your kind attention.
I'm not sure what you are trying to do with level. It seems that it will be 1 for companies and 2 for employees. If that's the case, you don't even need recursion. The first part of your cte creates a list of companies. That's fine. Now use that to join back to the original table to show all the employees too.
WITH
cte( BaseID, ClientID, Company_name, First_name, Last_name )AS(
SELECT Base_ID,
Base_ID AS Client_id ,
Company_name,
First_name,
Last_name
FROM dbo.Conv_client_clean
WHERE COMPANY_NAME IS NOT NULL
OR COMPANY_NAME <> ''
)
select c2.Base_id, c2.Client_id,
c1.Company_Name, c2.First_Name, c2.Last_Name,
case when c2.client_id is null then 1 else 2 end Level
from cte c1
join Conv_client_clean c2
on c1.BaseID = isnull( c2.Client_ID, c2.Base_id )
order by c1.BaseID, c2.Base_id;
Here's where I fiddled with it.
Unfortunately anything besides UNION ALL, after you've made your recursive reference, will not work. And if you think about it, it makes sense.
Recursion is conceptually identical to the following where recursion continues until max depth is reached or a query returns no results upon which another execution could act.
WITH Anchor AS (select...)
,recurse1 as (<Some body referring to Anchor>)
,recurse2 as (<Identical body except referring to recurse1>)
,recurse3 as (<Identical body except referring to recurse2>)
...
select * from Anchor
union all
select * from recurse1
union all
select * from recurse2
...
The problem is that conjunctive operators apply to EVERYTHING that precedes it. In your case, EXCEPT operates on everything to it's left side which includes the Anchor query. Afterwards, when looking for the anchor to which the recursive part must be applied, the query compiler doesn't find a 'top level union all operator' any more because it's been consumed as part of the left side of your recursive query.
It wouldn't help to contrive some syntax akin to parenthesis that could delimit the scope of the left side of your table conjunction because you would then build a case of 'multiple recursive references' which is also illegal.
BOTTOM LINE IS: The only conjunction that works in the recursive part of your query is UNION ALL because it simply concatenates the right side. It doesn't require knowledge of the left side to determine which rows to include.

SQL nested aggregate functions MAX(COUNT(*))

I'm trying to select max(count of rows).
Here is my 2 variants of SELECT
SELECT MAX(COUNT_OF_ENROLEES_BY_SPEC) FROM
(SELECT D.SPECCODE, COUNT(D.ENROLEECODE) AS COUNT_OF_ENROLEES_BY_SPEC
FROM DECLARER D
GROUP BY D.SPECCODE
);
SELECT S.NAME, MAX(D.ENROLEECODE)
FROM SPECIALIZATION S
CROSS JOIN DECLARER D WHERE S.SPECCODE = D.SPECCODE
GROUP BY S.NAME
HAVING MAX(D.ENROLEECODE) =
( SELECT MAX(COUNT_OF_ENROLEES_BY_SPEC) FROM
( SELECT D.SPECCODE, COUNT(D.ENROLEECODE) AS COUNT_OF_ENROLEES_BY_SPEC
FROM DECLARER D
GROUP BY D.SPECCODE
)
);
The first one is working OK, but I want to rewrite it using "HAVING" like in my second variant and add there one more column. But now 2nd variant don't output any data in results, just empty columns.
How can I fix it ? Thank YOU!)
This query based on description given in comments and some suggestions, so it may be wrong:
select -- 4. Join selected codes with specializations
S.Name,
selected_codes.spec_code,
selected_codes.count_of_enrolees_by_spec
from
specialization S,
(
select -- 3. Filter records with maximum popularity only
spec_code,
count_of_enrolees_by_spec
from (
select -- 2. Count maximum popularity in separate column
spec_code,
count_of_enrolees_by_spec,
max(count_of_enrolees_by_spec) over (partition by null) max_count
from (
SELECT -- 1. Get list of declarations and count popularity
D.SPECCODE AS SPEC_CODE,
COUNT(D.ENROLEECODE) AS COUNT_OF_ENROLEES_BY_SPEC
FROM DECLARER D
GROUP BY D.SPECCODE
)
)
where count_of_enrolees_by_spec = max_count
)
selected_codes
where
S.SPECCODE = selected_codes.spec_code
Also query not tested and some syntax errors are possible.