complex'ish SQL joins across multiple tables with multiple conditions across all tables - sql

Given the following tables:
labels tags_labels
|id |name | |url |labelid |
|-----|-------| |/a/b |1 |
|1 |punk | |/a/c |2 |
|2 |ska | |/a/b |3 |
|3 |stuff | |/a/z |4 |
artists tags
|id |name | |url |artistid |albumid |
|----|--------| |------|-----------|---------|
|1 |Foobar | |/a/b |1 |2637 |
|2 |Barfoo | |/a/z |2 |23 |
|3 |Spongebob| |/a/c |1 |32 |
I would like to get a list of urls that match a couple of conditions (which can be entered by the user into the script that uses these statements).
For example, the user might want to list all urls that have the labels "(1 OR 2) AND 3", but only if they are by the artists "Spongebob OR Whatever".
Is it possible to do this within a single statement using inner/harry potter/cross/self JOINs?
Or would I have to spread the query across multiple statements and buffer the results inside my script?
Edit:
And if it is possible, what would the statement look like? :p

Yes, you can do this in one query. And maybe an efficient way would be to dynamically generate the SQL statement, based on the conditions the user entered.

This query would allow you to filter by label name or artist name.
Building the sql dynamically to concatenate the user parameters or
passing the desired parameters into a stored procedure would obviously change
the where clauses but that really depends on how dynamic your 'script' must be...
SELECT tl.url
FROM labels l INNER JOIN tags_labels tl ON l.id = tl.labelid
WHERE l.name IN ('ska','stuff')
UNION (
SELECT t.url
FROM artists a INNER JOIN tags t ON a.id = t.artistid
WHERE a.name LIKE '%foo%'
)
Good Luck!

Related

SQL Architecture design

I have the following tables:
+---+---------+
|id | name | foreign_key1 = this table's id
+---+---------+
|1 | White |
|2 | Black |
+---+---------+
+----+------+-------------+
|id | name | foreign_key1|
+----+------+-------------+
|1 | Grey | 1 |
|2 | Grey | 2 |
+----+------+-------------+
Is there a way that I could persist the last table's information with only one row? So that table could represent that grey is both white and black in one row?
You could use an array-like column type (string) and make it a one row record, but I wouldn't suggest that, it's better to have them as separate rows. Your approach is fine, but I'll suggest (if I've understood your idea) a little different schema:
You can make two tables: Colors, and Related_Colors, like this:
Colors
+---+---------+
|id | name |
+---+---------+
|1 | White |
|2 | Black |
|3 | Gray |
+---+---------+
Related_Colors
+---+---------+---------+
|id |color1_id|color2_id|
+---+---------+---------|
|1 |3 |1 |
|2 |3 |2 |
+---+---------+---------+
You could; it's called denormalization (https://en.wikipedia.org/wiki/Denormalization). Essentially you would need to create (in the second table) a column for each one of the possible IDs in the first table. So the schema of the second table would be:
ID
Name
White
Black
This also explains why you probably should not do it; what happens when you want to add another ID in the first table (e.g. purple)? As things are you would just need to add another row in table 1, and reference it from the relevant rows. If you denormalize this way, you would need to change the schema to accommodate the new possible value. The new column would of course be empty for most rows.
Another possibility would be to maintain the values as a concatenated string; so the schema would be
ID
Name
List Of IDs From Table1
And in this case the last field would contain White, Black. The drawback of this approach is that you can no longer query efficiently by the values from table1. (You can't properly index that field)
Ultimately the question is - what are your needs. If you need to read rows quickly, and have them in a 'reporting friendly' format, denormalization may work for you. But in most DB design cases it would not be required.

Database Sql Chain Integers in row

At the moment I have for each users an integer in my database
which datatype would I have to use if I want to chain multiply integers for a single users in my database?
Now:
__________________________________
Users Numbers
Tom 2
__________________________________
What I want:
__________________________________
Users Numbers
Tom 2,12
__________________________________
As #jarlh stated, you shouldn't design your database to contain a set of data.
A relational database column must contain only a data of a single kind and not a set of data or different kinds of data through your rows.
To fix your error you can create another table named Numbers and associate it to your Users table with a 1:N (one to many) relation like shown here:
_Users___ _Numbers________________
|ID |name | |NumberID |UserID |value |
|1 |Tom | |1 |3 |243 |
|2 |Jess | |2 |1 |12 |
|3 |Luis | |3 |2 |87 |
In the Users table you have an ID and the name, then in your Numbers table you associate a number (for each new number you must insert a new row) to its owner with the foreign key UserID

SQL Server recursive query with associated table

I have a typical parent/child relationship table to represent folders. My challenge is using it in conjunction with another table.
The folder table is like this:
+--+----+--------+
|id|name|parentid|
+--+----+--------+
|1 |a |null |
+--+----+--------+
|2 |b |1 |
+--+----+--------+
|3 |c1 |2 |
+--+----+--------+
|4 |c2 |2 |
+--+----+--------+
The association table is like this:
+--+--------+
|id|folderid|
+--+--------+
|66|2 |
+--+--------+
|77|3 |
+--+--------+
so that where association.id = 66 has a relationship to folder.id = 2
What I need to do is find the association.id of the first ancestor with a record in the association table.. Using the example data above, given folder.id of 3 I expect to find 77; given folder.id of 2 or 4 I expect to find 66; any other folder.id value would find null.
Finding folder ancestry can be done with a common table expression like this:
WITH [recurse] (id,name,parentid,lvl) AS
(
select a.id,a.name,a.parentid,0 FROM folder AS a
WHERE a.id='4'
UNION ALL
select r.id,r.name,r.parentid,lvl+1 FROM folder as r
INNER JOIN [recurse] ON recurse.parentid = r.id
)
SELECT * from [recurse] ORDER BY lvl DESC
yielding the results:
+--+----+--------+---+
|id|name|parentid|lvl|
+--+----+--------+---+
|1 |a | |2 |
+--+----+--------+---+
|2 |b |1 |1 |
+--+----+--------+---+
|4 |c2 |2 |0 |
+--+----+--------+---+
To include the association.id I've tried using a LEFT JOIN in the recursive portion of the CTE, but this is not allowed by SQL Server.
What workaround do I have for this?
Or better yet, is the a way to query directly for the particular association.id? (e.g., without walking through the results of the CTE query that I have been attempting)
SELECT r.id, r.name, r.parentid, r.lvl, a.folderid, a.id as associationid
FROM [recurse] r
LEFT JOIN [association] a
ON r.id = a.folderid
WHERE a.folderId IS NOT NULL
ORDER BY lvl DESC
This will give you the records that have values in the association table. Then you could limit it to the first record that has a value or just grab the top result

How to concat all of a select sql query

So I have an access 2000 database and i want to write a sql query that would do one SELECT query and based on an id of each row returned in that SELECT query call another nested SELECT query that would concat all those results and the id are linked as a relationship so i just need to concat all the results of the nested second select query
so if the databases are like this...
Table 1 Table 2
|ID | First Name| |ID | Notes|
----------------- ------------
|1 | Mike | |1 | testing|
|2 | Alex | |1 | test2 |
|3 | Jon | |2 | testing|
so when the query is called it returns
1 mike testing test2
2 alex testing
3 jon
A LEFT JOIN or INNER JOIN, such as can be built in the query design window is only going to get you so far. It seems from the above that you also wish to concatenate several rows in table 2 when the id is the same. This cannot be done with Access (Jet) SQL. You will need a user defined function (UDF). You will find two examples here and a search for concatenate + Access should return others.

SQL Group by one column, count entries in another

I'm using a sqlite3 database, with a table like this.
|name |action |
-------------------------
|john |run |
|jim |run |
|john |run |
|john |jump |
|jim |jump |
|jim |jump |
|jim |dive |
I want to get an output like this
|name |run |jump |dive |
---------------------------------
|john |2 |1 |0 |
|jim |1 |2 |1 |
The closest I've come is with this, but I would like to have a single row like above.
SELECT name, action, COUNT(name)
FROM table
GROUP BY name, action
|name |action |COUNT(name) |
|john |run |2 |
|john |jump |1 |
|jim |run |1 |
|jim |jump |2 |
|jim |dive |1 |
Also, I will need to have some WHERE statements in the query as well.
Am I up in the night thinking this will work?
You can also accomplish what you want by using a sum aggregate and CASE conditions like this:
SELECT name,
sum(CASE WHEN action = 'run' THEN 1 END) as run,
sum(CASE WHEN action = 'jump' THEN 1 END) as jump,
sum(CASE WHEN action = 'dive' THEN 1 END) as dive
FROM table
GROUP BY name
You will still have to change the query every time additional actions are added.
What you are trying to do is called cross tabulation. Normally this is available as a feature called pivot table in Excel and other spreadsheet softwares.
I have found a blog article which will help you with this using SQL. Check out pivot-table-hack-in-sqlite3-and-mysql
I don't know SQLLite that well, but I image that you could use subqueries or temp tables.
With mssql you could write something like this:
select Name,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'run') as Run,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'dive') as dive,
(select count(*) from table as t1 where t1.Name = table.Name and t1.Action = 'jump') as run
from table
But this would need to be rewritten every time you ad another action type. You should probably add an index to get the speed up on the table. But check the query plan with "real" data first.
in oracle database you can write like below query to show required solution :-
select * from table_name
pivot (count(*) for action in ('run','jump','drive'))
this will give the desired output..