After searching a lot here, I could not find solution to my problem. So, I am posting this question.
I have a Database Table which has the structure like this:
folder_id folder_name parent_id
--------------------------------------
1 Private 0
2 Public 0
3 Photos 0
4 December 3
5 Bday 4
6 Celeb 5
In hierarchical form, it will be like a folder structure :
-- Private
-- Public
-- Photos
----- December
-------- Bday
----------- Celeb
Now, I would like to select a Path upto a particular folder, like to Bday or Celeb folder.
Thus, I want a MySQL query which will return me only the rows containing the folders between the path to a specific folder.
For Example, If I want a path to Celeb folder, then the Query should return these rows only :
folder_id folder_name parent_id
--------------------------------------
3 Photos 0
4 December 3
5 Bday 4
6 Celeb 5
Currently, I am stuck with this query and I am not able to make it work. The Query I am currently trying :
SELECT f.*
FROM fd_folders f
LEFT JOIN fd_folders p
ON f.folder_id = p.parent_id
WHERE f.folder_id <=6
ORDER BY f.folder_id;
But, the problem is that it is also returning the two other folders, i.e, Private and Public.
Please help.
Thank You
Best Regards
I'll just post this even though it does not DIRECTLY answer your question.
It looks like you may have to set up your table to have hierarchical data:
http://www.sitepoint.com/hierarchical-data-database-2/
This will take some playing with to get set up correctly, and inserting new data will not be a fun process. Your life may be much easier if you use an additional language that processes the results for you.
If you have the option of using SQL Server instead (not mysql), this whole thing actually becomes quite simple. You just need a Common Table Expression:
;WITH DirectoryTree (folderId, folderName, parentId)
AS
(
SELECT d.folderId, d.folderName, d.parentId
FROM zzz_Directories d
WHERE d.folderId = 4 -- target folderId
UNION ALL
SELECT d.folderId, d.folderName, d.parentId
FROM zzz_Directories d
INNER JOIN DirectoryTree dt ON dt.parentId = d.folderId
)
SELECT dt.folderId, dt.folderName, dt.parentId
FROM DirectoryTree dt
(Semicolon intentional)
Regrettably, mysql doesn't support this. Instead, you may have to do what was suggested in the comments: use a stored procedure. I don't have an environment up to test this, but I imagine such a procedure would have two parts:
if my parent is 0, return me
if my parent is not 0, return me UNION the procedure
the procedure would simply recursively call itself until the parent = 0. I don't know if this is even possible, but it's a place to start.
Related
In the application I am developing, the user has to set parameters to define the end product he will get.
My tables look like this :
Categories
-------------
Id Name
1 Material
2 Color
3 Shape
Parameters
-------------
Id CategoryId Name
1 1 Wood
2 1 Plastic
3 1 Metal
4 2 Red
5 2 Green
6 2 Blue
7 3 Round
8 3 Square
9 3 Triangle
Combinations
-------------
Id
1
2
...
ParametersCombinations
----------------------
CombinationId ParameterId
1 1
1 4
1 7
2 1
2 5
2 7
Now only some combinations of parameters are available to the user. In my example, he could get a red round wooden thingy or a green round wooden thingy but not a blue one because I can't produce it.
Let's say the user selected wood and round parameters. How do I make a request to know that there's only red and green available so I can disable the blue option for him ?
Or is there some better way to model my database ?
Let us assume you provide the selected parameters id in the following format
// I call this a **parameterList** for convenience sake.
(1,7) // this is parameter id 1 and id 7.
I am also assuming you are using some scripting language to help you with your app. Like ruby or php.
I am also assuming you want to avoid putting as much logic into your stored procedure or MySQL queries as much as possible.
Another assumption is that you are using one of the Rapid Application MVC Frameworks like Rails, Symfony or CakePHP.
Your logic would be:
Find all the combinations that contain ALL the parameters in your parameterList and put these found combinations in a list called relevantCombinations
Find all the parameters_combinations that contain at least 1 of the combinations in the list relevantCombinations. Retrieve only the unique parameter values.
First two steps can be solved using simple Model::find methods and a forloop in the frameworks I described above.
If you are not using frameworks, it is also cool to use the scripting language raw.
If you require them in MySQL queries, here are some possible queries. Be aware that these are not necessary the best queries.
First one is
SELECT * FROM (
SELECT `PossibleList`.`CombinationId`, COUNT(`PossibleList`.`CombinationId`) as number
FROM (
SELECT `CombinationId` FROM `ParametersCombinations`
WHERE `ParameterId` IN (1, 7)
) `PossibleList` GROUP BY `PossibleList`.`CombinationId`
) `PossibleGroupedList` WHERE `number` = 2;
-- note that the (1, 7) and the number 2 needs to be supplied by your app.
-- 2 refers to the number of parameters supplied.
-- In this case you supplied 1 and 7 therefore 2.
To confirm, look at http://sqlfiddle.com/#!2/16831/3.
Note how I purposely have a Combination 3 which only has the Parameter 1 but not 7. Therefore the query did not give you back 3, but only 1 and 2. Feel free to tweak the asterisk * in the first line.
Second one is
SELECT DISTINCT(`ParameterID`)
FROM `ParametersCombinations`
WHERE `CombinationId` IN (1, 2);
-- note that (1, 2) is the result we expect from the first step.
-- the one we call relevantCombinations
To confirm, look at http://sqlfiddle.com/#!2/16831/5
I do not recommend being a masochist and attempt to get your answer in a single query.
I also do NOT recommend using the MySQL queries I have supplied. It is less masochistic. But sufficiently masochistic for me NOT to recommend this way.
Since you did not indicate any tag other than mysql, I suspect that you are stronger with mysql. Hence my answer contains mysql.
My strongest suggestion would be my first. Make full use of established frameworks and put your logic in the business logic layer. Not in the data layer. Even if you don't use frameworks and just use raw php and ruby, that is still a better place for you to place your logic in than MySQL.
I saw that T gave an answer in a single MySQL query but I can tell you that (s)he considers only 1 parameter.
See this part:
WHERE ParameterId = 7 -- 7 is the selected parameter
You can adapt his/her answer with some trickery using a forloop and appending OR clauses.
Again, I do NOT recommend that in the big picture of building an app.
I have also tested his/her answer with http://sqlfiddle.com/#!2/2eda4/2. There may be 1 or 2 small bugs.
In summary, my recommendations in descending order of strength:
Use a framework like Rails or CakePHP and the pseudocode step 1 and 2 and as many find as you need. (STRONGEST)
Use raw scripting language and the pseudocode step 1 and 2 and as many simple queries as you need.
Use the raw MySQL queries I created. (LEAST STRONG)
P.S. I left out the part in my queries as to how to get the name of the Parameters. But given that you can get the ParameterIDs from my answer, I think that is trivial. I have also left out how you may need to remove the already selected parameters (1, 7). Again, that should be trivial to you.
Try the following
SELECT p.*, pc.CombinationId
FROM Parameters p
-- get the parameter combinations for all the parameters
JOIN ParametersCombinations pc
ON pc.ParameterId = p.Id
-- filter the parameter combinations to only combinations that include the selected parameter
JOIN (
SELECT CombinationId
FROM ParametersCombinations
WHERE ParameterId = 7 -- 7 is the selected parameter
) f ON f.CombinationId = pc.CombinationId
Or removing the already selected parameters
SELECT p.*, pc.CombinationId
FROM Parameters p
JOIN ParametersCombinations pc
ON pc.ParameterId = p.Id
JOIN (
SELECT CombinationId
FROM ParametersCombinations
WHERE ParameterId IN (7, 1)
) f ON f.CombinationId = pc.CombinationId
WHERE ParameterId NOT IN (7, 1)
I have a table "audit" with a "description" column, a "record_id" column and a "record_date" column. I want to select only those records where the description matches one of two possible strings (say, LIKE "NEW%" OR LIKE "ARCH%") where the record_id in each of those two matches each other. I then need to calculate the difference in days between the record_date of each other.
For instance, my table may contain:
id description record_id record_date
1 New Sub 1000 04/14/13
2 Mod 1000 04/14/13
3 Archived 1000 04/15/13
4 New Sub 1001 04/13/13
I would want to select only rows 1 and 3 and then calculate the number of days between 4/15 and 4/14 to determine how long it took to go from New to Archived for that record (1000). Both a New and an Archived entry must be present for any record for it to be counted (I don't care about ones that haven't been archived). Does this make sense and is it possible to calculate this in a SQL query? I don't know much beyond basic SQL.
I am using MySQL Workbench to do this.
The following is untested, but it should work asuming that any given record_id can only show up once with "New Sub" and "Archived"
select n.id as new_id
,a.id as archive_id
,record_id
,n.record_date as new_date
,a.record_date as archive_date
,DateDiff(a.record_date, n.record_date) as days_between
from audit n
join audit a using(record_id)
where n.description = 'New Sub'
and a.description = 'Archieved';
I changed from OR to AND, because I thought you wanted only the nr of days between records that was actually archived.
My test was in SQL Server so the syntax might need to be tweaked slightly for your (especially the DATEDIFF function) but you can select from the same table twice, one side grabbing the 'new' and one grabbing the 'archived' then linking them by record_id...
SELECT
newsub.id,
newsub.description,
newsub.record_date,
arc.id,
arc.description,
arc.record_date,
DATEDIFF(day, newsub.record_date, arc.record_date) AS DaysBetween
FROM
foo1 arc
, foo1 newsub
WHERE
(newsub.description LIKE 'NEW%')
AND
(arc.description LIKE 'ARC%')
AND
(newsub.record_id = arc.record_id)
EXPLANATION
I am working on a project where my client sends documents (contracts) out to their customers. These documents go through several rounds of "events" as they are send back and forth (ex. Prep, Sent to Client, Received Revised From Client, Signed By Client, etc). For a specific company (companyID 123456), I am trying to pull the most recent event for each document. So the example query below might return say 5 different documents, and each might have 4 different events they went through. In the example results I have simplified it down to a single document (documentId 6789).
Given the example results below, I expect to get the third row. All rows are for a single document, and that one has the most recent date. However, I get four as you see below.
If I Group By the documentID, then I get the right date, but my event and event id values are incorrect (transposed). The only success I have had is to wrap each value in the Select statement in a Max() function. When I do that I get the one row I want with the right event. However, things like the document ID are wrong because of course it returns me the max id, not the one that matches.
Could someone please help me adjust this query so I get the result I need? Thanks in advance!
Note: I found this "solution" here, but I don't think it applies directly to what I am doing:
Fetch the row which has the Max value for a column
QUERY
SELECT e.eventID,
e.event,
de.documentEventID,
de.documentID,
Max(de.eventDate) AS eventDate,
sd.companyID,
FROM siteDocuments sd
LEFT JOIN documents d ON d.documentID = sd.documentID
LEFT JOIN documentTypes dt ON dt.documentTypeID = d.documentTypeID
LEFT JOIN documentEvents de ON de.documentID = sd.documentID
LEFT JOIN events e ON e.eventID = de.eventID
WHERE sd.companyID = 123456
GROUP BY e.eventID
EXAMPLE RESULTS
-----------------------
EventId --- Event Doc --- EventId --- documentId --- eventDate
16 -------- FakeEventA -- 135791 ------ 6789 ------- 2012-04-11 08:35:54
32 -------- FakeEventB -- 726351 ------ 6789 ------- 2012-04-11 08:56:02
24 -------- FakeEventC -- 987236 ------ 6789 ------- 2012-05-09 16:48:57 <======
81 -------- FakeEventD -- 982378 ------ 6789 ------- 2012-04-20 14:06:19
(I put the dashes in to enforce formatting)
Are you sure the answers on the linked question (specifically this answer) aren't doing exactly what you want?
SELECT sd.*
FROM siteDocuments AS sd
LEFT OUTER JOIN siteDocuments AS sd2
ON (sd.documentId = sd2.documentId AND sd.eventDate < sd2.eventDate)
WHERE
sd2.documentId IS NULL
and sd.documentId = 6789;
I've used that answer before to achieve what you're looking for. (To maintain performance, you have to be careful to set up indexes correctly on large datasets, however--varies based on the DBMS you're using).
I have a table that looks like the following
cat_id | name | parent_id | Level
-------------------------------------------------
1 cat1 0 1
2 subcat1 1 2
3 subcat1-subcat 2 3
I am wondering what is the most efficient way to get the parent categories of cat_id 3, so my result set will look like this
cat_id | name | parent_id | Level
--------------------------------------------------
1 cat1 0 1
2 subcat1 1 2
You have to do multiple queries. One for each level up you want to go (or write it as a stored procedure).
The most efficient way is not to use what's called the "adjacency list model" (what you are using) and instead switch to "nested sets".
Googling for "nested set" will give you lots of info. It will take you some time to get used to it though, and to write the code for handling it.
I was having the same issue, but unfortunately there is no way to do this in single query. so you have to either write a function or a stored procedure which can get all parent categories.
algo will be as follows
**childs** = 3
Loop (not done)
Get immediate parents of **childs**,
save(concat) them in a *return* variable
update **childs** = immediate parents
REPEAT
return will contain all parents(flat) of given category
1) FUNCTION : in case of function you will return a string value as function can not return more than 1 values so your result will be some thing like "1,2". but again this will not full fill our purpose as we have to use this result in a query
SELECT * FROM table_name where id IN ("1,2") ORDER BY LEVEL
so instead we will return our result as a regular expression ;) and that is "^(1|2)$"
SELECT * FROM tbl_categories WHERE cat_id REGEXP "^(1|2)$" ORDER BY level;
2) STORED PROCEDURE: in case of stored procedure we can prepare a statement dynamically and upon executing that query we will have our required result.
for more detail on stored procedure pleas have a look on following tutorial.
Get all nested parents of a category – MySQL
I have a custom shop, and I need to redo the shipping. However, that is sometimes later, and in the meantime, I need to add a shipping option for when a cart only contains a certain range of products.
SO there is a ship_method table
id menuname name zone maxweight
1 UK Standard ukfirst 1 2000
2 UK Economy uksecond 1 750
3 Worldwide Air world_air 4 2000
To this I have added another column prod_restrict which is 0 for the existing ones, and 1 for the restricted ones, and a new table called ship_prod_restrict which contains two columns, ship_method_id and item_id, listing what products are allowed in a shipping category.
So all I need to do is look in my transactions, and for each cart, just check which shipping methods are either prod_restrict of 0 or have 1 and have no products in the cart that aren't in the restriction table.
Unfortunately it seems that because you can't values from an outer query to an inner one, I can't find a neat way of doing it. (edited to show the full query due to comments below)
select ship_method.* from ship_method, ship_prod_restrict where
ship_method.`zone` = 1 and prod_restrict='0' or
(
prod_restrict='1'
and ship_method.id = ship_prod_restrict.ship_method_id
and (
select count(*) from (
select transactions.item from transactions
LEFT JOIN ship_prod_restrict
on ship_prod_restrict.item_id = transactions.item
and ship_prod_restrict.ship_method_id=XXXXX
where transactions.session='shoppingcartsessionid'
and item_id is null
) as non_permitted_items < 1 )
group by ship_method.id
gives you a list of whether the section matches or not, and works as an inner query but I can't get that ship_method_id in there (at XXXXX).
Is there a simple way of doing this, or am I going about it the wrong way? I can't currently change the primary shipping table, as this is already in place for now, but the other bits can change. I could also do it within PHP but you know, that seems like cheating!
Not sure how the count is important, but this might be a bit lighter - hard to tell without a full table schema dump:
SELECT COUNT(t.item) FROM transactions t
INNER JOIN ship_prod_restrict r
ON r.item_id = t.item
WHERE t.session = 'foo'
AND r.ship_method_id IN (**restricted, id's, here**)