How to select correctly in SQL? - sql

I have a table containing a list of image related tags.
See the details here in this screenshot:
What I tried:
SELECT a.*
FROM wallpaper_tag as a, wallpaper_tag as b
WHERE a.tag = 1
AND b.tag = 2
AND a.wallpaper = b.wallpaper
ORDER BY wallpaper
LIMIT 10000
This SQL query works fine for me. But is there a better option?
I want to get a list of wallpapers if two tags match at the same time.

If what you actually want is all the wallpapers with tags 1 and 2, you can do it with aggregation:
SELECT wallpaper
FROM wallpaper_tag
WHERE tag IN (1, 2)
GROUP BY wallpaper
HAVING COUNT(DISTINCT tag) = 2
ORDER BY wallpaper
LIMIT 10000
If the combination of wallpaper and tag is unique, the HAVING clause may be simplified to just:
HAVING COUNT(*) = 2

I'd use an explicit join:
SELECT a.*
FROM wallpaper_tag as a
INNER JOIN wallpaper_tag as b
ON a.wallpaper = b.wallpaper
WHERE a.tag = 1 AND b.tag = 2
ORDER BY a.wallpaper
LIMIT 10000
I prefer this syntax as it makes it clearer what tables are joined and how they are joined.

Related

Only one expression can be specified in the SELECT list where the subquery is not introduced with EXISTS

I have the following code - it's odd because I seem to only have one expression in my SELECT list at the top, but I am still getting an error stating that only one condition can be specified in the SELECT list where the query is not introduced with 'EXISTS'.
I am trying to get the recent games won from the last three games.
Thanks
DECLARE #RecentGamesWon INT
SET #RecentGamesWon = (SELECT COUNT(*)
FROM game g
JOIN inserted ON inserted.HomeTeamID = g.HomeTeamID
WHERE g.HomeTeamID IN (SELECT TOP 3 *
FROM game g
WHERE (g.hometeamid = inserted.HomeTeamID
AND g.HomeScore > g.AwayScore)
OR (g.awayteamid = inserted.HomeTeamID
AND g.AwayScore > g.HomeScore)
ORDER BY g.GameDate));
I am unable to reproduce your problem statement, because i would require the data with proper table structure. Please provide the variable tables with the data what you are using, with you problem statements.
BUT, for a while you can try by correcting your existing query i.e. use TOP 3 HomeTeamID rather than TOP 3 *.
And if problem still persists, then use CTE to have your inner query results and then set the count from CTE to your desired variable.
You can't return two (or multiple) columns in your subquery to do the comparison in the WHERE A_ID IN (subquery) clause - which column is it supposed to compare A_ID to? Your subquery must only return the one column needed for the comparison to the column on the other side of the IN. So the query needs to be of the form:
SELECT * From ThisTable WHERE ThisColumn IN (SELECT ThatColumn FROM ThatTable)
In your query, this is the portion failing -
... WHERE g.HomeTeamID IN (SELECT TOP 3 * FROM game g ...
To fix - Your query should be as follows -
SET #RecentGamesWon = (SELECT COUNT(*)
FROM game g
JOIN inserted ON inserted.HomeTeamID = g.HomeTeamID
WHERE g.HomeTeamID IN (SELECT TOP 3 g.HomeTeamID
FROM game g
WHERE (g.hometeamid = inserted.HomeTeamID
AND g.HomeScore > g.AwayScore)
OR (g.awayteamid = inserted.HomeTeamID
AND g.AwayScore > g.HomeScore)
ORDER BY g.GameDate));
I believe you want:
SELECT #RecentGamesWon COUNT(*)
FROM inserted i CROSS APPLY
(SELECT TOP 3 g.*
FROM game g
WHERE i.HomeTeamID IN (g.hometeamid, g.awayteamid)
ORDER BY g.GameDate DESC
) g
WHERE (g.hometeamid = i.HomeTeamID AND g.HomeScore > g.AwayScore) OR
(g.awayteamid = i.HomeTeamID AND g.AwayScore > g.HomeScore);
This selects the 3 most recent games played by i.HomeTeamID and t hen counts the number of wins.

How can I join on multiple columns within the same table that contain the same type of info?

I am currently joining two tables based on Claim_Number and Customer_Number.
SELECT
A.*,
B.*,
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbp.Compound_Info AS B ON A.Claim_Number = B.Claim_Number AND A.Customer_Number = B.Customer_Number
WHERE A.Filled_YearMonth = '201312' AND A.Compound_Ind = 'Y'
This returns exactly the data I'm looking for. The problem is that I now need to join to another table to get information based on a Product_ID. This would be easy if there was only one Product_ID in the Compound_Info table for each record. However, there are 10. So basically I need to SELECT 10 additional columns for Product_Name based on each of those Product_ID's that are being selected already. How can do that? This is what I was thinking in my head, but is not working right.
SELECT
A.*,
B.*,
PD_Info_1.Product_Name,
PD_Info_2.Product_Name,
....etc {Up to 10 Product Names}
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbo.Compound_Info AS B ON A.Claim_Number = B.Claim_Number AND A.Customer_Number = B.Customer_Number
LEFT JOIN Company.dbo.Product_Info AS PD_Info_1 ON B.Product_ID_1 = PD_Info_1.Product_ID
LEFT JOIN Company.dbo.Product_Info AS PD_Info_2 ON B.Product_ID_2 = PD_Info_2.Product_ID
.... {Up to 10 LEFT JOIN's}
WHERE A.Filled_YearMonth = '201312' AND A.Compound_Ind = 'Y'
This query not only doesn't return the correct results, it also takes forever to run. My actual SQL is a lot longer and I've changed table names, etc but I hope that you can get the idea. If it matters, I will be creating a view based on this query.
Please advise on how to select multiple columns from the same table correctly and efficiently. Thanks!
I found put my extra stuff into CTE and add ROW_NUMBER to insure that I get only 1 row that I care about. it would look something like this. I only did for first 2 product info.
WITH PD_Info
AS ( SELECT Product_ID
,Product_Name
,Effective_Date
,ROW_NUMBER() OVER ( PARTITION BY Product_ID, Product_Name ORDER BY Effective_Date DESC ) AS RowNum
FROM Company.dbo.Product_Info)
SELECT A.*
,B.*
,PD_Info_1.Product_Name
,PD_Info_2.Product_Name
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbo.Compound_Info AS B
ON A.Claim_Number = B.Claim_Number
AND A.Customer_Number = B.Customer_Number
LEFT JOIN PD_Info AS PD_Info_1
ON B.Product_ID_1 = PD_Info_1.Product_ID
AND B.Fill_Date >= PD_Info_1.Effective_Date
AND PD_Info_2.RowNum = 1
LEFT JOIN PD_Info AS PD_Info_2
ON B.Product_ID_2 = PD_Info_2.Product_ID
AND B.Fill_Date >= PD_Info_2.Effective_Date
AND PD_Info_2.RowNum = 1

Query not working as expected

Goal
Select distinct ids from blog_news where
active = 1
title is not empty
has at least one picture unless picture is logo, or at least one video
The statement so far
select distinct n.id from blog_news n
left join blog_pics p ON n.id = p.blogid and active = '1' and trim(n.title) != ''
left join blog_vdos v ON n.id = v.blogid
where (p.islogo = '0' and p.id is not null) OR (v.id is not null)
order by `newsdate` desc, `createdate` desc
The issue
selects blog_news ids that have pictures, unless they're logos [correct]
selects blog_news ids that have both videos and pictures [correct]
does not select blog_news ids that have only videos [wrong]
How about this:
SELECT DISTINCT n.id
FROM blog_news n
WHERE n.active = '1'
AND trim(n.title) != ''
AND (EXISTS (SELECT 1
FROM blog_pics p
WHERE p.blogid = n.id
AND p.islogo = 0)
OR EXISTS (SELECT 1
FROM blog_vdos v
WHERE v.blogid = n.id)
)
ORDER BY n.newsdate desc, n.createdate desc
Where you are just interested in the existence (or not) of child rows then it is often clearer and easier to use EXISTS.
I can't see any problem in your query.
I expect active is column in blog_news table, you should call it n.active. If this column is in blog_pics table, then this is the problem.
I would add the condition (n.active, n.title) to WHERE, as it's not related to left join (blog_pics) - but that's just for better readability, the result would be the same.
You can write the query using sub selects as well:
SELECT n.id FROM blog_news n
WHERE n.active = 1 AND TRIM(n.title) != '' AND n.id IN (
SELECT DISTINCT p.blogid FROM blog_pics p WHERE p.islogo = 0 UNION
SELECT DISTINCT v.blogid FROM blog_vdos
);

MS-Access -> SELECT AS + ORDER BY = error

I'm trying to make a query to retrieve the region which got the most sales for sweet products. 'grupo_produto' is the product type, and 'regiao' is the region. So I got this query:
SELECT TOP 1 r.nm_regiao, (SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND
cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao ORDER BY total DESC
Then when i run the query, MS-Access asks for the "total" parameter. Why it doesn't consider the newly created 'column' I made in the select clause?
Thanks in advance!
Old Question I know, but it may help someone knowing than while you cant order by aliases, you can order by column index. For example, this will work without error :
SELECT
firstColumn,
IIF(secondColumn = '', thirdColumn, secondColumn) As yourAlias
FROM
yourTable
ORDER BY
2 ASC
The results would then be ordered by the values found in the second column wich is the Alias "yourAlias".
Aliases are only usable in the query output. You can't use them in other parts of the query. Unfortunately, you'll have to copy and paste the entire subquery to make it work.
You can do it like this
select * from(
select a + b as c, * from table)
order by c
Access has some differences compared to Sql Server.
Why it doesn't consider the newly
created 'column' I made in the select
clause?
Because Access (ACE/Jet) is not compliant with the SQL-92 Standard.
Consider this example, which is valid SQL-92:
SELECT a AS x, c - b AS y
FROM MyTable
ORDER
BY x, y;
In fact, x and y the only valid elements in the ORDER BY clause because all others are out of scope (ordinal numbers of columns in the SELECT clause are valid though their use id deprecated).
However, Access chokes on the above syntax. The equivalent Access syntax is this:
SELECT a AS x, c - b AS y
FROM MyTable
ORDER
BY a, c - b;
However, I understand from #Remou's comments that a subquery in the ORDER BY clause is invalid in Access.
Try using a subquery and order the results in an outer query.
SELECT TOP 1 * FROM
(
SELECT
r.nm_regiao,
(SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
) T1
ORDER BY total DESC
(Not tested.)
How about:
SELECT TOP 1 r.nm_regiao
FROM (SELECT Dw_Empresa.cod_regiao,
Count(Dw_Empresa.cod_regiao) AS CountOfcod_regiao
FROM Dw_Empresa
WHERE Dw_Empresa.[grupo_produto]='1'
GROUP BY Dw_Empresa.cod_regiao
ORDER BY Count(Dw_Empresa.cod_regiao) DESC) d
INNER JOIN tb_regiao AS r
ON d.cod_regiao = r.cod_regiao
I suggest using an intermediate query.
SELECT r.nm_regiao, d.grupo_produto, COUNT(*) AS total
FROM Dw_Empresa d INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
GROUP BY r.nm_regiao, d.grupo_produto;
If you call that GroupTotalsByRegion, you can then do:
SELECT TOP 1 nm_regiao, total FROM GroupTotalsByRegion
WHERE grupo_produto = '1' ORDER BY total DESC
You may think it's extra work to create the intermediate query (and, in a sense, it is), but you will also find that many of your other queries will be based off of GroupTotalsByRegion. You want to avoid repeating that logic in many other queries. By keeping it in one view, you provide a simplified route to answering many other questions.
How about use:
WITH xx AS
(
SELECT TOP 1 r.nm_regiao, (SELECT COUNT(*)
FROM Dw_Empresa
WHERE grupo_produto='1' AND
cod_regiao = d.cod_regiao) as total
FROM Dw_Empresa d
INNER JOIN tb_regiao r ON r.cod_regiao = d.cod_regiao
) SELECT * FROM xx ORDER BY total

Filtering objects with many to many relationships

I have a two tables one for documents one for mapping to categories.
Documents
id | document_name
1 | somename.doc
2 | anothername.doc
Documents_to_categories
cat_id | doc_id
10 | 1
10 | 2
11 | 3
12 | 1
Some documents can map to multiple categories. What I want is to be able to select documents that belong in multiple categories, like in a filtering scheme.
Basically in the script I have an array of document id's that were a result from a search. I need to filter those documents down based on categories.
This is what something like what I'm aiming for (I know it doesn't work but for example).
SELECT *
FROM Documents_to_categories A
JOIN Documents B ON A.doc_id = B.id
WHERE B.id IN (6703,6614,2286)
AND A.cat_id = :ID0
AND A.cat_id = :ID1
Edit: Sorry for all those who answered, my first time posting, was unclear with question. Hopefully this is more clear on what I want.
select a.doc_id, count(*)
from Documents_to_categories A
where a.doc_id in (<doc_id list>)
and a.cat_id in (<cat_id list>)
group by doc_id
having count(*) = <cat_id list length>
will return a list of doc_ids that have an entry for every category in the cat_id list. Note that you also need to supply the length of the list for the having clause.
You can use this to retrieve all of the details required in an outer select, using the select above to populate an inlist for the doc_ids.
This ends up looking like :
select b.id, b.document_name, a,cat_id
from Documents_to_categories A,
Documents B
where a.doc_id = b.id
and b.id in (select mylist.doc_id from (
select a.doc_id, count(*)
from Documents_to_categories A
where a.doc_id in (<doc_id list>)
and a.cat_id in (<cat_id list>)
group by doc_id
having count(*) = <cat_id list length>
) as mylist )
Assuming you're passing in the list of document id's as a comma separated list, the simplest solution would be to use dynamic SQL:
L_CURSOR SYS_REFCURSOR;
L_QUERY VARCHAR2(5000) DEFAULT 'SELECT d.document_name
FROM DOCUMENTS d
JOIN DOCUMENTS_TO_CATEGORIES c10 ON c10.doc_id = d.id
AND c10.cat_id = 10
JOIN DOCUMENTS_TO_CATEGORIES c12 ON c12.doc_id = d.id
AND c12.cat_id = 12
WHERE d.id IN (:document_list)'
BEGIN
FOR I IN 0 .. (TRUNC(LENGTH(L_QUERY) / 255)) LOOP
DBMS_OUTPUT.PUT_LINE(SUBSTR(L_QUERY, I * 255 + 1, 255));
END LOOP;
OPEN L_CURSOR FOR L_QUERY USING IN_DOCUMENT_LIST;
RETURN L_CURSOR;
END;
What I want is to be able to select documents that belong in multiple categories, like in a filtering scheme. Ex: I want to filter by category 10 and 12, so only documents belonging to 10 and 12 return (in this case doc 1).
Using HAVING:
SELECT d.document_name
FROM DOCUMENTS d
JOIN DOCUMENTS_TO_CATEGORIES dtc ON dtc.doc_id = d.id
WHERE dtc.cat_id IN (10, 12)
GROUP BY d.document_name
HAVING COUNT(DISTINCT dtc.cat_id) = 2
Using JOINs
SELECT d.document_name
FROM DOCUMENTS d
JOIN DOCUMENTS_TO_CATEGORIES c10 ON c10.doc_id = d.id
AND c10.cat_id = 10
JOIN DOCUMENTS_TO_CATEGORIES c12 ON c12.doc_id = d.id
AND c12.cat_id = 12
To get the documents that ONLY appear in BOTH categories, you could use the ANSI SQL INTERSECT operator as follows:
SELECT Documents.document_name
FROM Documents
WHERE Documents.id IN
(
SELECT Documents_to_categories.docid
FROM Documents_to_categories
WHERE Documents_to_categories.catid = 10
INTERSECT
SELECT Documents_to_categories.docid
FROM Documents_to_categories
WHERE Documents_to_categories.catid = 12
/*.. add additional filters */
)
This gives you all documents that belong in at least one category, of which at least one category is either 10 or 12.
SELECT
doc_id
FROM Documents_to_categories
WHERE doc_id IN (<your list of doc_ids>)
GROUP BY doc_id
HAVING COUNT(*) > 1
AND MAX(CASE WHEN cat_id IN (10, 12) THEN 1 ELSE 0 END) = 1;
I want to filter by category 10 and
12, so only documents belonging to 10
and 12 return (in this case doc 1)
You need to generate SQL query from your app:
SELECT
d.id,
d.document_name
FROM
Documents as d,
Documents_to_categories dc1,
Documents_to_categories dc2,
WHERE
d.id = dc1.doc_id
AND d.id = dc2.doc_id
AND dc2.cat_id = 10
AND dc1.cat_id = 12
This should work on all major database servers.
instead of selecting from a documents
table, I want to select it from an
array of document ID's.
Not sure what you mean. There is no array an entity in relational world (eg SQL). There is one which is called table or maybe set.