SQL name in list of items - mysql

I have two tables, one containing a path and the other containing a filename. Here's an example:
t1
- file.mov
- myfile.txt
- 2file.py
t2
- /new/path/file.mov
- /path/hello.txt
- /path/2file.py
I want to build a query to get me all the filenames for which I have a path:
- file.mov /new/path/file.mov
- 2file.py /path/2file.py
What would be the query I could use to find this? The closest I could think of is using IN, but that is based on an exact match and not a LIKE, which is what I need to use:
SELECT name FROM names WHERE name in (SELECT path FROM paths);

Using an INNER JOIN between to 2 tables will allow you to list either or both the file names and paths
SELECT names.name, paths.path
FROM names
INNER JOIN paths ON names.name = SUBSTRING_INDEX(paths.path, '/', -1)
;
However the output will be (from the sample):
| NAME | PATH |
|----------|--------------------|
| file.mov | /new/path/file.mov |
| 2file.py | /path/2file.py |
because you do not have a path for "myfile.txt", or a name for "/path/hello.txt", (so this result does not match the result shown in the question).

SELECT name
FROM names
WHERE name in
(SELECT SUBSTRING_INDEX(path, '/', -1) FROM paths);

You mentioned using LIKE - combining that with EXISTS could work. E.g.
SELECT name
FROM names
WHERE EXISTS (
SELECT *
FROM paths
WHERE path LIKE '%' + name
)
Just keep in mind that this has the potential to return false positives, i.e. if your paths table contained "path/2file.py", and you searched for "file.py".

Related

specify location name if point intersects it MySQL

I am using MySQL 8.0
I have two tables: location_table, rent_table
location_tables looks like:
location_name polygon
A BLOB
B BLOB
...
polygon is POLYGON datatype.
rent_table looks like:
user_id rent_time rent_location
1 x BLOB
2 x BLOB
...
where rent_location is POINT data type
for each row in rent_table I want to create a column that indicate which location_name it belongs to. If user_id rent_location intersects location_name = A new column would have A
It would look something like this:
user_id rent_time rent_location location_name
1 x BLOB A
2 x BLOB B
...
Thanks in advance!
What I've tried:
I can do one by one by using
select *
, st_intersects(A, Point(ST_X(work_location), ST_Y(work_location))) as location_A
, st_intersects(B, Point(ST_X(work_location), ST_Y(work_location))) as location_B
, st_intersects(C, Point(ST_X(work_location), ST_Y(work_location))) as location_C
from rent_table;
This works when I set A,B,C variable beforehand but I want to get location polygon directly from location_table.
I could use subquery like below:
select *
, st_intersects((select polygon from location_table where location_name = 'A'), Point(ST_X(work_location), ST_Y(work_location))) as location_A
from rent_table;
however I have millions of rows in rent_table therefore I do not want subquery in select statement to run for each of million rows.
You can do:
select
r.*,
l.location_name
from rent r
left join location l on ST_CONTAINS(l.polygon, r.rent_location)

Can I craft a statement to return records where (attribute A = A1 and attribute B = B1) OR (attrbt A = A2 and attrbt B = B2) OR etc etc?

User has given me a table of vendor #s and Invoice #s, and wants a query to find the documents with those attributes. Tried just giving him:
select * from document.docdata
join document.documents.vendor #s
join document.documents.invoice #s
Where
vendor # in (paste in column A from his table)
AND
invoice # in (paste in column b from his table)
But that didn't do the trick, because he's wanting to see the docs whose vendor# and invoice# match the rows of the table, as in vendor#, invoice# = A1, B1. My first question was "Why not just search by the invoice# and be done with it?"
But turns out the invoice numbers aren't unique.
So I'm needing a better way of writing this:
Select * from table
join vendor #s
join invoice #s
Where
(Vendor#=A1 AND Invoice#=B1)
OR
(Vendor#=A2 AND Invoice#=B2)
....
OR
(Vendor#=A652 AND Invoice#=B652)
For sample data, here's an example with the first 10 items from the user. I have this data in csv format.
A | B
---------------------
354055 | 1637
259769 | 2112
259769 | 2314
153060 | 47185
174829 | 63486
297719 | 4994-033017
203110 | 1360
292193 | 2058-09-1271
202308 | 60513
286641 | 1975
So I need the records that match both Company 354055 and Invoice 1637, as well as both Company 259769 and Invoice 2112, plus both Company 259769 and Invoice 2314, etc.
EDIT: I ended up just using excel to get 659 lines of "(Vendor#=x AND Invoice#=y) OR". Probably could run faster but it works, so off to production it goes.
MySQL supports tuple syntax:
Select *
from table join
vendor #s
on . . . join
invoice #s
on . . .
Where (#Vendor#, Invoice#) in ( (A1, B1), (A2, B2), . . . )
This is a rough answer, since we don't have good sample data or column names, but the general idea is, since you have an external table that matches invoice #s to vendor #s, you should import that data into a temporary table. Then you can join against the table for records where both conditions match in an efficient way.
There are also VALUES() expressions (formally: Table Value Constructors). You could use this to generate your query with a little less code: create the expression for your data as part of the SQL string, and then JOIN against that expression to accomplish the filter.
SELECT d.*
FROM document.docdata d
INNER JOIN (
VALUES
ROW(354055,'1637'),
ROW(259769,'2112'),
ROW(259769,'2314'),
ROW(153060,'47185')
-- etc
) filter on filter.invoice = d.invoice and filter.company = d.company

Get item from breadcrumb/tree path (Adjacency model)

I understand you can get breadcrumbs/ tree path using a with a recursive CTE, but is it possible to select an item knowing the breadcrumb/tree?
id| name | parent_id
--------------------
0 | a | null
1 | b | 0
2 | c | 1
3 | b | 2
For example, if the breadcrumb looked like this: a/b/c/b, how would I be able to return the row with id 3 knowing this information?
Postgres just rocks.
http://sqlfiddle.com/#!17/0a6f4/27
The idea is to build the textbook recursive query which returns the path of each element in the tree, along with a "level" which represents the number of nodes from the root. You can also call it "depth".
Then, we turn the path 'a/b/c/b' into an ARRAY['a','b','c','b']... therefore indexing this array on [level] gives the name of the node we're looking for at each level.
WITH RECURSIVE h(id,name,parent_id,level,path,search_path) AS (
SELECT id,
name,
parent_id,
1,
ARRAY[name],
ARRAY['a','b','c','b']
FROM t WHERE parent_id IS NULL AND name = 'a'
UNION ALL
SELECT t.id,
t.name,
t.parent_id,
level+1,
path || t.name,
h.search_path
FROM t JOIN h ON(t.parent_id=h.id)
WHERE search_path[level+1] = t.name
)
SELECT *, path=search_path as match FROM h;
This returns the nodes from the requested path, in path order. I added a "match" column which becomes true when the requested row was found. If you only want this row, put the condition in the where, unless you want it to stop at the closest match and return it in case the path is not found, in which case you'll need to take the last row.
Funnily enough it should be possible to attempt this in MySQL by using session variables to transfer the parent_id from one row to the next, although MySQL has no arrays, so something like find_in_set() could work instead... would be kind of a hack...

select one row multiple time when using IN()

I have this query :
select
name
from
provinces
WHERE
province_id IN(1,3,2,1)
ORDER BY FIELD(province_id, 1,3,2,1)
the Number of values in IN() are dynamic
How can I get all rows even duplicates ( in this example -> 1 ) with given ORDER BY ?
the result should be like this :
name1
name3
name2
name1
plus I shouldn't use UNION ALL :
select * from provinces WHERE province_id=1
UNION ALL
select * from provinces WHERE province_id=3
UNION ALL
select * from provinces WHERE province_id=2
UNION ALL
select * from provinces WHERE province_id=1
You need a helper table here. On SQL Server that can be something like:
SELECT name
FROM (Values (1),(3),(2),(1)) As list (id) --< List of values to join to as a table
INNER JOIN provinces ON province_id = list.id
Update: In MySQL Split Comma Separated String Into Temp Table can be used to split string parameter into a helper table.
To get the same row more than once you need to join in another table. I suggest to create, only once(!), a helper table. This table will just contain a series of natural numbers (1, 2, 3, 4, ... etc). Such a table can be useful for many other purposes.
Here is the script to create it:
create table seq (num int);
insert into seq values (1),(2),(3),(4),(5),(6),(7),(8);
insert into seq select num+8 from seq;
insert into seq select num+16 from seq;
insert into seq select num+32 from seq;
insert into seq select num+64 from seq;
/* continue doubling the number of records until you feel you have enough */
For the task at hand it is not necessary to add many records, as you only need to make sure you never have more repetitions in your in condition than in the above seq table. I guess 128 will be good enough, but feel free to double the number of records a few times more.
Once you have the above, you can write queries like this:
select province_id,
name,
#pos := instr(#in2 := insert(#in2, #pos+1, 1, '#'),
concat(',',province_id,',')) ord
from (select #in := '0,1,2,3,1,0', #in2 := #in, #pos := 10000) init
inner join provinces
on find_in_set(province_id, #in)
inner join seq
on num <= length(replace(#in, concat(',',province_id,','),
concat(',+',province_id,',')))-length(#in)
order by ord asc
Output for the sample data and sample in list:
| province_id | name | ord |
|-------------|--------|-----|
| 1 | name 1 | 2 |
| 2 | name 2 | 4 |
| 3 | name 3 | 6 |
| 1 | name 1 | 8 |
SQL Fiddle
How it works
You need to put the list of values in the assignment to the variable #in. For it to work, every valid id must be wrapped between commas, so that is why there is a dummy zero at the start and the end.
By joining in the seq table the result set can grow. The number of records joined in from seq for a particular provinces record is equal to the number of occurrences of the corresponding province_id in the list #in.
There is no out-of-the-box function to count the number of such occurrences, so the expression at the right of num <= may look a bit complex. But it just adds a character for every match in #in and checks how much the length grows by that action. That growth is the number of occurrences.
In the select clause the position of the province_id in the #in list is returned and used to order the result set, so it corresponds to the order in the #in list. In fact, the position is taken with reference to #in2, which is a copy of #in, but is allowed to change:
While this #pos is being calculated, the number at the previous found #pos in #in2 is destroyed with a # character, so the same province_id cannot be found again at the same position.
Its unclear exactly what you are wanting, but here's why its not working the way you want. The IN keyword is shorthand for creating a statement like ....Where province_id = 1 OR province_id = 2 OR province_id = 3 OR province_id = 1. Since province_id = 1 is evaluated as true at the beginning of that statement, it doesn't matter that it is included again later, it is already true. This has no bearing on whether the result returns a duplicate.

Mysql multiple tables select

I've got a table, called for example, "node", from which I need to return values for as shown:
SELECT nid FROM node WHERE type = "book"
After I get a list of values let's say:
|**nid**|
|123|
|12451|
|562|
|536|
Then I need to take these values, and check another table, for rows where column 'path' has values as "node/123", "node/12451" (numbers the previous request returned) in one joined request. It all would be easier if collumn 'path' had simple numbers, without the 'node/'.
And then also count the number of identical i.e. 'node/123' returned.
End result would look like:
nid | path | count(path) | count(distinct path)
123 |node/123| 412 | 123
562 |node/562| 123 | 56
Works fine if done in multiple separated queries, but that won't do.
select a.nid from node a join othertable b
on b.path = concat("node/", a.nid) where type='book'
You can probably do something like the following (nid may require additional conversion to some string type):
SELECT *
FROM OtherTable
JOIN node ON path = CONCAT('node/', nid)
WHERE type = 'book'
Thank you all for your help. Basically, the problem was that I didn't know how to get nid and node/ together, but concat helped.
End result looks something like:
SELECT node.nid, accesslog.path, count(accesslog.hostname), count(distinct accesslog.hostname)
FROM `node`, `accesslog`
WHERE node.uid=1
AND node.type='raamat'
AND accesslog.path = CONCAT('node/', node.nid)
GROUP BY node.nid