0

I've got a many to many relationship between items-itemnames-languages

The itemnames don't appear for every language.

I'd like to get a result with all the items only represented once, but be able to set the languageId to default to.

For example items 1,2,3 are defined in two languages, and item 4 and 5 have one language each, but the languages are different

[itemid][languageid][name]
1,       1,         item1
1,       2,         leItem1
2,       1,         item2
2,       2,         leItem2
3,       1,         item3
3,       2,         leItem3
4,       1,         item4
5,       2,         leItem5

I'd like to create a query that only gives me one of each itemID, but allow me to specify which language to prefer, so if I select a languageID of 2, my query would only return item names for that start with 'leItem' with the exception of item 4, which should still give me item4

Any ideas how to achieve this with a SELECT?

The theory (how I'm trying to get it to work), is that I create two queries, one for all where languageID matches x , and second where each item is represented (by group or distinct) and then merge the results.


sql for generating the table

-- Languages
CREATE TABLE [Languages] (
    [id] INT NOT NULL PRIMARY KEY IDENTITY,
    [language] NVARCHAR(20) NOT NULL ,
    [languagecode] NVARCHAR(6) NOT NULL
);


-- Items
CREATE TABLE [Items] (
    [id] INT NOT NULL PRIMARY KEY IDENTITY,
    [ImageId] INT ,
    [lastupdate] DATETIME,
    [isactive] BIT NOT NULL DEFAULT 'TRUE'
);

-- ItemNames
CREATE TABLE [ItemNames] (
    [itemId] INT NOT NULL ,
    [languageId] INT NOT NULL ,
    [name] NVARCHAR(50) NOT NULL ,
    FOREIGN KEY (itemId) REFERENCES Items(id),
    FOREIGN KEY (languageId) REFERENCES Languages(id),
    PRIMARY KEY ([itemId],[languageId])
);
Daniel
  • 34,125
  • 17
  • 102
  • 150
  • What are the actual tables involved (without the view). Also, as mentioned below what should happen if there are multiple non-preferred languages? – Leigh Apr 20 '12 at 03:24
  • those are the actual tables, if there are multiple it should get the one with the lowest id – Daniel Apr 20 '12 at 15:17

3 Answers3

1

You might be able to solve this with a subquery. This might not be the best way way of solving it, but I'll take a stab at it.

SELECT DISTINCT
  outer.itemid, 
  outer.languageid, 
  outer.name 
FROM 
  table AS outer
WHERE 
  outer.languageid = 2  
  OR NOT EXIST (SELECT * FROM table AS inner WHERE languageid = 2 AND inner.itemid = outer.itemid)

The first where condition is to provide all the entries that belongs to languageid = 2, the second part of the where conditions return true only if there isn't an entry with languageid = 2 for the current item you're looking at.

http://dev.mysql.com/doc/refman/5.0/en/exists-and-not-exists-subqueries.html

David Z.
  • 5,621
  • 2
  • 20
  • 13
  • Thanks David, I'm handling the first set of joins through a view anyway, so a subquery is inevitable. I guess the area I'm most interested in is what follow `WHEN` I'll give this a tinker – Daniel Apr 20 '12 at 01:49
  • 2
    If a third language is introduced into the data set, say `4, 3, Foobar4`, this query will return two rows for item 4. – Vadim K. Apr 20 '12 at 02:02
  • 1
    True, it's not very clear from the description how to handle a case like that. What happens if the preferred language does not exist but there are multiple non-preferred languages. – David Z. Apr 20 '12 at 02:15
1

I would try using a CASE statement. Basically the idea is to determine if the preferred language exists and return null if it does not. That way you can utilize ISNULL to returned the preferred language (when it is populated) otherwise return the minimum language ID:

// replace @preferred with the preferred language id ie 2
SELECT  itn.ItemID, itn.LanguageID, itn.Name
FROM    ItemNames itn INNER JOIN 
        (
            SELECT  itemID, 
                    // use preferred language if it exists
                    // otherwise, use the minimum languageID
                    ISNULL( MIN ( CASE WHEN LanguageID = @preferred THEN LanguageID ELSE NULL END ) 
                                , MIN (LanguageID) 
                          ) AS LanguageID
            FROM   ItemNames 
            GROUP BY itemID
        )
        sel ON sel.ItemID = itn.ItemID AND sel.LanguageID = itn.LanguageID 
Leigh
  • 28,765
  • 10
  • 55
  • 103
0

this is how I'm handling it for now...

I've created a cffunction that loops through the query repeatedly. It works, and because I'm sub-querying, I think performance should be ok. Seems like using a database side query might be too difficult for me.

<cffunction name="getLanguageUniqueEntries" output="no" returntype="query" >
    <cfargument name="q" Type="query" required="true">
    <cfargument name="languageId" Type="string" required="true">
    <cfargument name="uniqueColumn" Type="string" required="false" default="ID">

    <!---  Copy structure, assume table has an id column where no item matches -1 --->
    <cfquery dbtype="query" name="newQ"> SELECT * FROM q WHERE #uniqueColumn# = -1 </cfquery>

    <!--- get list of unigue IDs --->
    <cfquery dbtype="query" name="uniquePropertyIDs"> SELECT #uniqueColumn# FROM q GROUP BY #uniqueColumn# </cfquery>

    <!--- loop through unique IDs ---->
    <cfloop query="uniquePropertyIDs">
        <cfset colIdVal = uniquePropertyIDs[uniqueColumn][uniquePropertyIDs.CurrentRow]>

        <!--- find row in language --->
        <cfquery dbtype="query" name="currentLangQ" maxrows="1">
            SELECT * FROM q WHERE #uniqueColumn# = #colIdVal# AND LanguageID = #languageId#
        </cfquery>

        <cfif currentLangQ.recordcount NEQ 0>
            <!--- insert row --->
            <cfquery dbtype="query" name="newQ"> SELECT * FROM newQ UNION SELECT * FROM currentLangQ </cfquery>
        <cfelse>
            <!--- entry in language not found, get a default value --->
            <cfquery dbtype="query" name="anyLangQ" maxrows="1"> SELECT * FROM q WHERE #uniqueColumn# = #colIdVal# ORDER BY LanguageID</cfquery>

            <!--- insert row --->
            <cfquery dbtype="query" name="newQ"> SELECT * FROM newQ UNION SELECT * FROM anyLangQ</cfquery>
        </cfif>

    </cfloop>
    <cfreturn newQ>
</cffunction>
Daniel
  • 34,125
  • 17
  • 102
  • 150
  • I do not think you need to jump through such hoops for this :). If you post the ddl's of the tables involved, we could offer more specific advice. – Leigh Apr 20 '12 at 17:27
  • Use management studio to generate the [DDL](http://en.wikipedia.org/wiki/Data_Definition_Language#CREATE_statements). – Leigh Apr 20 '12 at 20:06
  • .. or since it's only a few tables you could do it by hand. We just need to see the table structure ie column names and data types. – Leigh Apr 21 '12 at 15:24
  • added the SQL to original post – Daniel Apr 23 '12 at 16:28