Simple SQL path query? - sql

I am working through an intro SQL textbook and am confused by the following problem, where we are given the table and values:
CREATE TABLE LineageTable (
parent INT,
id INT,
genus_name VARCHAR(30),
PRIMARY KEY (id)
);
INSERT INTO LineageTable VALUES
(3, 1, 'FamilyA'),
(2, 4, 'FamilyB'),
(7, 2, 'FamilyC');
And I want to write a function that will return a text string representing the path from the a given name to the desired root
My Attempt:
CREATE FUNCTION LineageTable (input VARCHAR(50))
RETURNS TABLE (input VARCHAR(50))
AS $$
BEGIN
RETURN QUERY
SELECT input
FROM LineageTable1
INNER JOIN LineageTable ON LineageTable.parent = LineageTable.id
WHERE LineageTable1.genus_name = LineageTable1.genus_name;
END $$
However, I am confused as how to iterate through this table multiple times to string the path together properly. Any ideas? Thanks all!

On Postgres you can use a RECURSIVE query:
WITH RECURSIVE Rec as
(
SELECT id, parent_id, Name
FROM Hierarchy
WHERE Name = 'Sirenia'
UNION ALL
SELECT Hierarchy.id, Hierarchy.parent_id, Hierarchy.Name
FROM Hierarchy
INNER JOIN Rec
ON Hierarchy.id = Rec.parent_Id
)
SELECT string_agg(Name, '->') path
FROM Rec;
| path |
|:---------------------------------:|
| Sirenia->Paenungulata->Afrotheria |
Rextester here

Related

Table Variable and Table-Valued Function equivalent in PostgreSQL

I need to create a function in PostgreSQL for the following :
Query multiple tables based on a business logic (all result sets return the same type of data)
Compile all result sets into one table and return that table
Is it possible to accomplish this without using the temp tables in PostgreSQL?
I currently do this in Microsoft SQL server using Table Variables, below is a sample function:
create FUNCTION test(#search_in nvarchar(500))
RETURNS #data_table TABLE
(
item_id int,
item_type nvarchar(1),
first_name nvarchar(100),
last_name nvarchar(100))
) AS
BEGIN
-- from first table
if charindex('search_in_authors', #search_in) > 0
insert into #data_table
select item_id, 'a', first_name, last_name
from authors
where first_name = 'james'
-- from second table
if charindex('search_in_editors', #search_in) > 0
insert into #data_table
select item_id, 'e', first_name, last_name
from editors
where first_name = 'james'
-- from third table
if charindex('search_in_publishers', #search_in) > 0
insert into #data_table
select item_id, 'p', first_name, last_name
from publishes
where first_name = 'james'
-- there could be more like these based on the business logic...
(...)
-- finally return the records compiled in #data_table
RETURN
END
Sample calls to the function:
select * from dbo.test('search_in_authors')
select * from dbo.test('search_in_authors, search_in_editors')
select * from dbo.test('search_in_authors, search_in_editors,search_in_publishers ')
Are there any options in PostgreSQL to achieve this other than using a temp table ?
Thanks,
San
You can use RETURN QUERY to add the result of various queries to the output.
CREATE OR REPLACE FUNCTION public.testf()
RETURNS TABLE(id INTEGER, name text)
STABLE
AS $$
DECLARE
BEGIN
RETURN QUERY select 1 as id, 'abc' as name;
RETURN QUERY select 2 as id, 'def' as name;
RETURN QUERY select 3 as id, 'xyz' as name;
-- Final return as set is now complete.
return;
END;
$$ LANGUAGE plpgsql;
select * from public.testf();
id | name
----+------
1 | abc
2 | def
3 | xyz
(3 rows)

Create one json per one table row

I would like to create jsons from the data in the table.
Table looks like that:
|code |
+------+
|D5ABX0|
|MKT536|
|WAEX44|
I am using FOR JSON PATH which is nice:
SELECT [code]
FROM feature
FOR JSON PATH
but the return value of this query are three concatenated jsons in one row:
|JSON_F52E2B61-18A1-11d1-B105-00805F49916B |
+----------------------------------------------------------+
1 |[{"code":"D5ABX0"},{"code":"MKT536"},{"code":"WAEX44"}]|
I need to have each row to be a separate json, like that:
|JSON_return |
+---------------------+
1 |{"code":"D5ABX0"} |
2 |{"code":"MKT536"} |
3 |{"code":"WAEX44"} |
I was trying to use splitting function (CROSS APPLY) which needs to have a separator as a parameter but this is not a robust solution as the json could be more expanded or branched and this could separate not the whole json but the json inside the json:
;WITH split AS (
SELECT [json] = (SELECT code FROM feature FOR JSON PATH)
)
SELECT
T.StringElement
FROM split S
CROSS APPLY dbo.fnSplitDelimitedList([json], '},{') T
The output is:
|StringElement |
+---------------------+
1 |[{"code":"D5ABX0" |
2 |"code":"MKT536" |
3 |"code":"WAEX44"}] |
Is there a way to force sqlserver to create one json per row?
You'll need to use as subquery to achieve this; FOR JSON will create a JSON string for the entire returned dataset. This should get you what you're after:
CREATE TABLE #Sample (code varchar(6));
INSERT INTO #Sample
VALUES ('D5ABX0'),
('MKT536'),
('WAEX44');
SELECT (SELECT Code
FROM #Sample sq
WHERE sq.code = S.code
FOR JSON PATH)
FROM #Sample S;
DROP TABLE #Sample;
CREATE TABLE #Temp
(
ID INT IDENTITY(1, 1) ,
StringValue NVARCHAR(100)
);
INSERT INTO #Temp
( StringValue )
VALUES ( N'D5ABX0' -- StringValue - nvarchar(100)
),
( 'MKT536' ),
( 'WAEX44' );
SELECT ID,'[{"code:":'''''+StringValue+'''''}]' AS JSON_return FROM #Temp
DROP TABLE #Temp

How to get result of LEFT join into another table as one field

Not sure how to describe this so I will show example:
table PAGES
id int
parent int
name nvarchar
status tinyint
table PAGES_MODULES
id int
id_parent int
module_type nvarchar
module_id int
status int
One page can have more than one linked modules. Example records:
id parent name status
1 -1 Xyz 1
2 -1 Yqw 1
id id_parent module_type module_id status
1 1 ARTICLE 1 1
2 1 GALLERY 2 1
3 2 CATEGORY 3 1
What I need is to create select which will not return 2 results if I do select left join page_modules.
I would like to have select which returns linked modules as this:
id parent name status modules
1 -1 Xyz 1 ARTICLE GALLERY
2 -1 Yqw 1 CATEGORY
Is that possible?
Thanks.
UPDATE
I have tried COALESE, CROSS APPLY and SELECT within SELECT methods and came to these conclusions:
http://blog.feronovak.com/2011/10/multiple-values-in-one-column-aka.html
Hope I can publish these here, not meaning to spam or something.
You'd need to create a custom aggregate function that could concatenate the strings together, there is no built-in SQL Server function that does this.
You can create a custom aggregate function (assuming your using the latest version of SQL) using a .Net assembly. Here's the MS reference on how to do this (the example in the article is actually for a CONCATENATE function just like you require): http://msdn.microsoft.com/en-us/library/ms182741.aspx
Use group_concat() to smoosh multiple rows' worth of data into a single field like that. Note that it does have a length limit (1024 chars by default), so if you're going to have a zillion records being group_concatted, you'll only get the first few lines worth unless you raise the limit.
SELECT ..., GROUP_CONCAT(modules SEPARATOR ' ')
FROM ...
GROUP BY ...
Note that it IS an aggregate function, so you must have a group-by clause.
-- ==================
-- sample data
-- ==================
declare #pages table
(
id int,
parent int,
name nvarchar(max),
status tinyint
)
declare #pages_modules table
(
id int,
id_parent int,
module_type nvarchar(max),
module_id int,
status int
)
insert into #pages values (1, -1, 'Xyz', 1)
insert into #pages values (2, -1, 'Yqw', 1)
insert into #pages_modules values (1, 1, 'ARTICLE', 1, 1)
insert into #pages_modules values (2, 1, 'GALLERY', 2, 1)
insert into #pages_modules values (3, 2, 'CATEGORY', 3, 1)
-- ==================
-- solution
-- ==================
select
*,
modules = (
select module_type + ' ' from #pages_modules pm
where pm.id_parent = p.id
for xml path('')
)
from #pages p
You need to join both tables and then GROUP BY by pages.id, pages.parent, pages.status, pages.name and pages.status. Your modules field in your resultset is then a string aggregate function, i.e in Oracle LISTAGG(pages_modules.modules, ' ') as modules.

How can I use the values Selected in a While Exists statement inside the While loop?

I'm new-ish to SQL and am trying to figure out how to use the values from the Select statement in a While Exists conditional loop. The purpose is to combine multiple occurences of an attribute for a Document into a single field, and later pivot and join those results to the Document record.
For example, three tables exist like so:
ATTRIBUTES TABLE
ID, ATTRIBUTE_NAME
---------------------------
1, Created
2, Embedded_Image
...
ATTRIBUTE_VALUES TABLE
ATTRIBUTE_ID, VALUE, DOC_ID
-------------------------------------------
1, 2010/11/01, 1
2, 'Home.png', 1
2, 'Castle.png', 1
2, 'Apartment.jpg', 1
1, 2008/06/23, 2
2, 'Ski Jump.jpg', 2
2, 'Snowboarding.png', 2
...
DOCUMENTS TABLE
ID, TEXT
---------------------------
1, 'Homes of the ...'
2, 'Winter sports ...'
...
So a final Pivot and Join of the tables would look like so:
DOC_ID, TEXT, Created, Embedded_Image
----------------------------------------------------------------------------------------
1, 'Homes of the ...', 2010/11/01, 'Home.png,Castle.png,Apartment.jpg'
2, 'Winter sports ...', 2008/06/23, 'Ski Jump.jpg, Snowboarding.png'
The SQL While Exists condition I've tried to write looks like so:
DECLARE #LOOP_DOC_ID UNIQUEIDENTIFIER
DECLARE #LOOP_ATTRIBUTE_NAME NVARCHAR(MAX)
WHILE EXISTS(
SELECT [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME]
FROM ([dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID])
)
BEGIN
SET #LOOP_DOC_ID = DOC_ID
SET #LOOP_ATTRIBUTE_NAME = ATTRIBUTE_NAME
SELECT STUFF(
(
SELECT DISTINCT ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FROM
(
[dbo].[ATTRIBUTE_VALUES] INNER JOIN [dbo].[ATTRIBUTES]
ON [dbo].[ATTRIBUTE_VALUES].[ATTRIBUTE_ID] = [dbo].[ATTRIBUTES].[ID]
)
WHERE [dbo].[ATTRIBUTE_VALUES].[DOC_ID] = #LOOP_DOC_ID
AND [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] = #LOOP_ATTRIBUTE_NAME
ORDER BY ',' + RTRIM(LTRIM([dbo].[ATTRIBUTE_VALUES].[VALUE]))
FOR XML PATH('')
), 1, 2, ''
) AS VALUE, #LOOP_DOC_ID AS DOC_ID, #LOOP_ATTRIBUTE_NAME AS ATTRIBUTE_NAME
END
SQL Server doesn't like the lines where I'm trying to SET the variables to the values from the Select statement in the While Exists condition.
How can I use the [dbo].[ATTRIBUTE_VALUES].[DOC_ID], [dbo].[ATTRIBUTES].[ATTRIBUTE_NAME] values Selected in the While Exists conditional statement between the BEGIN and END statements?
Preferrably I would like to do away with the #LOOP_DOC_ID and #LOOP_ATTRIBUTE_NAME variables and deal directly with the values.
I've looked through forums that have talked about using Cursors to solve similar problems, but each one of them seem to recommend only using Cursors as a last resort due to their lack of speed. I've also seen some people use stored procedures, but I can't use those, since my boss has ruled those as off-limits. Am I in need of a Cursor, or is there a better way to do this?
Have a look at something like this (Full Example)
DECLARE #ATTRIBUTES TABLE(
ID INT,
ATTRIBUTE_NAME VARCHAR(100)
)
INSERT INTO #ATTRIBUTES SELECT 1, 'Created'
INSERT INTO #ATTRIBUTES SELECT 2, 'Embedded_Image'
DECLARE #ATTRIBUTE_VALUES TABLE(
ATTRIBUTE_ID INT,
VALUE VARCHAR(100),
DOC_ID INT
)
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2010/11/01', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Home.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Castle.png', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Apartment.jpg', 1
INSERT INTO #ATTRIBUTE_VALUES SELECT 1, '2008/06/23', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Ski Jump.jpg', 2
INSERT INTO #ATTRIBUTE_VALUES SELECT 2, 'Snowboarding.png', 2
DECLARE #DOCUMENTS TABLE(
ID INT,
[TEXT] VARCHAR(100)
)
INSERT INTO #DOCUMENTS SELECT 1, 'Homes of the ...'
INSERT INTO #DOCUMENTS SELECT 2, 'Winter sports ...'
;WITH Vals AS (
SELECT d.ID DOC_ID,
d.[TEXT] [TEXT],
a.ATTRIBUTE_NAME,
av.VALUE
FROM #DOCUMENTS d INNER JOIN
#ATTRIBUTE_VALUES av ON d.ID = av.DOC_ID INNER JOIN
#ATTRIBUTES a ON av.ATTRIBUTE_ID = a.ID
)
SELECT *
FROM (
SELECT DOC_ID,
[TEXT],
ATTRIBUTE_NAME,
stuff(
(
select ',' + t.VALUE
from Vals t
where t.DOC_ID = v.DOC_ID
AND t.ATTRIBUTE_NAME = v.ATTRIBUTE_NAME
order by t.VALUE
for xml path('')
),1,1,'') Concats
FROM Vals v
GROUP BY DOC_ID,
[TEXT],
ATTRIBUTE_NAME
) s
PIVOT ( MAX(ConCats) FOR ATTRIBUTE_NAME IN ([Created],[Embedded_Image])) pvt
Output
DOC_ID TEXT Created Embedded_Image
1 Homes of the ... 2010/11/01 Apartment.jpg,Castle.png,Home.png
2 Winter sports ... 2008/06/23 Ski Jump.jpg,Snowboarding.png
From your sample, and with support from common sense, I venture the hypothesis that
A document has a single creation date.
A document can have many embedded images.
So pivoting on creation date is straightforward:
SELECT DOC_ID
, VALUE AS Created
FROM ATTRIBUTE_VALUES
WHERE ATTRIBUTE_ID = 1
and joining this subquery to your Documents table gives you the first three columns of your desired output.
Your final column summarizes multiple embedded images for each document. I personally would use some standard reporting tool (e.g. MS Access or Crystal Reports). Alternatively, create a new empty table with your four desired columns, populate the first three columns with a SQL INSERT statement, and then have Perl (or C#, or your favorite declarative language) query for the embedded images of each document, concatenate the results with commas, and insert the concatenation into your fourth column.
But if you want to do it in SQL, the concatenate-multiple-values question has been asked here before, e.g. in How to create a SQL Server function to "join" multiple rows from a subquery into a single delimited field?.

Comma separated Values

How can i fetch this query using mysql?
Table1:
id : nos
1 12,13,14
2 14
3 14,12
Table2:
id : values
12 PHP
13 JAVA
14 C++
Now , I want output like this:
1 PHP, JAVA, C++
2 C++
3 C++, PHP
Not tested but it should be something like this:
SELECT table1.id, GROUP_CONCAT(table2.values)
FROM table1 INNER JOIN table2 ON FIND_IN_SET(table2.id, table1.nos)
GROUP BY table1.id
There's no way that I know of to achieve that in SQL. You should instead have a 1 to N relationship to represent those lists. Something like:
Table 1: (just ids)
1
2
3
Table 1.1: (map ids to values in their list)
1, 12
1, 13
1, 14
2, 14
3, 14
3, 12
Not sure if this will work in mySQL but in SqlServer you could create a function:
create function dbo.replaceIdsWithValues
(
#inIds varchar(50)
)
returns varchar(50)
as
begin
declare #ret as varchar(50)
set #ret = #inIds
select #ret = replace(#ret,cast(id as varchar),theValues) from t2
return #ret
end
and then simply call:
select id, nos, dbo.replaceIdsWithValues(nos) from t1
that assuming your tables structure:
create table t1 (id int, nos varchar(50))
create table t2 (id int, theValues varchar(50))
You can test the full example
create table t1 (id int, nos varchar(50))
create table t2 (id int, theValues varchar(50))
insert into t1(id, nos)
select 1, '12,13,14'
union all select 2, '14'
union all select 3, '14,12'
insert into t2(id, theValues)
select 12, 'PHP'
union all select 13, 'JAVA'
union all select 14, 'C++'
select id, nos, dbo.replaceIdsWithValues(nos) from t1
Intended this as comment but it is getting long.
SoulMerge answer(+1) is specific to MySql, which the question was intially intended. Please see the edits for the initial question.
Seems the question again got edited for the MY-SQL, but anyway.
While you can achieve this in MS SQL by using PATINDEX, I am not sure you can do it this in oracle.
I think it would be better to restructure the tables as suggested by jo227o-da-silva(+1).
Although not completely relevant to the subject (MySQL), but will help others finding the question by title, in MSSQL server this can be achived using the FOR XML hint and some nasty string replacements.
I'll post up some code when I find it...