I've got a table structure like (simplified):
content
- id_content
- my_string1
- ...
content_has_content
- id_content
- id_subcontent
topic_has_content
- id_topic
- id_content
Any topic can have multiple 'content', any 'content' can have multiple 'subcontent' (instance of content).
With a given id_topic I'd like to recieve a list of all my_string1 from the linked contents, subcontents, subcontents-of-subcontents, and so on.
I understand "WITH" is not working for mysql but cannot find a nice recursive alternative.
Thanks
daniel
There is no recursion in MySQL and also the result you would get would be flat (no structure). The best way is still a while loop in PHP, Java or whatever programming language you use.
The query could look like this:
SELECT C.*, CHC.ID_SUBCONTENT
FROM CONTENT C
LEFT OUTER JOIN CONTENT_HAS_CONTENT CHC ON CHC.ID_CONTENT = C.ID_CONTENT
WHERE C.ID = ?
... // you get the idea
and in PHP you could repeat the query with the next sub_content_id, until ID_SUBCONTENT is null
Solution
PosgreSQL, Oracle, MS-SQL, ... have WITH RECURSIVE to handle such data structures. It internally uses a while loop to get all the parent ids for the current row (we need the child's here instead)
This can achieved in MySQL too, you can create a stored procedure and reproduce the same behavior
Assumed/DDL used
content_has_content has entries as
content_1
content_2
content_3
content_4
I pass the id_content as 2 and get the output as
content_2
content_3
content_4
All of the above are descendants of content_2
Fire call content_details(2); to get all the child rows for the passed id_content
SQL
###### Stored Routine
DELIMITER //
drop procedure IF EXISTS `content_details`;
CREATE DEFINER=`root`#`localhost` PROCEDURE `content_details`(`_idcontent` INT)
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE temp_content_ids varchar(200);
DECLARE idcontent, idcontent2 integer;
SET temp_content_ids= _idcontent;
SET idcontent = _idcontent;
SET idcontent2 = _idcontent;
WHILE idcontent2 IS NOT NULL DO
SET idcontent = NULL;
SELECT id_subcontent, CONCAT(id_subcontent,',',temp_content_ids) INTO idcontent, temp_content_ids FROM content_has_content WHERE id_content = idcontent2;
SET idcontent2 = idcontent;
END WHILE;
SELECT my_string1 FROM content WHERE FIND_IN_SET( id, temp_content_ids );
END//
What I basically do is run a while loop until I have the last child_id, store these ids in a comma separated string format and then fire a query to get all the rows which have a id present in the var I just created
Note: There are chances you could be having invalid values within your tables, such as row having a id_subcontent, which points to its own id_content, which could cause a never ending loop within the procedure, to avoid such situations, you can use a counter and limit the nesting to say around 50 (or any value as per your requirement) and raise a exception if that limit is surpassed
Some data to play with..
###### DLL Statements
CREATE TABLE content
( id_content int,
my_string1 varchar(200));
CREATE TABLE content_has_content
( id_content int,
id_subcontent int);
CREATE TABLE topic_has_content
( id_topic int,
id_content int);
INSERT INTO content VALUES (1, 'content_1'), (2, 'content_2'), (3, 'content_3'), (4, 'content_4');
INSERT INTO content_has_content VALUES (1, 2), (2, 3), (3, 4);
INSERT INTO topic_has_content VALUES (1, 1);
Hope this helps..
Related
I am able to create table, shred JSON and add data if it does not exist in SQL Server:
DECLARE #json nvarchar(max);
SET #json = N'[{"IplayerName": "Pilipiliz",
"Sname": "kikombe",
"WeightLBs":"60.236"
}]'
IF NOT EXISTS (SELECT * FROM sys.objects WHERE OBJECT_ID = object_id('Iplayerds'))
BEGIN
SELECT
[IplayerName],
[Sname],
[WeightLBs]
INTO
Iplayerds
FROM
OPENJSON(#json)
WITH (IplayerName NVARCHAR(200),
Sname NVARCHAR(20),
WeightLBs DECIMAL(10,4)
)
END
ELSE
PRINT 'exists'
However, when I try to replace the print statement with insert rows code shown below, it fails
INSERT INTO Iplayerds (IplayerName, Sname, WeightLBs)
VALUES ([IplayerName], [Sname], [WeightLBs]
FROM OPENJSON(#json))
What am I doing wrong?
The INSERT command comes in two flavors:
(1) either you have all your values available, as literals or SQL Server variables - in that case, you can use the INSERT .. VALUES() approach:
INSERT INTO dbo.Iplayerds (IplayerName, Sname, WeightLBs)
VALUES (#IplayerName, #Sname, #WeightLBs)
Note: I would recommend to always explicitly specify the list of column to insert data into - that way, you won't have any nasty surprises if suddenly your table has an extra column, or if your tables has an IDENTITY or computed column. Yes - it's a tiny bit more work - once - but then you have your INSERT statement as solid as it can be and you won't have to constantly fiddle around with it if your table changes.
(2) if you don't have all your values as literals and/or variables, but instead you want to rely on another table, multiple tables, or views, to provide the values, then you can use the INSERT ... SELECT ... approach:
INSERT INTO dbo.Iplayerds (IplayerName, Sname, WeightLBs)
SELECT
[IplayerName], [Sname], [WeightLBs]
FROM
OPENJSON(#json) WITH (IplayerName NVARCHAR(200),
Sname NVARCHAR(20),
WeightLBs DECIMAL(10,4)
)
Here, you must define exactly as many items in the SELECT as your INSERT expects - and those can be columns from the table(s) (or view(s)), or those can be literals or variables. Again: explicitly provide the list of columns to insert into - see above.
You can use one or the other - but you cannot mix the two - you cannot use VALUES(...) and then have a SELECT query in the middle of your list of values - pick one of the two - stick with it.
Say I have a column in my database called attributes which has this value as an example:
{"pages":["Page1"]}
How can I do a where clause so I can filter down rows that have "Page1" in it.
select JSON_QUERY(Attributes, '$.pages')
from Table
where JSON_QUERY(Attributes, '$.pages') in ('Page1')
Edit:
From the docs it seems like this might work though it seems so complicated for what it is doing.
select count(*)
from T c
cross apply Openjson(c.Attributes)
with (pages nvarchar(max) '$.pages' as json)
outer apply openjson(pages)
with ([page] nvarchar(100) '$')
where [page] = 'Page1'
Something like this:
use tempdb
create table T(id int, Attributes nvarchar(max))
insert into T(id,Attributes) values (1, '{"pages":["Page1"]}')
insert into T(id,Attributes) values (2, '{"pages":["Page3","Page4"]}')
insert into T(id,Attributes) values (3, '{"pages":["Page3","Page1"]}')
select *
from T
where exists
(
select *
from openjson(T.Attributes,'$.pages')
where value = 'Page1'
)
returns
id Attributes
----------- ---------------------------
1 {"pages":["Page1"]}
3 {"pages":["Page3","Page1"]}
(2 rows affected)
I am able to execute my stored procedure. When I execute it a second time instead of updating the existing values same values from source are inserted as new values.
i.e my target has
1
2
3
When I run the stored procedure a second time, instead of updating 1,2,3, it is inserting the same
1
2
3
1
2
3
My condition for when matched then select S.REPORT_TEST1 except T.REPORT_TEST1 is not working.
When I use the same code on a different table which doesn't have data conversions I am able to update.
Can anyone tell where am I going wrong?
CREATE PROCEDURE [dbo].[Merge]
INSERT INTO .[dbo].[TARGET](REPORT_TEST1, REPORT_TEST2, REPOST_TEST3)
FROM (MERGE [dbo].[TARGET] T
USING (SELECT
Cast([REPORT TEST1] as int) [REPORT_TEST1],
Cast([REPORT TEST2] as int) [REPORT_TEST2],
Cast([REPORT TEST3] as int) [REPORT_TEST3]
FROM
[dbo].[SOURCE]) S ON (T.[REPORT_TEST1] = S.[REPORT_TEST1])
WHEN NOT MATCHED BY TARGET
THEN INSERT
VALUES (S.REPORT_TEST1, S.REPORT_TEST2, S.REPOST_TEST3)
WHEN MATCHED
AND EXISTS (SELECT S.REPORT_TEST1, S.REPORT_TEST2, S.REPOST_TEST3
EXCEPT
SELECT T.REPORT_TEST1, T.REPORT_TEST2, T.REPOST_TEST3)
OUTPUT $ACTION ACTION_OUT,
S.REPORT_TEST1, S.REPORT_TEST2, S.REPOST_TEST3) ;
Thanks
would it not suffice to rewrite your WHEN MATCHED statement thusly:
WHEN MATCHED
AND S.REPORT_TEST2 <> T.REPORT_TEST2
AND S.REPORT_TEST3 <> T.REPORT_TEST3
(
SELECT
S.REPORT_TEST1
,S.REPORT_TEST2
,S.REPOST_TEST3
)
I think I understand what you're trying to do, but inside the MERGE context, you're only comparing this row with that row, not the source row against the whole target table. you could modify the subselect thusly if you're trying to query "this source is not at all in the target"
WHEN MATCHED AND EXISTS
(
SELECT
S.REPORT_TEST1
,S.REPORT_TEST2
,S.REPOST_TEST3
EXCEPT SELECT
T2.REPORT_TEST1
,T2.REPORT_TEST2
,T2.REPOST_TEST3
FROM
[dbo].[TARGET] T2
)
At the moment I'm working with databses and I got a strange error wirth procedures. I have create one which gets three parameters of the type VARCHAR: title, interpret, album. Here is the code:
BEGIN
INSERT IGNORE INTO Interpret (Name) VALUES (interpret);
SET #idInterpret := (SELECT id FROM Interpret WHERE Name = interpret);
INSERT IGNORE INTO Album (Name, Interpret) VALUES (album, #idInterpret);
SET #idAlbum := (SELECT id FROM Album WHERE Name = album AND Interpret = #idInterpret);
INSERT INTO Lied (Name, Album, Interpret) VALUES (title, #idAlbum, #idInterpret);
END
If I start this procedure I get an error which says that the album field can not be null (which is right) but it shouldn't be null because I read the value from the table above. If I call exact the same lines of SQL with real data (not as procedure with varaibles) all works great. Do you have any ideas why this happens?
Avoid naming variables and parameter as columns of your tables.
In your query:
SET #`idAlbum` := (SELECT `id`
FROM `Album`
WHERE `Name` = `album` AND `Interpret` = #`idInterpret`);
Interpret, are you referring to the parameter or column of the table?. We know that is column, MySQL interprets is the parameter.
SQL Fiddle demo
See:
13.6.4.2 Local Variable Scope and Resolution
Name Conflicts within Stored Routines in D.1 Restrictions on Stored Programs
Follow the comment #Bernd-Buffen using local varibales for this case.
To use vars in a Stored Procedure you must DECLARE it and then you
can use them without quotes. I have changed your Query, but not tested.
BEGIN
DECLARE idInterpret DEFAULT ='';
DECLARE idAlbum DEFAULT ='';
INSERT IGNORE INTO Interpret (Name) VALUES (interpret);
SELECT id IN TO idInterpret FROM Interpret WHERE Name = interpret;
INSERT IGNORE INTO Album (Name, Interpret) VALUES (album, idInterpret);
SELECT id INTO idAlbum FROM Album WHERE Name = album AND Interpret = idInterpret;
INSERT INTO Lied (Name, Album, Interpret) VALUES (title, idAlbum, idInterpret);
END
Im trying to generate a bunch of discounts codes. These have foreign key contraints.
At the moment I have
INSERT INTO code (title, code, desc) VALUES ('code1','XPISY9','test code');
INSERT INTO code_details (code_id, used, attempts) VALUES (
SELECT code_id from code where code = 'XPISY9',0,0);
The code_id in code_details is a foreign key for code_id in the code table.
What would be the best way to create a loop where I can generate a set of these values (around 100). I would need the code to be a not repeating random value.
Any help would be appreciated.
THanks
Once you have your 100 or so records in the code table, you can add the code details in one statement:
INSERT INTO code_details (code_id, used, attempts)
SELECT code, 0, 0
FROM code;
Generating the code records in the first place is another matter and is probably best done by using some other tool to generate 100 insert statements in a text file which you can then execute.
I've seen that done with every scripting language possible - chose your favourite. I've even seen Excel used with a column for the id and string formula to generate the insert statements.
Thanks for the help guys. I decided to put together a procedure for this and it worked great:
DELIMITER $$
CREATE PROCEDURE vouchergen(IN length INT(10) ,IN duration VARCHAR(20),IN sponsor VARCHAR(20),IN amount INT(10))
BEGIN
DECLARE i INT DEFAULT 1;
WHILE (i< amount) DO
SET #vcode= CONCAT(BINARY brand , UPPER(SUBSTRING(MD5(RAND()) FROM 1 FOR 6)));
INSERT INTO code (title, code, desc) VALUES (CONCAT(brand,i),#vcode,CONCAT(length,' ',duration));
INSERT INTO code_details (code_id, used, attempts) VALUES (
SELECT code_id from code where code = #vcode,0,0);
SET i=i+1;
END WHILE;
END$$;
I can then call this to gen however many i want:
CALL vouchergen(1,'week',APPL,400);
CALL vouchergen(1,'month',APPL,100);
CALL vouchergen(1,'day',APPL,200);