Enhance current SQLite query - sqlite

I inherited an old SQLite database I should not change (this is a requirement). There are many tables, but I will focus on two of them:
songs
----------
song_id (primary autoincrement)
group_id (external)
title
audio_file_path
wasPurchased (boolean, 0/1)
groups
----------
group_id (primary autoincrement, related to songs group_id)
group_name
At the moment, the application needs to perform this query:
SELECT song_id,title,audio_file_path,wasPurchased,G.group_name AS groupName,
G.group_id AS groupId FROM songs AS S JOIN groups AS G ON S.group_id=G.group_id
ORDER BY groupName DESC
Is there any way, with the same query, to extract how many distinct G.group_id have wasPurchased=0?
Any help is appreciated.

SELECT song_id,title,audio_file_path,wasPurchased,
G.group_name AS groupName, G.group_id AS groupId,
SUM (SELECT DISTINCT g.group_id
FROM yourtables/JOIN
WHERE wasPurchased = 0) as nb
FROM songs AS S
JOIN groups AS G ON S.group_id=G.group_id
ORDER BY groupName DESC
Not sure if it's the best way(never tried a select in a sum but...), but I think it will help you.

Related

Sqlite SELECT with multiple conditions

I have to create a database with a PRODUCTS table and a CATEGORIES table.
Each product has a name, a price, a creation date and may belong to several categories.
Categories have a name and a flag to indicate whether the category is private or public.
Then I have to select all records that belongs to more than 5 public categories.
I've created the tables like this:
CREATE TABLE PRODUCTS (
ID_PROD int NOT NULL PRIMARY KEY,
NAME TEXT(255),
PRICE INTEGER,
CREATION_DATE DATE
);
CREATE TABLE CATEGORIES (
ID_CAT INTEGER NOT NULL PRIMARY KEY,
NAME TEXT(255),
PRIVATE INTEGER
);
CREATE TABLE PROD_CAT (
ID INTEGER NOT NULL PRIMARY KEY,
ID_PROD INTEGER,
ID_CAT INTEGER,
FOREIGN KEY (ID_PROD) REFERENCES PRODUCTS(ID_PROD),
FOREIGN KEY (ID_CAT) REFERENCES CATEGORIES(ID_CAT)
)
I've managed to select all the records that belongs to more than 5 categories but I can't find out how to add the public category condition...
Here's what I've tried:
This works:
SELECT NAME
FROM PRODUCTS
WHERE ID_PROD IN (SELECT ID_PROD FROM PROD_CAT GROUP BY ID_PROD HAVING COUNT(*)>5)
But not this:
SELECT PRODUCTS.NAME
FROM PRODUCTS, CATEGORIES
WHERE ID_PROD IN (SELECT ID_PROD FROM PROD_CAT GROUP BY ID_PROD HAVING COUNT(*)>5)
AND CATEGORIES.PRIVATE = 1
Any help would be appreciated :)
You need a join of PROD_CAT to CATEGORIES:
SELECT NAME
FROM PRODUCTS
WHERE ID_PROD IN (
SELECT pc.ID_PROD
FROM PROD_CAT pc INNER JOIN CATEGORIES c
ON c.ID_CAT = pc.ID_CAT
WHERE c.PRIVATE -- or WHERE NOT c.PRIVATE for public categories
GROUP BY pc.ID_PROD
HAVING COUNT(*) > 5
)
Or, without the operator IN, with joins of all 3 tables:
SELECT p.ID_PROD, p.NAME
FROM PRODUCTS p
INNER JOIN PROD_CAT pc ON pc.ID_PROD = p.ID_PROD
INNER JOIN CATEGORIES c ON c.ID_CAT = pc.ID_CAT
WHERE c.PRIVATE -- or WHERE NOT c.PRIVATE for public categories
GROUP BY p.ID_PROD, p.NAME
HAVING COUNT(*) > 5
Since this looks like homework, I'll give a good hint.
Your first query returns products belonging to more than 5 categories, using a sub-query for the COUNT. The restriction you added in the second query was added to the top-level WHERE-clause, not the sub-query. The sub-query still works on PROD_CAT and still returns the same results, which may include public categories.

SQLite: count number of records in multiple tables

Using SQLite I can get all tablenames in my database:
SELECT name AS Tablename FROM sqlite_master WHERE type = 'table'
Result will be some tablenames, for example:
Tablename:
cars
planes
bus
How could I have a SQL query that will count the number of records for each table that is found, result should be:
Tablename Records:
cars 100
planes 200
bus 300
I understand that in this example I simply could run 3 SELECT COUNT() statements, however the number of tables can vary so that I can not hardcode a fixed number of SELECT COUNT()
All table and column names in a statement need to be known at the time it is compiled, so you can't do this dynamically.
You'd have to programmatically build up a new query string based on the results of getting the table names from sqlite_master. Either one query per table like you mentioned, or all together by creating something that looks like
SELECT 'table1' AS Tablename, count(*) AS Records FROM table1
UNION ALL
SELECT 'table2', count(*) FROM table2
-- etc.
You don't mention what language you're working in, so in psuedo-code of a functional style:
var allcounts = query("SELECT name FROM sqlite_master WHERE type = 'table'")
.map(name -> "SELECT '$name' AS Tablename, count(*) AS Records FROM \"$name\"")
.join(" UNION ALL ");
var totals = query(allcounts);

SQLite Group By Limit

I have a web service that generates radio station playlists and I'm trying to ensure that playlists never have tracks from the same artist more than n times.
So for example (unless it is Mandatory Metallica --haha) then no artist should ever dominate any 8 hour programming segment.
Today we use a query similar to this which generates smaller randomized playlists out of existing very large playlists:
SELECT FilePath FROM vwPlaylistTracks
WHERE Owner='{0}' COLLATE NOCASE AND
Playlist='{1}' COLLATE NOCASE
ORDER BY RANDOM()
LIMIT {2};
Someone then has to manually review the playlists and do some manual editing if the same artist appears consecutively or more than the desired limit.
Supposing the producer wants to ensure that no artist appears more than twice in the span of the playlist generated in this query (and assuming there is an artist field in the vwPlaylistTracks view; which there is) is GROUP BY the correct way to accomplish this?
I've been messing around with the view trying to accomplish this but this query always only returns 1 track from each artist.
SELECT
a.Name as 'Artist',
f.parentPath || '\' || f.fileName as 'FilePath',
p.name as 'Playlist',
u.username as 'Owner'
FROM mp3_file f,
mp3_track t,
mp3_artist a,
mp3_playlist_track pt,
mp3_playlist p,
mp3_user u
WHERE f.file_id = t.track_id
AND t.artist_id = a.artist_id
AND t.track_id = pt.track_id
AND pt.playlist_id = p.playlist_id
AND p.user_id = u.user_id
--AND p.Name = 'Alternative Rock'
GROUP BY a.Name
--HAVING Count(a.Name) < 3
--ORDER BY RANDOM()
--LIMIT 50;
GROUP BY creates exactly one result record for each distinct value in the grouped column, so this is not what you want.
You have to count any previous records with the same artist, which is not easy because the random ordering is not stable.
However, this is possible with a temporary table, which is ordered by its rowid:
CREATE TEMPORARY TABLE RandomTracks AS
SELECT a.Name as Artist, parentPath, name, username
FROM ...
WHERE ...
ORDER BY RANDOM();
CREATE INDEX RandomTracks_Artist on RandomTracks(Artist);
SELECT *
FROM RandomTracks AS r1
WHERE -- filter out if there are any two previous records with the same artist
(SELECT COUNT(*)
FROM RandomTracks AS r2
WHERE r2.Artist = r1.Artist
AND r2.rowid < r1.rowid
) < 2
AND -- filter out if the directly previous record has the same artist
r1.Artist IS NOT (SELECT Artist
FROM RandomTracks AS r3
WHERE r3.rowid = r1.rowid - 1)
LIMIT 50;
DROP TABLE RandomTracks;
It might be easier and faster to just read the entire playlist and to filter and reorder it in your code.

Complex sqlite query, what's the correct syntax?

The query is this one:
SELECT FriendID FROM Relationships WHERE UserID = 1
INTERSECT
(SELECT FriendID FROM Relationships WHERE UserID = 2
UNION SELECT UserID FROM Relationships WHERE FriendID = 2)
(for the curious readers, please note that the friend relationship is not necessarily symmetrical in this scenario)
I've tried all the possible combination of parentheses with no luck.
If I omit the parentheses, there's no operator precedence in the sense that it reads it like 5+6*3 = 33, so if I put the union before the intersection, the query works fine. But what will I do when I will have to intersect two unions?
You can use temporary tables in such case.
Thanks to Larry Lustig (which pointed me this), I rewrote my query as follows
SELECT FriendID FROM Relationships WHERE UserID = 1
INTERSECT SELECT ID FROM
(SELECT FriendID AS ID FROM Relationships WHERE UserID = 2
UNION SELECT UserID AS ID FROM Relationships WHERE FriendID = 2)
And it works.

SQL Server: How to select multiple columns with 1 column being distinct?

I am trying to do an SQL query on two tables to retrieve multiple columns with a certain column (PostID) to be distinct (and it is not the primary key of the that table).
In addition, I need the selected distinct rows to be the latest (one of the columns retrieved is the entry date).
Detailed description:
I am building a forum like application, using 3 tables to store data.
I use table1 to store user details, table2 to store the meta data for posts, table3 to store the post details, updates, and replies (postID is unique in table2 pointing towards an original post, while in table3, it is used to show the original post and updates and replies).
Table columns:
table1 (UserID, FullName, mobile, etc.)
table2 (postID, UserID, EntryDate, Deleted columns)
table3 (postdetailsId, PostID, UserID, Entrydate, etc.)
I am trying to retrieve all the posts for 1 user in a gridview, my SQL query uses the USERID to retrieve all his posts from the table. However, it is retrieving the original post and all its updates, and I only want to retrieve the latest update of each post.
How can it be done fully in SQL (I know I can do it in C# with the returned results)?
My query:
SELECT T1.FullName, T3.PostID, T3.EntryDate, T3.Title
FROM Table1 as T1, Table3 as T3
WHERE T3.UserID = T1.UserID
AND T3.UserID = #UserID
You could use GROUP BY PostID along with MAX(EntryDate)
SELECT *
FROM (
SELECT posts.*, ROW_NUMBER() OVER (PARTITION BY post_updates.UserID, post_updates.PostID ORDER BY post_updates.EntryDate DESC) AS rn
FROM table1 users
JOIN table3 post_updates
ON post_updates.userID = users.UserID
WHERE users.UserID = #UserID
) q
WHERE rn = 1

Resources