SQL query help on joining 2 tables - sql

I have a table of items and another table with item groups (not all items are in a group).
I want to get all from the items table and if it exists in the groups table, I only want to get one item per group please show me SQL query for this.

I am defining a sample items and itemGroups table.
items
itemID, itemName, ItemDescription
itemGroups
itemGroupId, itemGroupname, ItemGroupDescription, itemID
Now to get all items from the item table if it exists in the itemsgroups table you have to compare the itemID which should your primary key in items table and compare it to the foreigh key for items which should be stored in the item groups table. So the SQL query could look like this
select * from items
where itemID in (select itemID from itemGroups)
Your coloumns names might be different but this is the general concept

If you have a groupid stored with each item row, try this:
select *
from items i
where groupid is null or itemid = (
select min(itemid) from items i2
where i2.groupid = i.groupid
)
If you didn't establish the linkage that way you might have to take this approach:
select *
from items i
where itemid = (
select coalesce(min(ig.itemid), i.itemid)
from itemgroups ig
where ig.groupid = (select groupid from itemgroups ig2 where ig2.itemid = i.itemid)
)

Assuming you have a table structure similar to this:
create table items (itemid int, itemname varchar2(50));
insert all
into items (itemid, itemname) values (1,'Apples')
into items (itemid, itemname) values (2,'Bananas')
into items (itemid, itemname) values (3,'Ford')
into items (itemid, itemname) values (4,'Honda')
into items (itemid, itemname) values (5,'Football')
select * from dual;
create table itemgroups (itemgroupid int, itemid int, itemgroupname varchar2(50));
insert all
into itemgroups (itemgroupid, itemid, itemgroupname) values (1,1,'Fruits')
into itemgroups (itemgroupid, itemid, itemgroupname) values (1,2,'Fruits')
into itemgroups (itemgroupid, itemid, itemgroupname) values (2,3,'Cars')
into itemgroups (itemgroupid, itemid, itemgroupname) values (2,4,'Cars')
select * from dual;
You could do a left join to get all the records from your items table (but only get one item per group). Then, do some kind of aggregation like MAX or MIN to only get one value per group like this:
select min(i.itemid) as itemid,
min(i.itemname) as itemname,
ig.itemgroupid,
ig.itemgroupname
from items i
left join itemgroups ig on i.itemid = ig.itemid
group by ig.itemgroupid,
ig.itemgroupname
order by min(i.itemid);
Result:
+--------+----------+-------------+---------------+
| itemid | itemname | itemgroupid | itemgroupname |
+--------+----------+-------------+---------------+
| 1 | Apples | 1 | Fruits |
| 3 | Ford | 2 | Cars |
| 5 | Football | (null) | (null) |
+--------+----------+-------------+---------------+
SQL Fiddle Demo

Related

Insert multiple rows using the same foreign key that needs to be selected

Assume that there are two tables:
CREATE TABLE products (id SERIAL, name TEXT);
CREATE TABLE comments (id SERIAL, product_id INT, txt TEXT);
I would like to insert multiple comments for the same product. But I don't know the product_id yet, only the product name.
So I could do:
INSERT INTO comments (txt, product_id) VALUES
( 'cool', (SELECT id from products WHERE name='My product name') ),
( 'great', (SELECT id from products WHERE name='My product name') ),
...
( 'many comments later', (SELECT id from products WHERE name='My product name') );
I'd like to reduce the repetition. How to do this?
I tried this but it inserts no rows:
INSERT INTO
comments (txt, product_id)
SELECT
x.txt,
p.id
FROM
(
VALUES
('Great product'),
('I love it'),
...
('another comment')
) x (txt)
JOIN products p ON p.name = 'My product name';
Your query works just fine. The only way it inserts zero rows is if there is no product in the table products for a given string - in your query named My product name. However, #a_horse_with_no_name's suggestion to use a CROSS JOIN might simplify your query a bit. You can combine it with a CTE to collect all comments and then CROSS JOIN it with the record you filtered in from table products.
CREATE TABLE products (id SERIAL, name TEXT);
CREATE TABLE comments (id SERIAL, product_id INT, txt TEXT);
INSERT INTO products VALUES (1, 'My product name'),(2,'Another product name');
WITH j (txt) AS (
VALUES ('Great product'),('I love it'),('another comment')
)
INSERT INTO comments (product_id,txt)
SELECT id,j.txt FROM products
CROSS JOIN j WHERE name = 'My product name';
SELECT * FROM comments;
id | product_id | txt
----+------------+-----------------
1 | 1 | Great product
2 | 1 | I love it
3 | 1 | another comment
Check this db<>fiddle

Choosing between using count with filter and altering the on condition

There are 2 tables, video and category.
create table category (
id integer primary key,
name text
);
create table video (
id integer primary key,
category_id integer references category (id),
quality text
);
insert into category (id, name) values (1, 'Entertainment');
insert into category (id, name) values (2, 'Drawing');
insert into video (id, category_id, quality) values (1, 1, 'sd');
insert into video (id, category_id, quality) values (2, 1, 'hd');
insert into video (id, category_id, quality) values (3, 1, 'hd');
I can get the list of all categories with the number of all videos.
select category.id, category.name, count(video)
from category left outer join video
on (category.id = video.category_id)
group by category.id;
result
id | name | count
----+---------------+-------
2 | Drawing | 0
1 | Entertainment | 3
(2 rows)
To get all categories with the number of HD videos, both of these queries can be used.
count with filter
select
category.id,
category.name,
count(video) filter (where video.quality='hd')
from category left outer join video
on (category.id = video.category_id)
group by category.id;
result
id | name | count
----+---------------+-------
2 | Drawing | 0
1 | Entertainment | 2
(2 rows)
on
select
category.id,
category.name,
count(video)
from category left outer join video
on (category.id = video.category_id and video.quality='hd')
group by category.id;
result
id | name | count
----+---------------+-------
2 | Drawing | 0
1 | Entertainment | 2
(2 rows)
The results are equal. What are the pros and cons of using the first and the second way? Which one is preferred?
The second query is somehow more efficient, because the on predicate of the join reduces the number of rows earlier, while the first query keeps them all, and then relies on the filter of the aggregate function. I would recommend the second query.
The first query would be useful if you were, for example, to perform several conditional counts, like:
select
category.id,
category.name,
count(*) filter (where video.quality='hd') no_hd_videos,
count(*) filter (where video.quality='sd') no_sd_videos
from category
left outer join video on category.id = video.category_id
group by category.id;

Rotate rows into columns with column names not coming from the row

I've looked at some answers but none of them seem to be applicable to me.
Basically I have this result set:
RowNo | Id | OrderNo |
1 101 1
2 101 10
I just want to convert this to
| Id | OrderNo_0 | OrderNo_1 |
101 1 10
I know I should probably use PIVOT. But the syntax is just not clear to me.
The order numbers are always two. To make things clearer
And if you want to use PIVOT then the following works with the data provided:
declare #Orders table (RowNo int, Id int, OrderNo int)
insert into #Orders (RowNo, Id, OrderNo)
select 1, 101, 1 union all select 2, 101, 10
select Id, [1] OrderNo_0, [2] OrderNo_1
from (
select RowNo, Id, OrderNo
from #Orders
) SourceTable
pivot (
sum(OrderNo)
for RowNo in ([1],[2])
) as PivotTable
Reference: https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-2017
Note: To build each row in the result set the pivot function is grouping by the columns not begin pivoted. Therefore you need an aggregate function on the column that is being pivoted. You won't notice it in this instance because you have unique rows to start with - but if you had multiple rows with the RowNo and Id you would then find the aggregation comes into play.
As you say there are only ever two order numbers per ID, you could join the results set to itself on the ID column. For the purposes of the example below, I'm assuming your results set is merely selecting from a single Orders table, but it should be easy enough to replace this with your existing query.
SELECT o1.ID, o1.OrderNo AS [OrderNo_0], o2.OrderNo AS [OrderNo_1]
FROM Orders AS o1
INNER JOIN Orders AS o2
ON (o1.ID = o2.ID AND o1.OrderNo <> o2.OrderNo)
From your sample data, simplest you can try to use min and MAX function.
SELECT Id,min(OrderNo) OrderNo_0,MAX(OrderNo) OrderNo_1
FROM T
GROUP BY Id

Duplicated records from SELECT DISTINCT COUNT statement

I have 2 tables with the following structure:
------------------------------------
| dbo.Katigories | dbo.Products |
|-----------------|------------------|
| product_id | product_id |
| Cat_Main_ID | F_material |
| Cat_Sub_ID | |
What I am trying to accomplish is the following:
I want to COUNT how many unique products (from the Products table) have as Cat_Main_ID=111, have as Cat_Sub_ID=222, and have as F_material=10
I have tried the following SELECT COUNT statement:
SELECT DISTINCT COUNT(*) AS METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222
The above statement is working, and counts the correct products, but is giving me duplicated records.
For example: When a product belongs to only 1 category the result of my count is correct and I am getting the number 1 as a result. But when a product belongs to more than one category then the result of the count is incorrect, depending on how many categories the product belongs. I know that the reason for the duplicates is that some of my products belong simultaneously to more than one category or sub category, so I am getting incorrect count results.
It will be truly appreciated if someone could help me with the syntax of my COUNT statement, in order to get the correct number of products (without duplicates) from my Products table.
!!!!!!!!!!!!!-----------------------------------------!!!!!!!!!!
Dear all.
Please forgive me for the inconvenience!
I'm so stupid, that I was placing your code in the wrong point in my page. Eventually almost all of the suggestions were correct.
Now I have a problem and I do not know which of the answers I choose to be right. All of them are correct!
A very big thank to all of you and I apologize again to all of you.
I suppose grouping should be approporate compared to distinct in this case
SELECT product_id, COUNT(product_id)
METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222
GROUP BY P.product_id
Can u try it using subselect to count, like this:
DECLARE #Katigories TABLE ( product_id int , Cat_Main_ID int , Cat_Sub_ID int )
DECLARE #Products TABLE( product_id int , F_material int )
INSERT INTO #Products (product_id,F_material) VALUES (1, 10)
INSERT INTO #Products (product_id,F_material) VALUES (2, 10)
INSERT INTO #Products (product_id,F_material) VALUES (3, 15)
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,111, 222 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,123, 223 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 1,444, 222 )
INSERT INTO #Katigories ( product_id ,Cat_Main_ID ,Cat_Sub_ID ) VALUES ( 2,133, 223 )
SELECT
P.product_id,
(SELECT COUNT(*) FROM #Katigories WHERE product_id = P.product_id AND Cat_Main_ID=111 AND Cat_Sub_ID=222 AND P.F_material=10) AS METR_AN_EXEI_001
FROM #Products P
If you need to count distinct products use count(distinct ...). Something like this.
SELECT COUNT(DISTINCT P.product_id) AS METR_AN_EXEI_001
FROM dbo.Products P
INNER JOIN dbo.Katigories K
ON P.product_id = K.product_id
AND
K.Cat_Main_ID = 111
AND
P.F_material = 10
AND
K.Cat_Sub_ID = 222

SQL DISTINCT, GROUP BY OR....?

I have a database with the following columns
SKU | designID | designColor | width | height | price | etc.
SKU number is unique and designID is repeated.
Basically, I want to DISTINCT or GROUP BY designID and get the value of the rest of row even though they are not repeated.
Example:
123 | A-1 | RED | 2 | 3 | $200 | etc.
135 | A-2 | BLU | 8 | 4 | $150 | etc.
After all, I should be able to sort them by either column. I already tried GROUP BY and DISTINCT but non of them return the rest of the row's value.
Example:
SELECT DISTINCT designID
FROM tbl_name
Which will return
A-1
A-2
and no other data.
GROUP BY example:
SELECT designID, designColor
FROM tbl_name
GROUP BY designID, designColor
Which will return
A-01 | RED
A-02 | BLU
Any idea so I can have DISTINCT result with all the row values?
Thanks in advance.
====================================
Thanks everybody for all your time and tips, Please let me describe more;
basically I need to eliminate the repeated designID and show just one of them and it doen't matter which one of them, first, middle or last one. Important is the one I show has to have all the row information, like SKU, Price, Size, etc. I dont't know, maybe I should use a different code rather than DISTINCT or GROUP BY.
Here is what I want from database.
Unless I misunderstand, you can SELECT DISTINCT on multiple columns:
SELECT
DISTINCT designID,
designColor,
width,
height,
price
FROM tbl_name
ORDER BY designColor
This will give you all the unique rows. If you have, for example, two designID values across 15 total rows with 2 and 3 different designColor values respectively, this will give you 5 rows.
If you don't care which row will be returned, you could use MAX and a subquery-group by:
create table #test(
SKU int,
designID varchar(10),
designColor varchar(10),
width int,
height int,
price real,
etc varchar(50)
)
insert into #test values(123, 'A-1' ,'RED', 2, 3, '200', 'etc')
insert into #test values(135, 'A-2' ,'BLUE', 8, 4, '150', 'etc')
insert into #test values(128, 'A-2' ,'YELLOW', 6, 9, '300', 'etc')
select t.* FROM #test t INNER JOIN
(
SELECT MAX(SKU) as MaxSKU,designID
FROM #test
GROUP BY designID
) tt
ON t.SKU = tt.MaxSKU;
drop table #test;
Result:
SKU designID designColor width height price etc
123 A-1 RED 2 3 200 etc
135 A-2 BLUE 8 4 150 etc
If they are all guaranteed to be duplicate (100% i.e. all columns) then a distinct would be your friend. i.e.
SELECT DISTINCT design_id, designColor, width, height, price FROM tbl_name
This will give distinct values on everything except SKU (which will always be unique and foil your distinct.
If you want unique designId values and the other results are different, then you need to figure out which of the values you want. If you really don't care, you can just arbitarily pick and aggregate function (say, MIN) and use GROUP BY
i.e.
SELECT designID, MIN(designColor) FROM tbl_name GROUP BY designID
This will give you a unique design id and a value for the other columns.
If you want the designID for the biggest skew, you could use a ranking function i.e.
;WITH rankedSKUs
AS
(
SELECT SKU, ROW_NUMBER() OVER(ORDER BY SKU DESC) as id
FROM tbl_name
)
SELECT *
FROM tbl_name T
WHERE EXISTS(SELECT * FROM rankedSKUs where id = 1 and SKU = T.sku)
This will return all columns for each distinct designID taking the largest value for SKU as authoritative for each designed.
If you want return every field, you might as well remove the distinct (assuming you have an id like you seem to).
Your request is really weird because if you take say,
SELECT DISTINCT designID
FROM tbl_name
you get a list of unique design id's, and if you then look up in the table for all rows with those id's, you'll get every single row in the table.
As a side note, the use of distinct usually means you designed your database badly (ie, not normalized) or that you designed your query badly (ie, you know, really badly). My money is on the former.
If you use LINQ you can use something like this:
get_data_context().my_table.GroupBy( t => t.designID ).Select( t => new { t.Key,
REST = t.Select( u => new { u.SKU , u.designID , u.designColor , u.width ,
u.height , u.price } ) } );