SQL - extracting data from 3 tables - sql

I'm new to sql and I'm wondering how to extract the relevant data from the sites and plugins table using the sites_plugins table. The data I am interested in is sites.description, plugins.fullName, plugins.currentVersion and sites_plugins.syncedAt
Below are the sql tables
INSERT INTO sites (id, name, description) VALUES
(1, "facebook", "Facebook"),
(2, "amazon", "Amazon"),
(3, "google", "Google");
INSERT INTO plugins (id, name, fullName, currentVersion) VALUES
(1, "yoast", "Yoast SEO", "16.8"),
(2, "jetpack", "Jetpack", "9.9.1"),
(3, "akismet", "Akismet", "4.1.10"),
(4, "wordfence", "Wordfence Security", "7.5.4"),
(5, "contact-form", "Contact Form 7", "7.5.4.2");
INSERT INTO sites_plugins (siteId, pluginId, version, syncedAt) VALUES
(1, 1, "16.8", NULL),
(1, 3, "3.8", '2021-07-01 10:00:00'),
(2, 3, "4.1.10", NULL),
(2, 5, "7.0", NULL),
(2, 4, "7.5.3", '2021-06-15 12:00:00');
Ultimately, I would very much like to achieve a data format like the following
{["Amazon", "Jetpack", "16.8", "NULL"]}
Thanks for any advice, Best Kacper

You must join all tables by thier linking column and pick for the SELECT the wanted columns, which you can freely pick from all tables
SELECT
s.description
,p.fullName
,sp.version
, sp.syncedAt
FROM sites_plugins sp
INNER JOIN sites s ON s.id = sp.siteId
INNER JOIN plugins p = p.id = sp.pluginId
But your wanted result is not possible with the data you provided

Related

Is it possible to set e the initial-select value of a recursive CTE query with a parameter?

Using this self-referencing table:
CREATE TABLE ENTRY (
ID integer NOT NULL,
PARENT_ID integer,
... other columns ...
)
There are many top-level rows (with PARENT_ID = NULL) that can have 0 to several levels of child rows, forming a graph like this:
(1, NULL, 'A'),
(2, 1, 'B'),
(3, 2, 'C'),
(4, 3, 'D'),
(5, 4, 'E'),
(6, NULL, 'one'),
(7, 6, 'two'),
(8, 7, 'three'),
(9, 6, 'four'),
(10, 9, 'five'),
(11, 10, 'six');
I want to write a query that would give me the subgraph (all related rows in both directions) for a given row, for instance (just showing the ID values):
ID = 3: (1, 2, 3, 4, 5)
ID = 6: (6, 7, 8, 9, 10, 11)
ID = 7: (6, 7, 8)
ID = 10: (6, 9, 10, 11)
It's similar to the query in ยง3.3 Queries against a Graph of the SQLite documentation, for returning a graph from any of its nodes:
WITH RECURSIVE subtree(x) AS (
SELECT 3
UNION
SELECT e1.ID x FROM ENTRY e1 JOIN subtree ON e1.PARENT_ID = subtree.x
UNION
SELECT e2.PARENT_ID x FROM ENTRY e2 JOIN subtree ON e2.ID = subtree.x
)
SELECT x FROM subtree
LIMIT 100;
... with 3 as the anchor / initial-select value.
This particular query works fine in DBeaver. The sqlite version available in db-fiddle gives a circular reference error, but this nested CTE gives the same result in db-fiddle.
However, I can only get this to work when the initial value is hard-coded in the query. I can't find any mention of how to supply that initial-select value as a parameter.
I'd think it should be straightforward. Maybe the case of having more than one top-level row is very unusual, or I'm overlooking something blindingly obvious?
Any suggestions?
As forpas points out above, SQLite doesn't support passing parameters to stored/user defined functions.
Using a placeholder in the prepared statement from the calling code is a good alternative.

Join table rows with empty key

I have these 2 tables which I would like to query:
create table active_pairs
(
pair text,
exchange_id integer
);
create table exchanges
(
exchange_id integer,
exchange_full_name text
);
INSERT INTO active_pairs (pair, exchange_id)
VALUES ('London/Berlin', 2),
('London/Berlin', 3),
('Paris/Berlin', 4),
('Paris/Berlin', 3),
('Oslo/Berlin', 2),
('Oslo/Berlin', 6),
('Huston/Berlin', 2);
INSERT INTO exchanges (exchange_id)
VALUES (2, 'Exchange 1'),
(3, 'Exchange 2'),
(4, 'Exchange 3'),
(3, 'Exchange 21'),
(2, 'Exchange 12'),
(6, 'Exchange 11'),
(2, 'Exchange 31');
I use these queries to list all pairs:
Query to list items:
SELECT * FROM common.active_pairs ap
INNER JOIN common.exchanges ce on ap.exchange_id = ce.exchange_id
WHERE ap.exchange_id = 1
GROUP BY pair, ap.exchange_id, ce.exchange_id, ap.id
HAVING COUNT(ap.pair) = 1;
I get as a result 172 rows.
Query to count rows to calculate pagination:
SELECT DISTINCT COUNT(*) OVER () counter
FROM common.active_pairs cp
INNER JOIN common.exchanges ce on cp.exchange_id = ce.exchange_id
WHERE cp.exchange_id = 1
GROUP BY pair
HAVING COUNT(cp.pair) = 1
I get as a result 158 rows.
I should be able to get equal total numbers from both queries in order to calculate properly pagination.
Is it possible that records with empty exchange_id in giving the different result?
You do not group by active_pairs.exchange_id but only by the name of the pair (active_pairs.pair). If that name is not unique, pairs with the same name but different ID are counted as one in your pagination query.

Query to detect changes in item location from one day before (RFID tracking)

I have some RFID tags on itens, which generate some data on a table.
Unfortunately, the reports on this system are poorly to non existant, and I want to make one.
Consider my data somethings as this. Everytime we "query" the system, it scans and inserts all item data on the same table:
My desired output, when using 02/03/2020 day as base, is this (based on the location):
I've done some color (based on the address column) on the first picture to better ilustrate.
In this example, you can see all possible status:
No change (ITEM_ID stood on the same place as the day before)
Item left forever (was sold, so it is not on any other place - example as ITEM_ID 00006)
Item was moved from address A to B (example ITEM_ID 0005: was on A104 and now is in A110)
New item added on a unused address (example ITEM_ID 0008, another bag)
New item on a address used yesterday (example ITEM_ID 0009 which was put on A104, used the day before by ITEM_ID 0005)
Someone told me that this can be accomplished by using ROLLUP or CUBE, but I'm not sure if it is the best approach, or how to use it.
The totals are a plus. I can export data do Excel and do a count based on the STATUS column (or even another select)
In summary, it is a tracking report.
ANY tips will be kindly appreciated.
SQL SERVER is Microsoft SQL Server 2016 Standard
Basically, you want to join the table with itself to track where things are going. I don't think I've addressed all of your concerns, but this should be a good place to start.
CREATE TABLE rfid
(
item_id INT,
address VARCHAR(50),
description VARCHAR(50),
qty INT,
[date] DATE
)
INSERT INTO rfid
(item_id,
address,
description,
qty,
[date])
VALUES (1,
'a100',
'cable',
100,
'2020-01-03'),
(2,
'a101',
'charger',
100,
'2020-01-03'),
(3,
'a102',
'laptop',
100,
'2020-01-03'),
(4,
'a103',
'chair',
100,
'2020-01-03'),
(5,
'a104',
'basket',
100,
'2020-01-03'),
(6,
'a105',
'bag',
100,
'2020-01-03'),
(1,
'a100',
'cable',
100,
'2020-02-03'),
(2,
'a101',
'charger',
100,
'2020-02-03'),
(3,
'a102',
'laptop',
100,
'2020-02-03'),
(4,
'a103',
'chair',
100,
'2020-02-03'),
(5,
'a110',
'basket',
100,
'2020-02-03'),
(8,
'a200',
'bag',
100,
'2020-02-03'),
(9,
'a104',
'keyboard',
100,
'2020-02-03');
WITH inventory_new (item_id, address, description)
AS (SELECT item_id,
address,
description
FROM rfid
WHERE [date] = '2020-02-03'),
inventory_old (item_id, address, description)
AS (SELECT item_id,
address,
description
FROM rfid
WHERE [date] = '2020-01-03')
SELECT COALESCE(o.item_id, n.item_id) item_id,
COALESCE(o.description, n.description) description,
CASE
WHEN o.address = n.address THEN 'no change'
WHEN o.address IS NULL THEN 'in'
WHEN n.address IS NULL THEN 'out'
END outcome
FROM inventory_old o
FULL OUTER JOIN inventory_new n
ON ( n.item_id = o.item_id )

Oracle SQL: count frequencies and convert to columns

I want to count how frequent values appear in certain columns and create a new table, with the values as columns and the frequencies as data. Example:
create table users
(id number primary key,
name varchar2(255));
insert into users values (1, 'John');
insert into users values (2, 'Joe');
insert into users values (3, 'Max');
create table meals
(id number primary key,
user_id number,
food varchar2(255));
insert into meals values (1, 1, 'Apple');
insert into meals values (2, 1, 'Apple');
insert into meals values (3, 1, 'Orange');
insert into meals values (4, 1, 'Bread');
insert into meals values (5, 1, 'Apple');
insert into meals values (6, 2, 'Apple');
insert into meals values (7, 2, 'Bread');
insert into meals values (8, 2, 'Bread');
insert into meals values (9, 2, 'Apple');
insert into meals values (10, 3, 'Orange');
insert into meals values (11, 3, 'Bread');
insert into meals values (12, 3, 'Bread');
So I got different users and their meals (here Bread, Apple and Oranges). For every user I want to know how often did he eat the different food. The following query does exactly what I want:
select
(select count(id) from meals where meals.user_id = users.id and meals.food = 'Apple') as count_apple,
(select count(id) from meals where meals.user_id = users.id and meals.food = 'Orange') as count_orange,
(select count(id) from meals where meals.user_id = users.id and meals.food = 'Bread') as count_bread
from users;
The problem is, this is REALLY slow, especially when I got more than 100.000 users and dozens of different foods. I am sure that there is a faster way, but I am not experienced enough in SQL to solve this problem.
If you're using 11g, then you can use the pivot operator, like so:
select * from (
select user_id, food from meals
)
pivot (count(*) as count for (food) in ('Apple', 'Orange', 'Bread'));
Otherwise you'll have to do a manual pivot:
select user_id,
sum(case when food = 'Apple' then 1 else 0 end) count_apple,
sum(case when food = 'Orange' then 1 else 0 end) count_orange,
sum(case when food = 'Bread' then 1 else 0 end) count_bread
from meals
group by user_id
In either case, these should be faster than your original approach as you're only accessing the meals table once.

Grouping records by subsets SQL

I have a database with PermitHolders (PermitNum = PK) and DetailedFacilities of each Permit Holder. In the tblPermitDetails table there are 2 columns
PermitNum (foreign Key)
FacilityID (integer Foreign Key Lookup to Facility table).
A permitee can have 1 - 29 items on their permit, e.i. Permit 50 can have a Boat Dock (FacID 4), a Paved walkway (FacID 17) a Retaining Wall (FacID 20) etc. I need an SQL filter/display whatever, ALL PERMIT #s that have ONLY FacIDs 19, 20, or 28, NOT ones that have those plus "x" others,....just that subset. I've worked on this for 4 days, would someone PLEASE help me? I HAVE posted to other BB but have not received any helpful suggestions.
As Oded suggested, here are more details.
There is no PK for the tblPermitDetails table.
Let's say that we have Permitees 1 - 10; Permit 1 is John Doe, he has a Boat Dock (FacID 1), a Walkway (FacID 4), a buoy (FacID 7), and Underbrushing (FacID 19)...those are 3 records for Permit 1. Permit 2 is Sus Brown, she has ONLY underbrushing (FacID 19), Permit 3 is Steve Toni, he has a Boat Dock (FacID 1), a Walkway (FacID 4), a buoy (FacID 7), and a Retaining Wall (FacID 20). Permit 4 is Jill Jack, she has Underbrushing (FacID 19), and a Retaining Wall (FacID 20). I could go on but i hope you follow me. I want an SQL (for MS Access) that will show me ONLY Permits 2 & 4 because they have a combination of FacIDs 19 & 20 [either both, or one or the other], BUT NOT ANYTHING ELSE such as Permit 1 who has #19, but also has 4 & 7.
I hope that helps, please say so if not.
Oh yea, I DO know the difference between i.e. and e.g. since i'm in my 40's have written over 3000 pages of archaeological field reports and an MA thesis, but I'm really stressed out here from struggling with this SQL and could care less about consulting the Chicago Manual of Style before banging out a plea for help. SO, DON"T be coy about my compostion errors! Thank you!
Untested, but how about something like this?
SELECT DISTINCT p.PermitNum
FROM tblPermitDetails p
WHERE EXISTS
(SELECT '+'
FROM tblFacility f
WHERE p.FacilityID = f.FacilityID
AND f.facilityID = 19 )
AND EXISTS
(SELECT '+'
FROM tblFacility f
WHERE p.FacilityID = f.FacilityID
AND f.facilityID = 20 )
AND EXISTS
(SELECT '+'
FROM tblFacility f
WHERE p.FacilityID = f.FacilityID
AND f.facilityID = 28 )
AND NOT EXISTS
(SELECT '+'
FROM tblFacility f
WHERE p.FacilityID = f.FacilityID
AND f.facilityID NOT IN (19,20,28) )
SELECT PermitNum
FROM tblPermitDetails
WHERE FacilityID IN (19, 20, 28)
GROUP BY PermitNum
HAVING COUNT(PermitNum)=3
I wasn't sure if you wanted ALL of 19,20,28 or ANY of 19,20,28... also, this is untested, but if you want the any of solution it should be fairly close
Select
allowed.PermitNum
from
DetailedFacilties allowed
join DetailedFacilities disallowed on allowed.PermitNum != disallowed.PermitNum
where
allowed.FacilityID in (19, 20, 28)
and disallowed.FacilityID not in (19, 20, 28)
SELECT DISTINCT PermitNum FROM tblPermitDetails t1
WHERE FacilityID IN (19, 20, 28)
AND NOT EXISTS (SELECT 1 FROM tblPermitDetails t2
WHERE t2.PermitNum = t1.PermitNum
AND FacilityId NOT IN (19, 20, 28));
Or, in prose, get the list of PermitNums that have any of the requested permit numbers as long as no row exists for that PermitNum that isn't in the requested list.
A more optimized version of the same query would be the following:
SELECT PermitNum FROM (SELECT DISTINCT PermitNum FROM tblPermitDetails
WHERE FacilityID IN (19, 20, 28)) AS t1
WHERE NOT EXISTS (SELECT 1 FROM tblPermitDetails t2
WHERE t2.PermitNum = t1.PermitNum
AND FacilityID NOT IN (19, 20, 28));
It's a little harder to read, but it will involve fewer "NOT EXISTS" subqueries by doing the "DISTINCT" part first.
Update:
David-W-Fenton mentions that NOT EXISTS should be avoided for optimization reasons. For a small table, this probably won't matter much, but you could also do the query using COUNT(*) if you needed to avoid NOT EXISTS:
SELECT DISTINCT PermitNum FROM tblPermitDetails t1
WHERE (SELECT COUNT(*) FROM tblPermitDetails t2
WHERE t1.PermitNum = t2.PermitNum
AND FacilityID IN (19, 20, 28))
=
(SELECT COUNT(*) FROM tblPermitDetails t3
WHERE t1.PermitNum = t3.PermitNum)
What about (untested)
select permitnum
from tblPermitDetails t1
left outer join
(Select distinct permitnum from tblPermitDetails where facilityId not in (19, 20, or 28)) t2
on t1.permitnum=t2.permitnum
where t2.permitnum is null
i.e. we find all the permits that cannot match your criteria (they have at least one detail outside those you list), then we find all the permits that are left, via a left join and where criteria.
with indexes set up properly, this should be pretty quick.
Quick way might be to only look at the ones with exactly three matches (with an inner query), and then among those only include the ones that have 19, 20, and 28.
Of course, that is sort of a brute force method, and not very elegant. But it has the small benefit of being understandable. None of the approaches I can think of will be easy to customize to various other sets of values.
Ok, it seems i didn't understand the problem at first. So, again:
I will recreate the example by Stacy here:
DECLARE #PermitHolders TABLE
(PermitNum INT NOT NULL,
PermitHolder VARCHAR(20))
DECLARE #tblPermitDetails TABLE
(PermitNum INT,
FacilityID INT)
INSERT INTO #PermitHolders VALUES (1, 'John Doe')
INSERT INTO #PermitHolders VALUES (2, 'Sus Brown')
INSERT INTO #PermitHolders VALUES (3, 'Steve Toni')
INSERT INTO #PermitHolders VALUES (4, 'Jill Jack')
INSERT INTO #tblPermitDetails VALUES (1, 1)
INSERT INTO #tblPermitDetails VALUES (1, 4)
INSERT INTO #tblPermitDetails VALUES (1, 7)
INSERT INTO #tblPermitDetails VALUES (1, 19)
INSERT INTO #tblPermitDetails VALUES (2, 19)
INSERT INTO #tblPermitDetails VALUES (3, 1)
INSERT INTO #tblPermitDetails VALUES (3, 4)
INSERT INTO #tblPermitDetails VALUES (3, 7)
INSERT INTO #tblPermitDetails VALUES (3, 20)
INSERT INTO #tblPermitDetails VALUES (4, 19)
INSERT INTO #tblPermitDetails VALUES (4, 20)
And this is the solution:
SELECT * FROM #PermitHolders
WHERE (PermitNum IN (SELECT PermitNum FROM #tblPermitDetails WHERE FacilityID IN (19, 20, 28)))
AND (PermitNum NOT IN (SELECT PermitNum FROM #tblPermitDetails WHERE FacilityID NOT IN (19, 20, 28)))
I have one observation on the side:
You didn't mention any PK for tblPermitDetails. If non exists, this may not be good for performance. I recommend that you create a PK using both PermitNum and FacilityID (composite key) because this will serve as both your PK and a useful index for the expected queries.