I have two tables: ItemCategories and Items. ItemCategories list the categories for items and contain language dependent text strings. The CultureCode column determines which language the text belongs to.
ItemCategories
ID Name CultureCode
1 Food 'en-us'
2 Clothing 'en-us'
3 Computers 'en-us'
4 Books 'en-us'
1 Essen 'de-de'
2 Kleidung 'de-de'
3 Rechner 'de-de'
4 Bücher 'de-de'
Items
ID CategoryID1 CategoryID2
4 1 NULL
5 NULL 2
6 3 4
7 NULL NULL
The Items table lists each item and the values under the CategoryID1 and CategoryID2 refer to the ID field in CategoryItems. The values of CategoryID1 and CategoryID2 can be both null , both set to an ID or just one of them set to an ID value.
I need to create an SQL (T-SQL) that returns the ID from Items along with its correspond Name from ItemCategories when the CultureCode is set to a specific language. So if my CultureCode is set to 'en-us', the results should be:
ID CategoryID1 CategoryID2 CategoryName1 CategoryName2
4 1 NULL Food Null
5 NULL 2 null Clothing
6 3 4 Computers Books
7 NULL NULL null null
This is what I have so far:
Select I.*
From Items I
Left Join ItemCategories IC On (IC.ID = I.CategoryID1) Or (IC.ID = I.CategoryID2)
where (IC.CultureCode = 'en-us') Or (IC.CultureCode is null)
But this is not giving me the results I want.
Sounds like you need to split your LEFT JOIN into two separate LEFT JOINs:
SELECT I.ID, I.CategoryID1, I.CategoryID2, CategoryName1 = IC1.Name, CategoryName2 = IC2.Name
FROM Items I
LEFT JOIN ItemCategories IC1 ON IC1.ID = I.CategoryID1 AND IC1.CultureCode = 'en-us'
LEFT JOIN ItemCategories IC2 ON IC2.ID = I.CategoryID2 AND IC2.CultureCode = 'en-us';
Give a try and let me know your results/Comments.
CREATE TABLE ItemCategories
(
ID INT
,Name VARCHAR(50)
,CultureCode VARCHAR(5)
)
GO
INSERT INTO ItemCategories VALUES(1 ,'Food ' , 'en-us')
INSERT INTO ItemCategories VALUES(2 ,'Clothing' , 'en-us')
INSERT INTO ItemCategories VALUES(3 ,'Computers' , 'en-us')
INSERT INTO ItemCategories VALUES(4 ,'Books' , 'en-us')
INSERT INTO ItemCategories VALUES(1 ,'Essen' , 'de-de')
INSERT INTO ItemCategories VALUES(2 ,'Kleidung' , 'de-de')
INSERT INTO ItemCategories VALUES(3 ,'Rechner' , 'de-de' )
INSERT INTO ItemCategories VALUES(4 ,'Bücher ' , 'de-de')
CREATE TABLE Items
(
ID INT
,CategoryID1 INT
,CategoryID2 INT
)
GO
INSERT INTO Items VALUES (4 , 1 , NULL)
INSERT INTO Items VALUES (5 , NULL , 2 )
INSERT INTO Items VALUES (6 , 3 , 4 )
INSERT INTO Items VALUES (7 , NULL , NULL)
Select I.*,IC.Name,IT.Name
From Items I
left join ItemCategories IC ON (IC.ID = I.CategoryID1) AND IC.CultureCode = 'en-us'
left join ItemCategories IT On (It.ID = I.CategoryID2) AND IT.CultureCode = 'en-us'
You can do it in two ways:
Use subqueries:
Select
I.ID,
(SELECT TOP 1 ID FROM ItemCategories IC WHERE IC.ID=I.CategoryID1 AND IC.CultureCode = 'en-us') AS CategoryID1,
(SELECT TOP 1 ID FROM ItemCategories IC WHERE IC.ID=I.CategoryID2 AND IC.CultureCode = 'en-us') AS CategoryID2,
(SELECT TOP 1 Name FROM ItemCategories IC WHERE IC.ID=I.CategoryID1 AND IC.CultureCode = 'en-us') AS CategoryName1,
(SELECT TOP 1 Name FROM ItemCategories IC WHERE IC.ID=I.CategoryID2 AND IC.CultureCode = 'en-us') AS CategoryName2
From Items I
Or, split query into 2 joins
SELECT DISTINCT I.*, IC1.Name AS CategoryName1, IC2.Name AS CategoryName2
FROM Items I
LEFT JOIN ItemCategories IC1 ON I.CategoryID1=IC1.ID
LEFT JOIN ItemCategories IC2 ON I.CategoryID2=IC2.ID
WHERE (IC1.CultureCode IS NULL OR IC1.CultureCode = 'en-us') AND (IC2.CultureCode IS NULL OR IC2.CultureCode = 'en-us');
Related
I've created 3 tables: item, item_category and category, when I select items with two specific categories it returns 0 rows, even when I have items with those categories...
Does anyone have any idea why is that happening?
For example the select without filters I'm using is:
SELECT i.item_id, c.cate_friendly_id
FROM item i , item_category ic, category c
WHERE
i.item_id = ic.item_id
AND ic.cate_id = c.cate_id
Which returns the rows
1;"aaaa"
1;"7777"
1;"bbbb"
2;"aaaa"
2;"1111"
2;"cccc"
It is what I was expecting also returns the right rows when I select items with cate_friendly_id = "7777" for example:
SELECT i.item_id, c.cate_friendly_id
FROM item i , item_category ic, category c
WHERE
i.item_id = ic.item_id
AND ic.cate_id = c.cate_id
AND (
c.cate_friendly_id = '7777'
)
Return 1 row which is:
1;"7777"
And return 2 rows if I filter by category "aaaa"
1;"aaaa"
2;"aaaa"
That is also fine, but when I select items both with cate_friendly_id = "aaaa" and "7777", no row is found, for example:
SELECT i.item_id, c.cate_friendly_id
FROM item i , item_category ic, category c
WHERE
i.item_id = ic.item_id
AND ic.cate_id = c.cate_id
AND (
c.cate_friendly_id = 'aaaa'
AND c.cate_friendly_id = '7777'
)
Was it not supposed to return me item 1 witch has both category "aaaa" and "7777"?
Thanks you for the help!
It is the expected answer, as any single category row is either 'aaaa' or '7777' but not both.
What you want is to query the items having two rows, one for 'aaaa' and one for '7777', like
SELECT i.item_id
FROM item i , item_category ic, category c
WHERE
i.item_id = ic.item_id
AND ic.cate_id = c.cate_id
AND c.cate_friendly_id in ( 'aaaa', '7777')
GROUP BY i.item_id
HAVING COUNT(*) = 2;
I have this table:
CREATE TABLE items (
id SERIAL PRIMARY KEY,
data TEXT,
parent INT,
posted INT
);
Each item has a piece of data, a timestamp, and a parent. I'd like to select the top 10 root items (parent = 0), sorted by the timestamp of the most recent child.
If item #1 has a child #2 that has a child #3, #3 is considered a child of #1.
How can I do this?
EDIT:
The query has been rewritten to
first sort the child items
get the root parent id and the rank for each item
select the top 10 parents
select the details for the top 10 parents
Common Table expressions have been used to incrementally select the data following the above steps.
WITH recursive c AS
(
SELECT *
FROM seeds
UNION ALL
SELECT
T.id,
T.parent,
c.topParentID,
(c.child_level + 1),
c.child_rank
FROM items AS T
INNER JOIN c ON T.parent = c.id
WHERE T.id <> T.parent
)
, seeds AS
(
SELECT
id,
parent,
parent AS topParentID,
0 AS child_level,
rank() OVER (ORDER BY posted DESC) child_rank
FROM items
WHERE parent <> 0
ORDER BY posted DESC
)
, rank_level AS
(
SELECT DISTINCT
c2.id id,
c_ranks.min_child_rank child_rank,
c_roots.max_child_level root_level
FROM
(
SELECT
id,
MAX(child_level) max_child_level
FROM c
GROUP BY id
)
c_roots
INNER JOIN c c2 ON c_roots.id = c2.id
INNER JOIN
(
SELECT
id,
MIN(child_rank) min_child_rank
FROM c
GROUP BY id
)
c_ranks
ON c2.id = c_ranks.id
)
, top_10_parents AS
(
SELECT
c.topParentID id,
MIN(rl.child_rank) id_rank
FROM rank_level rl
INNER JOIN c ON rl.id = c.id AND c.child_level = rl.root_level
GROUP BY c.topParentID
ORDER BY MIN(rl.child_rank)
limit 10
)
SELECT
i.*
FROM
items i
INNER JOIN top_10_parents tp ON tp.id = i.id
ORDER BY tp.id_rank;
SQL Fiddle
Reference:
WITH Queries (Common Table Expressions) on PostgreSQL Manual
Have 2 tables for example:
In 1st: object & parent columns
object | parent
-------+---------
object1| null
object2| object1
object3| null
2nd has: object & reference columns
object | reference
-------+---------
object1| null
object2| null
object3| object1
Need to query tables to order like following: parent is first, then - child(s), objects which have reference(s) to parent.
object1
object2
object3
Is it possible to do in one SQL query or need to sort manually in an array? Seems it is a classical task, probably solution already exists somewhere?
Is this what you're looking for?
CREATE TABLE oparen (object varchar(10), parent varchar(10));
CREATE TABLE oref (object varchar(10), ref varchar(10));
INSERT INTO oparen VALUES
('object1',null),('object2','object1'),
('object3',null),('object4','object2');
INSERT INTO oref VALUES
('object1',null),('object2',null),('object3','object1'),
('object5','object6'),('object6','object1'),('object7','object4');
WITH hier AS (
SELECT parent AS obj, 1 AS rank FROM oparen
WHERE parent IS NOT NULL
UNION
SELECT object, 2 FROM oparen
WHERE parent IS NOT NULL
UNION
SELECT object, 3 FROM oref
WHERE ref IS NOT NULL),
allobj AS (
SELECT object AS obj FROM oparen
UNION
SELECT object FROM oref)
SELECT a.obj, coalesce(h.rank, 4) AS rank
FROM allobj a LEFT JOIN hier h ON a.obj = h.obj
ORDER BY coalesce(h.rank, 4), a.obj;
EDIT: After the improved example in the answer below, the following query should do the trick:
WITH parents AS (
SELECT parent AS obj, 1 AS rank FROM oparen
WHERE parent IS NOT NULL
),
family AS (
SELECT * FROM parents
UNION ALL
SELECT object, 2 FROM oparen op
WHERE parent IS NOT NULL
AND NOT EXISTS (SELECT obj FROM parents WHERE obj = op.object)
),
hier AS (
SELECT * FROM family
UNION ALL
SELECT object AS obj, coalesce(f.rank + 2, 5) AS rank
FROM oref LEFT JOIN family f ON oref.ref = f.obj
WHERE ref IS NOT NULL
),
allobj AS (
SELECT object AS obj FROM oparen
UNION
SELECT object FROM oref)
SELECT a.obj, h.rank AS rank
FROM allobj a LEFT JOIN hier h ON a.obj = h.obj
ORDER BY h.rank, a.obj;
Testbed creation in the top is updated according to the new requirements.
I inserted following data:
INSERT INTO oparen VALUES
('object1',null),('object2','object1'),('object3',null),('object4','object2');
INSERT INTO oref VALUES
('object1',null),('object2',null),('object3','object1'),('object5','object6'),('object6','object1');
Order is incorrect and object2 listed twice. DISTINCT on obj breaks the order also. Should go 6 then 5.
No, does not work: checked for another data and simplified to use and only by oref table content:
INSERT INTO oref VALUES
('object1',null),('object2',null),('object3','object1'),
('object5','object6'),('object6','object1'),('object7','object4'), ('object4','object5');
WITH family AS (
SELECT object AS obj, 1 AS rank FROM oref
WHERE ref IS NULL
),
hier AS (
SELECT * FROM family
UNION ALL
SELECT object AS obj, coalesce(f.rank + 2, 5) AS rank
FROM oref LEFT JOIN family f ON oref.ref = f.obj
WHERE ref IS NOT NULL
),
allobj AS (
SELECT object AS obj FROM oref)
SELECT a.obj, h.rank AS rank
FROM allobj a
LEFT JOIN hier h ON a.obj = h.obj
ORDER BY h.rank, a.obj;
Think need to use recursive queries here. Will write and post here.
Following recursive query works:
WITH RECURSIVE tables(object, rank) AS (
SELECT DISTINCT o.object, 1 AS rank FROM oref o
WHERE o.ref IS NULL
UNION
SELECT o.object, t.rank + 1 AS rank
FROM (SELECT DISTINCT o.object, o.ref FROM oref o
WHERE ref IS NOT NULL) o, tables t
WHERE o.ref = t.object AND rank <= t.rank
),
ordered AS (
SELECT * FROM tables
)
SELECT * FROM tables
WHERE tables.rank = (SELECT MAX(rank) FROM ordered WHERE ordered.object = tables.object)
ORDER BY rank;
Any comments, questions, objections, propositions? ;)
I have three tables:
CREATE TABLE person
(id int,
name char(50))
CREATE TABLE eventtype
(id int,
description char(50))
CREATE TABLE event
(person_id int,
eventtype_id int,
duration int)
What I want is a single query which gives me a list of the total duration of each eventtype for each person, including all zero entries. E.g. if there are 10 people and 15 different eventtypes, there should be 150 rows returned, irrespective of the contents of the event table.
I can get an outer join to work between two tables (e.g. durations for all eventtypes), but not with a second outer join.
Thanks!
You'll have to add a CROSS APPLY to the mix to get the non-existing relations.
SELECT q.name, q.description, SUM(q.Duration)
FROM (
SELECT p.Name, et.description, Duration = 0
FROM person p
CROSS APPLY eventtype et
UNION ALL
SELECT p.Name, et.description, e.duration
FROM person p
INNER JOIN event e ON e.person_id = p.id
INNER JOIN eventtype et ON et.id = e.eventtypeid
) q
GROUP BY
q.Name, q.description
You can cross join person and eventtype, and then just join the result to the event table:
SELECT
p.Name,
et.Description,
COALESCE(e.duration,0)
FROM
person p
cross join
eventtype et
left join
event e
on
p.id = e.person_id and
et.id = e.eventtype_id
A cross join is one where, for each row in the left table, it's joined to every row in the right table.
If you want a row for every combination of person and eventtype, that suggets a CROSS JOIN. To get the duration we need to join to event, but this needs to be an OUTER join since there might not always be a row. Your use of "total" suggests there there could be more than one event for a given combination of person and event, so we'll need a SUM in there as well.
Sample data:
insert person values ( 1, 'Joe' )
insert person values ( 2, 'Bob' )
insert person values ( 3, 'Tim' )
insert eventtype values ( 1, 'Cake' )
insert eventtype values ( 2, 'Pie' )
insert eventtype values ( 3, 'Beer' )
insert event values ( 1, 1, 10 )
insert event values ( 1, 2, 10 )
insert event values ( 1, 2, 5 )
insert event values ( 2, 1, 10 )
insert event values ( 2, 2, 7 )
insert event values ( 3, 2, 8 )
insert event values ( 3, 3, 16 )
insert event values ( 1, 1, 10 )
The query:
SELECT
PET.person_id
, PET.person_name
, PET.eventtype_id
, PET.eventtype_description
, ISNULL(SUM(E.duration), 0) total_duration
FROM
(
SELECT
P.id person_id
, P.name person_name
, ET.id eventtype_id
, ET.description eventtype_description
FROM
person P
CROSS JOIN eventtype ET
) PET
LEFT JOIN event E ON PET.person_id = E.person_id
AND PET.eventtype_id = E.eventtype_id
GROUP BY
PET.person_id
, PET.person_name
, PET.eventtype_id
, PET.eventtype_description
Output:
person_id person_name eventtype_id eventtype_description total_duration
----------- ----------- ------------ --------------------- --------------
1 Joe 1 Cake 20
1 Joe 2 Pie 15
1 Joe 3 Beer 0
2 Bob 1 Cake 10
2 Bob 2 Pie 7
2 Bob 3 Beer 0
3 Tim 1 Cake 0
3 Tim 2 Pie 8
3 Tim 3 Beer 16
Warning: Null value is eliminated by an aggregate or other SET operation.
(9 row(s) affected)
tblUserProfile - I have a table which holds all the Profile Info (too many fields)
tblMonthlyProfiles - Another table which has just the ProfileID in it (the idea is that this table holds 2 profileids which sometimes become monthly profiles (on selection))
Now when I need to show monthly profiles, I simply do a select from this tblMonthlyProfiles and Join with tblUserProfile to get all valid info.
If there are no rows in tblMonthlyProfile, then monthly profile section is not displayed.
Now the requirement is to ALWAYS show Monthly Profiles. If there are no rows in monthlyProfiles, it should pick up 2 random profiles from tblUserProfile. If there is only one row in monthlyProfiles, it should pick up only one random row from tblUserProfile.
What is the best way to do all this in one single query ?
I thought something like this
select top 2 * from tblUserProfile P
LEFT OUTER JOIN tblMonthlyProfiles M
on M.profileid = P.profileid
ORder by NEWID()
But this always gives me 2 random rows from tblProfile. How can I solve this ?
Try something like this:
SELECT TOP 2 Field1, Field2, Field3, FinalOrder FROM
(
select top 2 Field1, Field2, Field3, FinalOrder, '1' As FinalOrder from tblUserProfile P JOIN tblMonthlyProfiles M on M.profileid = P.profileid
UNION
select top 2 Field1, Field2, Field3, FinalOrder, '2' AS FinalOrder from tblUserProfile P LEFT OUTER JOIN tblMonthlyProfiles M on M.profileid = P.profileid ORDER BY NEWID()
)
ORDER BY FinalOrder
The idea being to pick two monthly profiles (if that many exist) and then 2 random profiles (as you correctly did) and then UNION them. You'll have between 2 and 4 records at that point. Grab the top two. FinalOrder column is an easy way to make sure that you try and get the monthly's first.
If you have control of the table structure, you might save yourself some trouble by simply adding a boolean field IsMonthlyProfile to the UserProfile table. Then it's a single table query, order by IsBoolean, NewID()
In SQL 2000+ compliant syntax you could do something like:
Select ...
From (
Select TOP 2 ...
From tblUserProfile As UP
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Order By NewId()
) As RandomProfile
Union All
Select MP....
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From (
Select TOP 1 ...
From tblUserProfile As UP
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
Order By NewId()
) As RandomProfile
Using SQL 2005+ CTE you can do:
With
TwoRandomProfiles As
(
Select TOP 2 ..., ROW_NUMBER() OVER ( ORDER BY UP.ProfileID ) As Num
From tblUserProfile As UP
Order By NewId()
)
Select MP.Col1, ...
From tblUserProfile As UP
Join tblMonthlyProfile As MP
On MP.ProfileId = UP.ProfileId
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) >= 1
Union All
Select ...
From TwoRandomProfiles
Where Not Exists( Select 1 From tblMonthlyProfile As MP1 )
Union All
Select ...
From TwoRandomProfiles
Where ( Select Count(*) From tblMonthlyProfile As MP1 ) = 1
And Num = 1
The CTE has the advantage of only querying for the random profiles once and the use of the ROW_NUMBER() column.
Obviously, in all the UNION statements the number and type of the columns must match.