Below is what I have in my table.
myTable
++++++++++++++++++++
Parent + Child
++++++++++++++++++++
C1 + G1
C1 + G2
C1 + G3
G3 + D1
G3 + D2
C1 + G4
G4 + D3
G4 + D4
C2 + G5
C2 + G6
C2 + G7
C2 + G8
++++++++++++++++++++
What, I want is as below using MYSQL.
C1
G1
G2
G3
D1
D2
G4
D3
D4
C2
G5
G6
G7
G8
Please let me know if this is possible in MYSQL. The output is something like TREE.
Update 1
If I get new table like below is also fine so that I can use this example.
++++++++++++++++++++++++++++++++++++++++
Parent + Child + PLevel + CLevel
++++++++++++++++++++++++++++++++++++++++
C1 + G1 + 1 + 2
C1 + G2 + 1 + 2
C1 + G3 + 1 + 2
G3 + D1 + 2 + 3
G3 + D2 + 2 + 3
C1 + G4 + 1 + 2
G4 + D3 + 2 + 3
G4 + D4 + 2 + 3
C2 + G5 + 1 + 2
C2 + G6 + 1 + 2
C2 + G7 + 1 + 2
C2 + G8 + 1 + 2
++++++++++++++++++++++++++++++++++++++++
NOTE : I have started level with 1 (in example I have level starting from 0). If I get this new table with level starting from 0 is also fine.
Although you can't do with a single query, you can do with a stored procedure... The only pre-requirement, you need to add 2 more records to your existing sample table to represent that "C1" and "C2" ARE the top level... Add a record where the "Parent" field is blank, and the child level is "C1" and another for "C2". This will "prepare" the top-most parent level. for subsequent hierarchy association, otherwise you have no starting "basis" of the top-level hierarchy. It also requires a "primary key" column (which I've created in this script as "IDMyTable" which is just 1-x sequential, but would assume you have an auto-increment column on your table to use instead).
I've included all the output columns to show HOW it's built, but the premise of this routine is to create a table based on the expected column outputs, yet extra to hold the hierarchical representation downstream as it's being built. To MAKE SURE they retain the correct orientation as the layers get deeper, I'm concatinating the "ID" column -- you'll see how it works in the final result set.
Then, in the final result set, I am pre-padding spaces based on however deep the hierarchy data is.
The loop will add any records based on their parent being found in the preceding result set, but only if the ID has not already been added (prevent duplicates)...
To see how the cyclical order was constantly appended to, you can run the last query WITHOUT the order by and see how each iteration qualified and added the previous hierarchy level was applied...
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetHierarchy2`()
BEGIN
-- prepare a hierarchy level variable
set #hierlvl := 00000;
-- prepare a variable for total rows so we know when no more rows found
set #lastRowCount := 0;
-- pre-drop temp table
drop table if exists MyHierarchy;
-- now, create it as the first level you want...
-- ie: a specific top level of all "no parent" entries
-- or parameterize the function and ask for a specific "ID".
-- add extra column as flag for next set of ID's to load into this.
create table MyHierarchy as
select
t1.IDMyTable,
t1.Child AS Parent,
#hierlvl as IDHierLevel,
cast( t1.IDMyTable as char(100)) FullHierarchy
from
MyTable t1
where
t1.Parent is null
OR t1.Parent = '';
-- how many rows are we starting with at this tier level
set #lastRowCount := ROW_COUNT();
-- we need to have a "primary key", otherwise our UPDATE
-- statement will nag about an unsafe update command
alter table MyHierarchy add primary key (IDMyTable);
-- NOW, keep cycling through until we get no more records
while #lastRowCount > 0 do
-- NOW, load in all entries found from full-set NOT already processed
insert into MyHierarchy
select
t1.IDMyTable,
t1.Child as Parent,
h1.IDHierLevel +1 as IDHierLevel,
concat_ws( ',', h1.FullHierarchy, t1.IDMyTable ) as FullHierarchy
from
MyTable t1
join MyHierarchy h1
on t1.Parent = h1.Parent
left join
MyHierarchy h2
on t1.IDMyTable = h2.IDMyTable
where
h2.IDMyTable is null;
set #lastRowCount := row_count();
-- now, update the hierarchy level
set #hierLevel := #hierLevel +1;
end while;
-- return the final set now
select
*, concat( lpad( ' ', 1 + (IDHierLevel * 3 ), ' ' ), Parent ) as ShowHierarchy
from MyHierarchy
order by FullHierarchy;
END
MySQL and RDBMS's in general are not great at this sort of structure. You'll probably have to use client-side recursion to do this.
If the recursion is limited to just three deep, like your example, you can do it with joins, but it's not very scalable for deeper trees.
first Create Recursive function for calc level.
function fn_CalcLevel(int #ID)
As Begin
Declare #ParentID int
Select #ParentID = ParentID From Table1 where ID = #ID
IF (#ParentID IS NULL) Return 1 Else Return 1+fn_CalcLevel(#ParentID)
End
then Create your Query Such as Below
Select *, fn_CalcLevel(Table1.ID) as Level
From Table1
If you restructured your table a bit you could use something like:
SELECT Child,CONCAT(LPAD('',Clevel,' '),Child),etc from tablename
The restructuring is that you would need the root node in as a row with parent node of 0. You can add your own ordering with parent / child / C level to get the sequence as desired.
I know this is from a few years back, but it might save someone else some effort!
Related
As example of data from CSV, I have a row:
1;75353;CWB;114#389#115#381#11#382#117#78#118#384;1244;13;4727;15
I would like to get something like that:
1;75353;CWB;114;1244;13;4727;15
1;75353;CWB;389;1244;13;4727;15
1;75353;CWB;115;1244;13;4727;15
1;75353;CWB;381;1244;13;4727;15
1;75353;CWB;11;1244;13;4727;15
1;75353;CWB;382;1244;13;4727;15
1;75353;CWB;117;1244;13;4727;15
1;75353;CWB;78;1244;13;4727;15
1;75353;CWB;118;1244;13;4727;15
1;75353;CWB;384;1244;13;4727;15
I have tried to use (I'm using SQLITE, but I can use MariaDB if needed, also):
select dt2.zone_number, dt2.section_num, dt1.zone, dt1.section, dt1.local, dt1.local_name, dt1.address, dt1.neighbor
from datatable01 as dt1, datatable02 as dt2 where
(
dt1.zone = dt2.zone_number and
instr(dt2.section_num, dt1.section) > 0
)
order by dt1.zone, dt1.local
But... this capture any case. From datatable02, if dt1.section = 11, it's capture 114 from dt2.section_num (example above)
I have tried to use regexp '[0-9]{2,3}', but I got error (syntax error).
Any suggestion to solve it?
Thank you.
I believe the following will do what you want or could be the basis of what you want :-
WITH midparts(w, s) AS (
SELECT '',
/* extract the mid-part i.e. from after CBW; to the next ; */
substr(substr(row1,instr(row1,'CWB;')+length('CWB;')),1,instr(substr(row1,instr(row1,'CWB;')+length('CWB;')),';')-1)||'#'
FROM t01
UNION ALL SELECT
substr(s, 0, instr(s, '#')),
substr(s, instr(s, '#')+1)
FROM midparts WHERE s!=''
),
/* extract the start and end (prefix and suffix) */
startandend(prefix,suffix) AS (
SELECT
substr(row1,1,instr(row1,'CWB;') + length('CWB;') -1),
substr(substr(row1,instr(row1,'CWB;')+length('CWB;')),instr(substr(row1,instr(row1,'CWB;')+length('CWB;')),';'))
FROM t01
)
SELECT (SELECT prefix FROM startandend)||w||(SELECT suffix FROM startandend) AS generated FROM midparts WHERE w!='';
noting that the table with the data has been named t01 and that the row containing the data is row1
Example :-
The above was tested using :-
DROP TABLE IF EXISTS t01;
CREATE TABLE IF NOT EXISTS t01 (row1);
INSERT INTO t01 VALUES('1;75353;CWB;114#389#115#381#11#382#117#78#118#384;1244;13;4727;15');
WITH midparts(w, s) AS (
SELECT '',
/* extract the mid-part i.e. from after CBW; to the next ; */
substr(substr(row1,instr(row1,'CWB;')+length('CWB;')),1,instr(substr(row1,instr(row1,'CWB;')+length('CWB;')),';')-1)||'#'
FROM t01
UNION ALL SELECT
substr(s, 0, instr(s, '#')),
substr(s, instr(s, '#')+1)
FROM midparts WHERE s!=''
),
/* extract the start and end (prefix and suffix) */
startandend(prefix,suffix) AS (
SELECT
substr(row1,1,instr(row1,'CWB;') + length('CWB;') -1),
substr(substr(row1,instr(row1,'CWB;')+length('CWB;')),instr(substr(row1,instr(row1,'CWB;')+length('CWB;')),';'))
FROM t01
)
SELECT (SELECT prefix FROM startandend)||w||(SELECT suffix FROM startandend) AS generated FROM midparts WHERE w!='';
Result :-
How should i write procedure which will check next free number (which is string) based on input.
Procedure should have 2 input value, first one is user input (numbers) and second one is maximum amount of characters in string.
This is procedure i tried to write:
CREATE DEFINER=`root`#`localhost` PROCEDURE `getfreenum`(IN num CHAR(20), IN maxval CHAR(20))
begin
set #compare := (num + num *10);
set #maxId := (select sifra from artikli where sifra >= #compare order by sifra asc limit 1);
while #compare = #maxId do
set #compare := #compare + 1;
set #maxId = (select sifra from artikli where sifra >= #compare order by sifra asc limit 1);
end while;
select #compare;
end
This procedure finds me next available value after my input but it does not include my input in that number, meaning if i call procedure Call getfreenum(1,5) i get value 779 but i should get next 5 char value which includes input number, which is 1.
So a Call getfreenum(1,5) procedure should return 10043 if everything is taken from 10000 to and including 100042 and Call getfreenum(11,5) should check for remaining 3 characters and return let's say 11000. Or 11600 if everything is taken from 11000 to 11599. And it should work like that for every input, even if i enter 4 characters: Call getfreenum(1234,5) procedure should check for 12340, 12341, 12342 and if 12349 is free it should return that, but it should not return value which changes input number, meaning, if I call Call getfreenum(1234,5) and everything is taken including '123459' then function should return NULL or some fixed value for all errors.
Function is used for asigning item numbers to items in a shop. Sometimes for some items maximum number of digits is 3 and sometimes it is 5. Some items have starting numbers: let's say "1254" for cigarettes. and "12" are starting numbers for luxury goods. It is easier for cashier to use this logic when assigning item numbers. it's just more complex for me :) #Schwern – stacks 3 mins ago
This is better handled with better schema design.
Recognize that "1254" is really two parts. There's the category ID 12 and the item ID 54. Rather than store "1254" you'd store these two separately.
create table item_categories(
id integer primary key auto_increment,
shop_id_padding integer not null default 5,
name text not null
);
create table items (
id integer primary key auto_increment,
name text not null,
category_id integer not null,
foreign key(category_id) references item_categories(id)
);
An explicit item_categories table gives us referential integrity, a place to store the category name, as well as how much padding to use.
Now you can let auto_increment do its job.
insert into item_categories (id, name, shop_id_padding) values (12, "cigarettes", 2);
insert into items (name, category_id) values ("Coffin Nails", 12);
insert into items (name, category_id) values ("Death Sticks", 12);
select * from items;
+----+--------------+-------------+
| id | name | category_id |
+----+--------------+-------------+
| 1 | Coffin Nails | 12 |
| 2 | Death Sticks | 12 |
+----+--------------+-------------+
Construct the shop ID using concat. Pad the ID according to the category.
select concat(i.category_id, lpad(i.id, cat.shop_id_padding, '0')) as shop_id
from items i
join item_categories cat on i.category_id = cat.id;
+---------+
| shop_id |
+---------+
| 1201 |
| 1202 |
+---------+
You can get the set of all sifra where sifra + 1 does not exist with NOT EXISTS and a correlated subquery.
num * power(10, maxval - floor(log10(num)) - 1) gives you the minimum number, e.g. 21000 for 21, 5 and num * power(10, maxval - floor(log10(num)) - 1) + power(10, maxval - floor(log10(num)) - 1) gives you one more than the maximum number, e.g. 22000 for 21, 5. Compare sifra + 1 against it accordingly.
Finally you have to make sure the requested number of digits does not exceed the number of digits for the given prefix. I.e. floor(log10(num)) < maxval has to be satisfied.
To make sure at least the predecessor num * power(10, maxval - floor(log10(num)) - 1) - 1 of the minimum number exists use UNION ALL to add it to the base set.
Like that you can calculate the number with just a SELECT, without any (possibly relatively slow) loops.
CREATE PROCEDURE getfreenum
(IN num integer,
IN maxval integer)
BEGIN
SELECT CASE
WHEN NOT EXISTS (SELECT *
FROM artikli t2
WHERE t2.sifra = num * power(10, maxval - floor(log10(num)) - 1)) THEN
num * power(10, maxval - floor(log10(num)) - 1)
ELSE
min(t1.sifra) + 1
END sifra
FROM artikli t1
WHERE floor(log10(num)) < maxval
AND EXISTS (SELECT *
FROM artikli t2
WHERE sifra = num * power(10, maxval - floor(log10(num)) - 1))
AND NOT EXISTS (SELECT *
FROM artikli t2
WHERE t2.sifra = t1.sifra + 1)
AND t1.sifra >= num * power(10, maxval - floor(log10(num)) - 1) - 1
AND t1.sifra < num * power(10, maxval - floor(log10(num)) - 1) + power(10, maxval - floor(log10(num)) - 1) - 1;
END;
DB Fiddle
And if you're dealing with numbers you should use an appropriate type, not char.
I have a SQL query that I am using that works perfectly for what I am needing at extracting the first date in a text field. This is a free form text field that is tied to a status that when the right conditions align my query looks for a date and if it is in the correct format it extracts this date.
Sometimes the date will be input as a range of dates or a comma separated list of dates. I would like to know if there is a way to extract the last date in the case of a date range or the other dates in a list of dates?
The current query has 3 steps in temp tables for extracting the date here are snippets for each step.
In the first step it looks for the word 'proposed' and grabs a number of characters after:
,SUBSTRING(al.Comments,
PATINDEX('%proposed%',al.Comments)+9,17) [DateFirstPass]
In the second step the query it extracts the date:
,LEFT(
SUBSTRING(p1.DateFirstPass, PATINDEX('%[0-9/]%', p1.DateFirstPass), 10), --string (only numbers and forward slash) MAX of 10 chars (mm/dd/yyyy)
PATINDEX('%[^0-9/]%', SUBSTRING(p1.DateFirstPass, PATINDEX('%[0-9/]%', p1.DateFirstPass), 10) + 'X')-1) --char length after negating nonvalid characters '%[^0-9/]%'
[DateSecondPass]
In the last pass it adds in the year if it is missing:
CASE
WHEN ISDATE(p2.DateSecondPass) = 1
THEN CAST(p2.DateSecondPass AS DATE)
WHEN ISDATE(p2.DateSecondPass + '/' + this.yr) = 1
THEN p2.DateSecondPass + '/' + this.yr --Adds missing year
END [DateThirdPass]
FROM
#ProposedDateParse2 p2
CROSS JOIN (VALUES (CAST(YEAR(GETDATE()) AS varchar(4)))) this(yr)
Grab a copy of patternSplitCM
Here's the code:
-- PatternSplitCM will split a string based on a pattern of the form
-- supported by LIKE and PATINDEX
--
-- Created by: Chris Morris 12-Oct-2012
CREATE FUNCTION dbo.PatternSplitCM
(
#List VARCHAR(8000) = NULL,
#Pattern VARCHAR(50)
) RETURNS TABLE WITH SCHEMABINDING
AS
RETURN
WITH numbers AS (
SELECT TOP(ISNULL(DATALENGTH(#List), 0))
n = ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) d (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) e (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) f (n),
(VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) g (n))
SELECT
ItemNumber = ROW_NUMBER() OVER(ORDER BY MIN(n)),
Item = SUBSTRING(#List,MIN(n),1+MAX(n)-MIN(n)),
[Matched]
FROM (
SELECT n, y.[Matched], Grouper = n - ROW_NUMBER() OVER(ORDER BY y.[Matched],n)
FROM numbers
CROSS APPLY (
SELECT [Matched] = CASE WHEN SUBSTRING(#List,n,1) LIKE #Pattern THEN 1 ELSE 0 END
) y
) d
GROUP BY [Matched], Grouper
Solution against a variable:
DECLARE #string varchar(8000) =
'Blah blah 3/1/2017, 12/19/2018,1/2/2020,1111/11111/1111/111/111 blah blah';
SELECT TOP (1) Item
FROM dbo.patternSplitCM(#string, '[0-9/]')
WHERE [Matched] = 1 AND ISDATE(item) = 1
ORDER BY -ItemNumber;
Results:
item
------
1/2/2020
Example against a table:
DECLARE #table table (someid int identity, sometext varchar(1000));
INSERT #table(sometext) VALUES
('Blah blah 3/1/2017, 12/19/2018,1/2/2020,1111/11111/1111/111/111 blah blah'),
('Yada yada 1/1/12, 12/31/1999 call me at 555-1212!');
SELECT t.someid, getLastDate.Item
FROM #table t
CROSS APPLY
(
SELECT TOP (1) Item
FROM dbo.patternSplitCM(t.sometext, '[0-9/]')
WHERE [Matched] = 1 AND ISDATE(item) = 1
ORDER BY -ItemNumber
) getLastDate;
Results:
someid Item
----------- -----------
1 1/2/2020
2 12/31/1999
In my first table A, I am having data which i need to display, but displaying order would be differnt from the one here .
Display order depends on position in the second table B.
I want the field having lowest position comes first and with name price
Field_21 Field_31 field_41
112 wed www
111 tue dse
123 sun edwd
Name POSITION Name
Field_31 2 ask
Field_21 1 bid
Field_41 0 price
Final Data would be like
price bid ask
www 112 wed
dse 111 tue
edwd 123 sun
Try the following:
DECLARE #tbl VARCHAR(60), #sql VARCHAR(8000)
SET #tbl = 'tblData' -- change for the table
SELECT #sql = 'SELECT '
+ STUFF(
(
SELECT ', ' + ColumnName + ' as ' + ColumnLabel
FROM columnOrder
ORDER BY Position
FOR XML PATH('')
)
, 1, 1, ''
)
+ ' FROM '
+ #tbl
--SELECT #sql
EXEC (#sql)
note as you cannot have 2 columns called [name] in one table, I use ColumnName and ColumnLabel,
see this sqlfiddle
I have a table, MapLocation, which has a column and two relationships with tables that have a field that really need to be displayed as a single concatenated value. I was thinking this was a perfect case for a computed column, but not sure how to go about it.
MapLocation MaoNo Section
_____________________ _____________________ _____________________
MapNoId MapNoId SectionId
SectionId MapNumber (int) Section (int)
Identifier (nvarchar)
LocationName (nvarchar)
LocationName = "MapNUmber - SectionNumber - Identifier"
ex: 20 - 03 - SW4
How would I write that? I haven't done much with computed columns or concatenating in SQL.
Edit:
I need an actual computed column that is automatically updated, im looking for the formula. Or is this more of a function/trigger? Its possible, I certainly barely know what I'm doing. The idea is that I dont want to have to do two more server calls and concatenate these values client side.
You would use something like this to get the value:
select cast(n.MapNumber as nvarchar(10)) + ' - ' -- cast the MapNumber
+ cast(s.SectionId as nvarchar(10)) + ' - ' -- cast the SectionId
+ l.Identifier
from MapLocation l
left join MaoNo n
on l.MapNoId = n.MapNoId
left join Section s
on l.SectionId = s.SectionId
Then if you need to perform an UPDATE:
update l
set l.LocationName = (cast(n.MapNumber as nvarchar(10)) + ' - '
+ cast(s.SectionId as nvarchar(10)) + ' - '
+ l.Identifier)
from MapLocation l
left join MaoNo n
on l.MapNoId = n.MapNoId
left join Section s
on l.SectionId = s.SectionId
Edit #1 - you can use a TRIGGER:
CREATE TRIGGER trig_LocationName
ON MapLocation
AFTER INSERT
AS
Begin
update MapLocation
set LocationName = (cast(n.MapNumber as nvarchar(10)) + ' - '
+ cast(s.SectionId as nvarchar(10)) + ' - '
+ i.Identifier)
from Inserted i
left join MaoNo n
on i.MapNoId = n.MapNoId
left join Section s
on i.SectionId = s.SectionId
where MapLocation.MapNoId = i.MapNoId -- fields here to make sure you update the correct record
End