Query a union table with fields as columns - sql

I'm not quite sure if this is possible, or falls into the category of pivot tables, but I figured I'd go to the pros to see.
I have three basic tables: Card, Property, and CardProperty. Since cards do not have the same properties, and often multiple values for the same property, I decided to use the union table approach to store data instead of having a really big column structure in my card table.
The property table is a basic keyword/value type table. So you have the keyword ATK and the value assigned to it. There is another property called SpecialType which a card can have multiple values for, such as "Sycnro" and "DARK"
What I'd like to do is create a view or stored procedure that gives me the Card Id, Card Name, and all the property keywords assigned to the card as columns and their values in the ResultSet for a card specified. So ideally I'd have a result set like:
ID NAME SPECIALTYPE
1 Red Dragon Archfiend Synchro
1 Red Dragon Archfiend DARK
1 Red Dragon Archfiend Effect
and I could tally my results that way.
I guess even slicker would be to simply concatenate the properties together based on their keyword, so I could generate a ResultSet like:
1 Red Dragon Archfiend Synchro/DARK/Effect
..but I don't know if that's feasible.
Help me stackoverflow Kenobi! You're my only hope.

Is this for SQL server?
If yes then
Concatenate Values From Multiple Rows Into One Column (2000)
Concatenate Values From Multiple Rows Into One Column Ordered (2005+)

Related but values are values are kept in separate columns and you have know your "special types" a head of time: SQL query to compare product sales by month
Otherwise I would do this with cursor in a stored procedure or preform the transformation in the business or presentation layer.
Stab at sql if you know all cases:
Select
ID,NAME
,Synchro+DARK+Effect -- add a some substring logic to trim any trailing /'s
from
(select
ID
,NAME
--may need to replace max() with min().
,MAX(CASE SPECIALTYPE WHEN "Synchro" THEN SPECIALTYPE +"/" ELSE "" END) Synchro
,MAX(CASE SPECIALTYPE WHEN "DARK" THEN SPECIALTYPE +"/" ELSE "" END) DARK
,MAX(CASE SPECIALTYPE WHEN "Effect" THEN SPECIALTYPE ELSE "" END) Effect
from
table
group by
ID
,NAME) sub1

Don't collapse by concatenation for storage of related records in your database. Its not exactly best practices.
What you're describing is a pivot table. Pivot tables are hard. I'd suggest avoiding them if at all possible.
Why not just read in your related rows and process them in memory? It doesn't sound like you're going to spend too many milliseconds doing this...

One option is to have Properties have a PropertyType, so:
table cards
integer ID | string name | ... (other properties common to all Cards)
table property_types
integer ID | string name | string format | ... (possibly validations)
table properties
integer ID | integer property_type_id | string name | string value
foreign key property_type_id references property_types.ID
table cards_properties
integer ID | integer card_id | integer property_id
foreign key card_id references cards.ID
foreign key property_id references propertiess.ID
That way, when you want to set a new property value, you can validate it by its type. One type could be "SpecialType" with an enumeration of values.

I do have a type/format for my properties table, that way I know how to cast/evaluate when I'm dealing with an integer value. I wasn't sure if it was pertinent to this issue or not.

Related

Creating a computed Int column in SQL Server from a text column

I want to create a new column in an existing SQL Server table that utilizes state abbreviations (an existing column) to recode the states into a number (1-50) for statistical analysis that will be performed after exporting the output.
Assuming that Alabama is AL = 1 and Wyoming is WY = 50, how would I go about doing this for every state?
Two ways:
Create a lookup table, STATE_ID (with values 1..50) and STATE_ABBREV ('AL' to 'WY'), then join on this table.
Create a large CASE statement:
CASE STATE_ABBR
WHEN 'AL' THEN 1
...
WHEN 'WY' THEN 50
ELSE NULL
END AS STATE_ID
Using a lookup table is really preferred, as this puts the logic into a single place in case it gets used elsewhere. Plus, this is really data, and coding it into a view as code is not the right way to go.
A different approach, if you don't want to use a temporary or lookup table. Create a list of all states in the alphabetical order, and use the charindex function as shown below
create table #temp
( stateCode char(2))
INSERT INTO #temp values ('PA'),('AL'),('NJ'),('MA'),('DC')
select StateCode,charindex(Statecode+'|','AL|PA|NJ|NY|MA|DC|')/3+1 as numb
from #temp
order by numb
Note that charindex is slow, but this approach could be adapted to your computed field if you want.
Again, I think a lookup table is a better solution, but if it has to be a computed field, this should work for you

I want to show how many of the alumni attended the social event

I want an output to show how many alumni attended the event
I got Event Table
CREATE TABLE EVENT
(EVENTID INTEGER CONSTRAINT EVENT_ID_PK PRIMARY KEY,
EDATEANDTIME VARCHAR2(20),
EVENUE VARCHAR2(30),
EAUDIENCE VARCHAR2(30),
EATTENDED VARCHAR2(30),
EVENT_ALUMNI INTEGER,
CONSTRAINT EVENT_ALUMNI_FK FOREIGN KEY (EVENT_ALUMNI) REFERENCES
ALUMNI(ALUMNIID));
Here is what I inserted in Event
INSERT INTO EVENT VALUES(25993, 'Jan 14, 2015', 'Concorde Hall', 'Tobias, Tucker, Felix, Nicole, Desiree, Taylor, Frant, Ifeoma, Forrest, Stewart, Cole, Arthur, Thomas, Bo, Lucian',
'Tobias, Tucker, Felix, Nicole, Desiree, Taylor, Frant, Ifeoma, Forrest, Stewart, Cole, Arthur, Thomas, Bo',17337);
INSERT INTO EVENT VALUES(23823, 'July 18 2015', 'Rochester Hotel', 'Joan, Thalia, Haleeda', 'Joan, Haleeda'
,19927);
And I have a View Statement to view how many attended
CREATE VIEW VIEWH AS SELECT ETYPE, EDATEANDTIME, COUNT (*) EATTENDED FROM EVENT
WHERE EDATEANDTIME LIKE '%2015%' AND ETYPE = 'Social'
GROUP BY ETYPE, EDATEANDTIME, EATTENDED;
Here is where I got problem. When i run the query, the output is I got only one who attended the event instead of like 10 or 15
I want to know where i went wrong.
There are several potential problems with your setup, I mentioned a couple in a Comment to your question.
Regarding the count specifically: your first problem is you have EATTENDED in the GROUP BY clause. Why? This almost surely means that each group is exactly one row.
Then your next problem is counting tokens out of a comma-separated string. One way is using regexp_count() as TheGameiswar has shown. (err... his/her solution has disappeared? anyway, I was saying...)
Another is to use length() and translate():
select ... , 1 + length(EATTENDED) - length(translate(EATTENDED, ',', '') as ATT_CNT ...
A couple of notes about this:
The length difference, just like regexp_count(), counts commas in the string. You add one to count tokens (you don't care how many commas there are, you care how many names are separated by commas). translate() works faster than regexp_count() (all regexp functions are very powerful, but slower, so they should be used only when they are really needed, IF performance is important); length doesn't add much overhead since in Oracle the length of VARCHAR2 is always calculated and stored as part of the internal representation of a string.
I gave a different alias to the resulting column; in your original query you use the same name, EATTENDED, for a column in your base table and as an alias for this numeric column in your view. You can do it, it's legal, but it is almost certain to cause problems in the future.
It is because COUNT gives number of "rows" in the result and not number of "values in column".
To give you a simple example
example1_table:
col1
--------
a
b
c
SELECT COUNT(col1) FROM example1_table; // The result is 3, as there are three rows
example2_table:
col1
--------
a,b,c
SELECT COUNT(col1) FROM example1_table; // The result is 1, as there is only one row (it is not about number of values in it)
Note:
As stated by #mathguy you could have used a better Database structure.
Instead of using only one table EVENT create two tables let they be: table1 table2 so you could avoid redundancy.
CREATE TABLE table1
(EVENTID INTEGER CONSTRAINT EVENT_ID_PK PRIMARY KEY,
EDATEANDTIME VARCHAR2(20),
EVENUE VARCHAR2(30),
EVENT_ALUMNI INTEGER,
CONSTRAINT tab1_FK FOREIGN KEY (EVENT_ALUMNI) REFERENCES
ALUMNI(ALUMNIID));
CREATE TABLE table2
(
EAUDIENCE VARCHAR2(30),
EATTENDED VARCHAR2(30),
tab2ID INTEGER
CONSTRAINT tab2_FK FOREIGN KEY (tab2ID) REFERENCES
tab1(EVENT_ALUMNI));
If you don't want to make any changes in your table, assuming there is always a comma present between two attendees names you could try:
CREATE VIEW VIEWH AS SELECT ETYPE, EDATEANDTIME, regexp_count(EATTENDED,',')+1 FROM EVENT WHERE EDATEANDTIME LIKE '%2015%' AND ETYPE = 'Social' GROUP BY ETYPE, EDATEANDTIME;

Create column from other columns in Database

I have a table name: test
ID | Prefix | ACCID
ID's type is INTEGER which is selected from ID_SEQ
Prefix's type is VARCHAR(6)
ACCID is the combination of Prefix + ID
I want to auto-create ACCID when I insert the ID and Prefix value such as
INSERT INTO TEST (PREFIX) VALUES ('A01407V');
and the database store the ACCID as 'A01407V000001'
I create the sequence as
CREATE SEQUENCE ID_SEQ AS INT MAXVALUE 999999 CYCLE;
How to implement SQL statement to produce this result?
Thank you for all solutions and suggestions.
Ps. I use Apache Derby as my SQL Server
As documented in the manual, Derby supports generated columns (since Version 10.5)
The real problem is the formatting of a number with leading zeros as Derby has no function for that.
If you really, really think you need to store a value that can always be determined by the values already stored in the table, you can use something like this:
create table test
(
id integer,
prefix varchar(6),
accid generated always as (prefix||substr('000000', 1, 6 - length(rtrim(char(id))))||rtrim(char(id)))
);
The expression substr('000000', 1, 6 - length(rtrim(char(id))))||rtrim(char(id)) is just a complicated way to format a the ID with leading zeros.
I would highly recommend to not store this value though. It is much cleaner to create a view that shows this value if you do need access to this in SQL.
You can use COMPUTED Column.
Is a computed column that is based on some other column in the table. We can physically save the data of the column/ or not. Table will automatically update the value of this column.
syntax:
columnname AS expression [PERSISTED]
--PERSISTED will make it physically saved, otherwise it will be calculated every time.
We can create indexes on computed columns.
You add, The following in the table CREATE Script
ACCID AS Prefix + CAST(ID AS CHAR(6)) [PERSISTED]

Avoid sql_variant but use what?

My database holds template forms and real forms having values. Users will be able to create custom template forms with different types of fields. I will keep values in a separate table.
Forms table:
Id | Name | TemplateId
FormFields table:
Id | FormId | Name | ValueType (nvchar)
Values Table
FieldId | Value
When user designs a form it is saved into forms table having TemplateId NULL. Also FormFields table stores the fields that will be used for this form.
Later when user creates a real form using that template, the real values of this form (derieved from FormFields) will be stored in Values table.
Now, the value column in Values table seems to be sql_variant type. If I don't use sql_variant how can I solve this problem?
Ps: What about creating different tables for each kind of values? TableIntValues, TableBoolValues etc?
http://www.jotform.com/ can be a good sample for my project.
I would suggest using separate columns for the separate data types, along these lines:
FieldId | StringValue | IntegerValue | DateTimeValue
That way you can have queries run over the data sensibly, and also keep it type safe. You would obviously need logic to ensure that the data gets populated correctly, and a constraint to ensure only one column is populated and not all are NULL.

SQL query select from table and group on other column

I'm phrasing the question title poorly as I'm not sure what to call what I'm trying to do but it really should be simple.
I've a link / join table with two ID columns. I want to run a check before saving new rows to the table.
The user can save attributes through a webpage but I need to check that the same combination doesn't exist before saving it. With one record it's easy as obviously you just check if that attributeId is already in the table, if it is don't allow them to save it again.
However, if the user chooses a combination of that attribute and another one then they should be allowed to save it.
Here's an image of what I mean:
So if a user now tried to save an attribute with ID of 1 it will stop them, but I need it to also stop them if they tried ID's of 1, 10 so long as both 1 and 10 had the same productAttributeId.
I'm confusing this in my explanation but I'm hoping the image will clarify what I need to do.
This should be simple so I presume I'm missing something.
If I understand the question properly, you want to prevent the combination of AttributeId and ProductAttributeId from being reused. If that's the case, simply make them a combined primary key, which is by nature UNIQUE.
If that's not feasible, create a stored procedure that runs a query against the join for instances of the AttributeId. If the query returns 0 instances, insert the row.
Here's some light code to present the idea (may need to be modified to work with your database):
SELECT COUNT(1) FROM MyJoinTable WHERE AttributeId = #RequestedID
IF ##ROWCOUNT = 0
BEGIN
INSERT INTO MyJoinTable ...
END
You can control your inserts via a stored procedure. My understanding is that
users can select a combination of Attributes, such as
just 1
1 and 10 together
1,4,5,10 (4 attributes)
These need to enter the table as a single "batch" against a (new?) productAttributeId
So if (1,10) was chosen, this needs to be blocked because 1-2 and 10-2 already exist.
What I suggest
The stored procedure should take the attributes as a single list, e.g. '1,2,3' (comma separated, no spaces, just integers)
You can then use a string splitting UDF or an inline XML trick (as shown below) to break it into rows of a derived table.
Test table
create table attrib (attributeid int, productattributeid int)
insert attrib select 1,1
insert attrib select 1,2
insert attrib select 10,2
Here I use a variable, but you can incorporate as a SP input param
declare #t nvarchar(max) set #t = '1,2,10'
select top(1)
t.productattributeid,
count(t.productattributeid) count_attrib,
count(*) over () count_input
from (select convert(xml,'<a>' + replace(#t,',','</a><a>') + '</a>') x) x
cross apply x.x.nodes('a') n(c)
cross apply (select n.c.value('.','int')) a(attributeid)
left join attrib t on t.attributeid = a.attributeid
group by t.productattributeid
order by countrows desc
Output
productattributeid count_attrib count_input
2 2 3
The 1st column gives you the productattributeid that has the most matches
The 2nd column gives you how many attributes were matched using the same productattributeid
The 3rd column is how many attributes exist in the input
If you compare the last 2 columns and the counts
match - you can use the productattributeid to attach to the product which has all these attributes
don't match - then you need to do an insert to create a new combination